diff --git a/packages/react-native-sdk/package.json b/packages/react-native-sdk/package.json index 85f1e2b8..f5e7d707 100644 --- a/packages/react-native-sdk/package.json +++ b/packages/react-native-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@reflag/react-native-sdk", - "version": "0.1.3", + "version": "0.1.4", "license": "MIT", "repository": { "type": "git", @@ -32,7 +32,7 @@ }, "dependencies": { "@react-native-async-storage/async-storage": "^2.2.0", - "@reflag/react-sdk": "1.4.3" + "@reflag/react-sdk": "1.4.4" }, "peerDependencies": { "react": "*", diff --git a/packages/react-sdk/README.md b/packages/react-sdk/README.md index ad19b737..55c3e3f8 100644 --- a/packages/react-sdk/README.md +++ b/packages/react-sdk/README.md @@ -541,7 +541,8 @@ The `` initializes the Reflag SDK, fetches flags and starts list - `apiBaseUrl`: Optional base URL for the Reflag API. Use this to override the default API endpoint, - `appBaseUrl`: Optional base URL for the Reflag application. Use this to override the default app URL, - `sseBaseUrl`: Optional base URL for Server-Sent Events. Use this to override the default SSE endpoint, -- `debug`: Set to `true` to enable debug logging to the console, +- `logger`: Optional custom logger implementation (`debug`, `info`, `warn`, `error`) used by the underlying client, +- `debug`: Set to `true` to enable debug logging to the console. If both `logger` and `debug` are provided, `logger` takes precedence, - `toolbar`: Optional [configuration](https://docs.reflag.com/supported-languages/browser-sdk/globals#toolbaroptions) for the Reflag toolbar, - `feedback`: Optional configuration for feedback collection diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index 803e2555..0c5633e9 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@reflag/react-sdk", - "version": "1.4.3", + "version": "1.4.4", "license": "MIT", "repository": { "type": "git", diff --git a/packages/react-sdk/src/index.tsx b/packages/react-sdk/src/index.tsx index dadc04a1..26a69b71 100644 --- a/packages/react-sdk/src/index.tsx +++ b/packages/react-sdk/src/index.tsx @@ -14,6 +14,7 @@ import { CompanyContext, HookArgs, InitOptions, + Logger, RawFlag, ReflagClient, ReflagContext, @@ -152,6 +153,13 @@ export type ReflagPropsBase = { */ initialLoading?: boolean; + /** + * A custom logger to use for SDK logs. + * Use this for advanced control or filtering of SDK logs. + * If both `logger` and `debug` are provided, `logger` takes precedence. + */ + logger?: Logger; + /** * Set to `true` to enable debug logging to the console, */ @@ -164,7 +172,7 @@ export type ReflagPropsBase = { */ export type ReflagInitOptionsBase = Omit< InitOptions, - "user" | "company" | "other" | "otherContext" | "bootstrappedFlags" + "user" | "company" | "other" | "otherContext" | "bootstrappedFlags" | "logger" >; /** @@ -178,20 +186,28 @@ const reflagClients = new Map(); * Only creates a new ReflagClient is not already created or if it hook is run on the server. * @internal */ -function useReflagClient(initOptions: InitOptions, debug = false) { +function useReflagClient(initOptions: InitOptions & { debug?: boolean }) { + const { + debug = false, + logger, + publishableKey, + sdkVersion, + ...clientOptions + } = initOptions; const isServer = typeof window === "undefined"; - if (isServer || !reflagClients.has(initOptions.publishableKey)) { + if (isServer || !reflagClients.has(publishableKey)) { const client = new ReflagClient({ - ...initOptions, - logger: debug ? console : undefined, - sdkVersion: initOptions.sdkVersion ?? SDK_VERSION, + ...clientOptions, + publishableKey, + logger: logger ?? (debug ? console : undefined), + sdkVersion: sdkVersion ?? SDK_VERSION, }); if (!isServer) { - reflagClients.set(initOptions.publishableKey, client); + reflagClients.set(publishableKey, client); } return client; } - return reflagClients.get(initOptions.publishableKey)!; + return reflagClients.get(publishableKey)!; } type ProviderContextType = { @@ -204,7 +220,10 @@ const ProviderContext = createContext(null); /** * Props for the ReflagClientProvider. */ -export type ReflagClientProviderProps = Omit & { +export type ReflagClientProviderProps = Omit< + ReflagPropsBase, + "debug" | "logger" +> & { client: ReflagClient; }; @@ -282,6 +301,7 @@ export function ReflagProvider({ otherContext, loadingComponent, initialLoading = true, + logger, debug, ...config }: ReflagProps) { @@ -289,13 +309,12 @@ export function ReflagProvider({ () => ({ user, company, other: otherContext, ...context }), [user, company, otherContext, context], ); - const client = useReflagClient( - { - ...config, - ...resolvedContext, - }, + const client = useReflagClient({ + ...config, + ...resolvedContext, debug, - ); + logger, + }); // Initialize the client if it is not already initialized useEffect(() => { @@ -340,17 +359,17 @@ export function ReflagBootstrappedProvider({ children, loadingComponent, initialLoading = false, + logger, debug, ...config }: ReflagBootstrappedProps) { - const client = useReflagClient( - { - ...config, - ...flags.context, - bootstrappedFlags: flags.flags, - }, + const client = useReflagClient({ + ...config, + ...flags.context, + bootstrappedFlags: flags.flags, debug, - ); + logger, + }); // Initialize the client if it is not already initialized useEffect(() => { diff --git a/packages/react-sdk/test/usage.test.tsx b/packages/react-sdk/test/usage.test.tsx index 2f35fd52..8b5d5301 100644 --- a/packages/react-sdk/test/usage.test.tsx +++ b/packages/react-sdk/test/usage.test.tsx @@ -228,6 +228,44 @@ describe("", () => { }); }); + test("uses provided logger", async () => { + const logger = { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; + + const { result, unmount } = renderHook(() => useClient(), { + wrapper: ({ children }) => getProvider({ children, logger }), + }); + + await waitFor(() => { + expect(result.current.logger).toBe(logger); + }); + + unmount(); + }); + + test("prefers logger over debug mode", async () => { + const logger = { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; + + const { result, unmount } = renderHook(() => useClient(), { + wrapper: ({ children }) => getProvider({ children, logger, debug: true }), + }); + + await waitFor(() => { + expect(result.current.logger).toBe(logger); + }); + + unmount(); + }); + test("only calls init once with the same args", () => { const node = getProvider(); const initialize = vi.spyOn(ReflagClient.prototype, "initialize"); diff --git a/packages/vue-sdk/README.md b/packages/vue-sdk/README.md index 6553cbba..4b0770c0 100644 --- a/packages/vue-sdk/README.md +++ b/packages/vue-sdk/README.md @@ -185,7 +185,8 @@ The `` initializes the Reflag SDK, fetches flags and starts list - `apiBaseUrl`: Optional base URL for the Reflag API. Use this to override the default API endpoint, - `appBaseUrl`: Optional base URL for the Reflag application. Use this to override the default app URL, - `sseBaseUrl`: Optional base URL for Server-Sent Events. Use this to override the default SSE endpoint, -- `debug`: Set to `true` to enable debug logging to the console, +- `debug`: Set to `true` to enable debug logging to the console. If both `logger` and `debug` are provided, `logger` takes precedence, +- `logger`: Optional custom logger implementation (`debug`, `info`, `warn`, `error`) used by the underlying client, - `toolbar`: Optional [configuration](https://docs.reflag.com/supported-languages/browser-sdk/globals#toolbaroptions) for the Reflag toolbar, - `feedback`: Optional configuration for feedback collection diff --git a/packages/vue-sdk/package.json b/packages/vue-sdk/package.json index 0878f69f..b0894936 100644 --- a/packages/vue-sdk/package.json +++ b/packages/vue-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@reflag/vue-sdk", - "version": "1.3.1", + "version": "1.3.2", "license": "MIT", "repository": { "type": "git", diff --git a/packages/vue-sdk/src/ReflagBootstrappedProvider.vue b/packages/vue-sdk/src/ReflagBootstrappedProvider.vue index 219a755f..430f8361 100644 --- a/packages/vue-sdk/src/ReflagBootstrappedProvider.vue +++ b/packages/vue-sdk/src/ReflagBootstrappedProvider.vue @@ -8,19 +8,19 @@ const { flags, initialLoading = false, enableTracking = true, + logger, debug, ...config } = defineProps(); -const client = useReflagClient( - { - ...config, - ...flags?.context, - enableTracking, - bootstrappedFlags: flags?.flags, - }, +const client = useReflagClient({ + ...config, + ...flags?.context, + enableTracking, + bootstrappedFlags: flags?.flags, debug, -); + logger, +}); const isLoading = ref( client.getState() !== "initialized" ? initialLoading : false, diff --git a/packages/vue-sdk/src/ReflagProvider.vue b/packages/vue-sdk/src/ReflagProvider.vue index 254f2785..a9ad19b7 100644 --- a/packages/vue-sdk/src/ReflagProvider.vue +++ b/packages/vue-sdk/src/ReflagProvider.vue @@ -14,6 +14,7 @@ const { otherContext, initialLoading = true, enableTracking = true, + logger, debug, ...config } = defineProps(); @@ -25,14 +26,13 @@ const resolvedContext = computed(() => ({ ...context, })); -const client = useReflagClient( - { - ...config, - ...resolvedContext.value, - enableTracking, - }, +const client = useReflagClient({ + ...config, + ...resolvedContext.value, + enableTracking, debug, -); + logger, +}); const isLoading = ref( client.getState() !== "initialized" ? initialLoading : false, diff --git a/packages/vue-sdk/src/hooks.ts b/packages/vue-sdk/src/hooks.ts index 8d912752..90af75b0 100644 --- a/packages/vue-sdk/src/hooks.ts +++ b/packages/vue-sdk/src/hooks.ts @@ -38,22 +38,28 @@ const reflagClients = new Map(); * @internal */ export function useReflagClient( - initOptions: InitOptions, - debug = false, + initOptions: InitOptions & { debug?: boolean }, ): ReflagClient { + const { + debug = false, + logger, + publishableKey, + ...clientOptions + } = initOptions; const isServer = typeof window === "undefined"; - if (isServer || !reflagClients.has(initOptions.publishableKey)) { + if (isServer || !reflagClients.has(publishableKey)) { const client = new ReflagClient({ - ...initOptions, + ...clientOptions, + publishableKey, sdkVersion: SDK_VERSION, - logger: debug ? console : undefined, + logger: logger ?? (debug ? console : undefined), }); if (!isServer) { - reflagClients.set(initOptions.publishableKey, client); + reflagClients.set(publishableKey, client); } return client; } - return reflagClients.get(initOptions.publishableKey)!; + return reflagClients.get(publishableKey)!; } /** diff --git a/packages/vue-sdk/src/types.ts b/packages/vue-sdk/src/types.ts index 06240685..7bac3ea6 100644 --- a/packages/vue-sdk/src/types.ts +++ b/packages/vue-sdk/src/types.ts @@ -3,6 +3,7 @@ import type { Ref } from "vue"; import type { CompanyContext, InitOptions, + Logger, RawFlags, ReflagClient, ReflagContext, @@ -70,7 +71,7 @@ export type RequestFlagFeedbackOptions = Omit< */ export type ReflagInitOptionsBase = Omit< InitOptions, - "user" | "company" | "other" | "otherContext" | "bootstrappedFlags" + "user" | "company" | "other" | "otherContext" | "bootstrappedFlags" | "logger" >; /** @@ -83,6 +84,13 @@ export type ReflagBaseProps = { */ initialLoading?: boolean; + /** + * A custom logger to use for SDK logs. + * Use this for advanced control or filtering of SDK logs. + * If both `logger` and `debug` are provided, `logger` takes precedence. + */ + logger?: Logger; + /** * Set to `true` to enable debug logging to the console. */ @@ -92,7 +100,10 @@ export type ReflagBaseProps = { /** * Props for the ReflagClientProvider. */ -export type ReflagClientProviderProps = Omit & { +export type ReflagClientProviderProps = Omit< + ReflagBaseProps, + "debug" | "logger" +> & { /** * A pre-initialized ReflagClient to use. */ diff --git a/packages/vue-sdk/test/usage.test.ts b/packages/vue-sdk/test/usage.test.ts index 1c8277da..ec6aac44 100644 --- a/packages/vue-sdk/test/usage.test.ts +++ b/packages/vue-sdk/test/usage.test.ts @@ -63,6 +63,71 @@ describe("ReflagProvider", () => { expect(wrapper.findComponent(Child).vm.client).toBeDefined(); }); + test("uses provided logger", async () => { + const logger = { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; + + const Child = defineComponent({ + setup() { + const client = useClient(); + return { client }; + }, + template: "
", + }); + + const wrapper = mount(ReflagProvider, { + props: { + publishableKey: "key-with-logger", + logger, + }, + slots: { default: () => h(Child) }, + }); + + await nextTick(); + const clientLogger = wrapper.findComponent(Child).vm.client.logger; + expect(clientLogger.debug).toBe(logger.debug); + expect(clientLogger.info).toBe(logger.info); + expect(clientLogger.warn).toBe(logger.warn); + expect(clientLogger.error).toBe(logger.error); + }); + + test("prefers logger over debug mode", async () => { + const logger = { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; + + const Child = defineComponent({ + setup() { + const client = useClient(); + return { client }; + }, + template: "
", + }); + + const wrapper = mount(ReflagProvider, { + props: { + publishableKey: "key-with-logger-and-debug", + logger, + debug: true, + }, + slots: { default: () => h(Child) }, + }); + + await nextTick(); + const clientLogger = wrapper.findComponent(Child).vm.client.logger; + expect(clientLogger.debug).toBe(logger.debug); + expect(clientLogger.info).toBe(logger.info); + expect(clientLogger.warn).toBe(logger.warn); + expect(clientLogger.error).toBe(logger.error); + }); + test("throws without provider", () => { const Comp = defineComponent({ setup() { diff --git a/yarn.lock b/yarn.lock index d52de854..b095ef2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5875,7 +5875,7 @@ __metadata: dependencies: "@react-native-async-storage/async-storage": "npm:^2.2.0" "@reflag/eslint-config": "npm:^0.0.2" - "@reflag/react-sdk": "npm:1.4.3" + "@reflag/react-sdk": "npm:1.4.4" "@reflag/tsconfig": "npm:^0.0.2" "@types/react": "npm:^19.0.12" eslint: "npm:^9.21.0" @@ -5887,7 +5887,7 @@ __metadata: languageName: unknown linkType: soft -"@reflag/react-sdk@npm:1.4.3, @reflag/react-sdk@workspace:^, @reflag/react-sdk@workspace:packages/react-sdk": +"@reflag/react-sdk@npm:1.4.4, @reflag/react-sdk@workspace:^, @reflag/react-sdk@workspace:packages/react-sdk": version: 0.0.0-use.local resolution: "@reflag/react-sdk@workspace:packages/react-sdk" dependencies: