From 86c75f81d008de0e81192ed24b1228c11aac2e5d Mon Sep 17 00:00:00 2001 From: Adam Wootton Date: Mon, 26 Aug 2024 12:09:26 -0400 Subject: [PATCH] feat: expose js client from nextjs sdk and allow debugger to use nextjs (#942) --- lib/web-debugger/next.tsx | 19 ++++++++++ lib/web-debugger/package.json | 8 ++++ .../src/initializeDevCycleDebugger.ts | 37 ++++++++++++++----- lib/web-debugger/tsconfig.lib.json | 8 +++- sdk/js/src/Client.ts | 13 +++++++ sdk/nextjs/index.ts | 7 ++++ sdk/nextjs/src/client/useDevCycleClient.ts | 6 +++ yarn.lock | 3 ++ 8 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 lib/web-debugger/next.tsx create mode 100644 sdk/nextjs/src/client/useDevCycleClient.ts diff --git a/lib/web-debugger/next.tsx b/lib/web-debugger/next.tsx new file mode 100644 index 000000000..1bc0e7afb --- /dev/null +++ b/lib/web-debugger/next.tsx @@ -0,0 +1,19 @@ +import { useEffect } from 'react' +import { + initializeDevCycleDebugger, + DebuggerIframeOptions, +} from './src/initializeDevCycleDebugger.js' +import { useDevCycleClient } from '@devcycle/nextjs-sdk' + +export const DevCycleDebugger = (options: DebuggerIframeOptions): null => { + const client = useDevCycleClient() + + useEffect(() => { + const cleanupPromise = initializeDevCycleDebugger(client, options) + return () => { + cleanupPromise.then((cleanup) => cleanup()) + } + }, [client]) + + return null +} diff --git a/lib/web-debugger/package.json b/lib/web-debugger/package.json index 9af69b9b2..0eb084495 100644 --- a/lib/web-debugger/package.json +++ b/lib/web-debugger/package.json @@ -28,6 +28,10 @@ "./react": { "import": "./react.js", "types": "./react.d.ts" + }, + "./next": { + "import": "./next.js", + "types": "./next.d.ts" } }, "dependencies": { @@ -35,9 +39,13 @@ }, "peerDependencies": { "@devcycle/js-client-sdk": "*", + "@devcycle/nextjs-sdk": "*", "@devcycle/react-client-sdk": "*" }, "peerDependenciesMeta": { + "@devcycle/nextjs-sdk": { + "optional": true + }, "@devcycle/react-client-sdk": { "optional": true } diff --git a/lib/web-debugger/src/initializeDevCycleDebugger.ts b/lib/web-debugger/src/initializeDevCycleDebugger.ts index 20633e296..abf98d94e 100644 --- a/lib/web-debugger/src/initializeDevCycleDebugger.ts +++ b/lib/web-debugger/src/initializeDevCycleDebugger.ts @@ -5,6 +5,8 @@ import { } from '@devcycle/js-client-sdk' import { BucketedUserConfig } from '@devcycle/types' +type NextClient = Omit + type LiveEvent = { type: string key?: string @@ -18,6 +20,7 @@ type ClientData = { liveEvents: LiveEvent[] loadCount: number expanded: boolean + allowIdentify: boolean } } @@ -26,6 +29,7 @@ const clientData: ClientData = { liveEvents: [], loadCount: 0, expanded: false, + allowIdentify: true, }, } @@ -47,10 +51,10 @@ class IframeManager { debuggerUrl: string position: string debugLogs: boolean - client: DevCycleClient + client: DevCycleClient | NextClient constructor( - client: DevCycleClient, + client: DevCycleClient | NextClient, { position = 'right', debuggerUrl = 'https://debugger.devcycle.com', @@ -58,6 +62,7 @@ class IframeManager { }: DebuggerIframeOptions = {}, ) { this.client = client + clientData.current.allowIdentify = 'identifyUser' in client this.debuggerUrl = debuggerUrl this.position = position this.debugLogs = debugLogs @@ -229,13 +234,25 @@ class IframeManager { event.data.type === 'DEVCYCLE_IDENTIFY_USER' && event.data.user ) { - this.client.identifyUser(event.data.user).then(() => { - this.updateIframeData() - }) + if ('identifyUser' in this.client) { + this.client.identifyUser(event.data.user).then(() => { + this.updateIframeData() + }) + } else { + this.log( + 'Unable to change user identity from debugger in Next.js', + ) + } } else if (event.data.type === 'DEVCYCLE_RESET_USER') { - this.client.resetUser().then(() => { - this.updateIframeData() - }) + if ('resetUser' in this.client) { + this.client.resetUser().then(() => { + this.updateIframeData() + }) + } else { + this.log( + 'Unable to change user identity from debugger in Next.js', + ) + } } else if (event.data.type === 'DEVCYCLE_REFRESH') { this.updateIframeData() } else if (event.data.type === 'DEVCYCLE_TOGGLE_OVERLAY') { @@ -259,7 +276,7 @@ class IframeManager { } export const checkShouldEnable = async ( - client: DevCycleClient, + client: DevCycleClient | NextClient, { shouldEnable, shouldEnableVariable, @@ -284,7 +301,7 @@ export const checkShouldEnable = async ( } export const initializeDevCycleDebugger = async ( - client: DevCycleClient, + client: DevCycleClient | NextClient, { shouldEnable, shouldEnableVariable, diff --git a/lib/web-debugger/tsconfig.lib.json b/lib/web-debugger/tsconfig.lib.json index fcaff409d..fe8e804a2 100644 --- a/lib/web-debugger/tsconfig.lib.json +++ b/lib/web-debugger/tsconfig.lib.json @@ -5,6 +5,12 @@ "declaration": true, "types": ["node"] }, - "include": ["src/**/*.ts", "src/**/*.tsx", "index.ts", "react.tsx"], + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "index.ts", + "react.tsx", + "next.tsx" + ], "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] } diff --git a/sdk/js/src/Client.ts b/sdk/js/src/Client.ts index 4921d1c83..eebc293a5 100644 --- a/sdk/js/src/Client.ts +++ b/sdk/js/src/Client.ts @@ -403,6 +403,12 @@ export class DevCycleClient< user: DevCycleUser, callback?: ErrorCallback, ): Promise | void { + if (this.options.next) { + this.logger.error( + 'Unable to change user identity from the clientside in Next.js', + ) + return + } const promise = this._identifyUser(user) if (callback && typeof callback == 'function') { @@ -460,6 +466,13 @@ export class DevCycleClient< resetUser( callback?: ErrorCallback, ): Promise | void { + if (this.options.next) { + this.logger.error( + 'Unable to change user identity from the clientside in Next.js', + ) + return + } + let oldAnonymousId: string | null | undefined const anonUser = new DVCPopulatedUser( { isAnonymous: true }, diff --git a/sdk/nextjs/index.ts b/sdk/nextjs/index.ts index 6538707f1..108072611 100644 --- a/sdk/nextjs/index.ts +++ b/sdk/nextjs/index.ts @@ -1,5 +1,7 @@ // Use this file to export React client code (e.g. those with 'use client' directive) // or other non-server utilities +import { DevCycleClient as JSClient } from '@devcycle/js-client-sdk' + export { useVariable, useVariableValue } from './src/client/useVariableValue' export type * from './src/common/types' export { useUserIdentity } from './src/client/useUserIdentity' @@ -7,5 +9,10 @@ export { useTrack } from './src/client/useTrack' export { useAllVariables } from './src/client/useAllVariables' export { useAllFeatures } from './src/client/useAllFeatures' export { renderIfEnabled } from './src/client/renderIfEnabled' +export { useDevCycleClient } from './src/client/useDevCycleClient' export { DevCycleClientsideProvider } from './src/client/DevCycleClientsideProvider' export { DVCVariable, DVCVariableValue } from '@devcycle/react-client-sdk' + +type DevCycleClient = Omit + +export type { DevCycleClient } diff --git a/sdk/nextjs/src/client/useDevCycleClient.ts b/sdk/nextjs/src/client/useDevCycleClient.ts new file mode 100644 index 000000000..4e287720b --- /dev/null +++ b/sdk/nextjs/src/client/useDevCycleClient.ts @@ -0,0 +1,6 @@ +import { useDevCycleClient as internalUseClient } from './internal/useDevCycleClient' +import { DevCycleClient } from '@devcycle/js-client-sdk' + +export const useDevCycleClient = (): DevCycleClient => { + return internalUseClient() +} diff --git a/yarn.lock b/yarn.lock index f45b4391f..acfb6bfeb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4857,8 +4857,11 @@ __metadata: "@devcycle/types": ^1.16.2 peerDependencies: "@devcycle/js-client-sdk": "*" + "@devcycle/nextjs-sdk": "*" "@devcycle/react-client-sdk": "*" peerDependenciesMeta: + "@devcycle/nextjs-sdk": + optional: true "@devcycle/react-client-sdk": optional: true languageName: unknown