Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/browser-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reflag/browser-sdk",
"version": "1.4.2",
"version": "1.4.3",
"packageManager": "yarn@4.1.1",
"license": "MIT",
"repository": {
Expand Down
4 changes: 2 additions & 2 deletions packages/browser-sdk/src/flag/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ReflagContext } from "../context";
import { HttpClient } from "../httpClient";
import { Logger, loggerWithPrefix } from "../logger";
import RateLimiter from "../rateLimiter";
import { getLocalStorageAdapter, StorageAdapter } from "../storage";
import { getDefaultStorageAdapter, StorageAdapter } from "../storage";
import { createAbortController } from "../utils/abortController";
import { createEventTarget } from "../utils/eventTarget";

Expand Down Expand Up @@ -235,7 +235,7 @@ export class FlagsClient {
this.logger = loggerWithPrefix(logger, "[Flags]");
this.rateLimiter =
rateLimiter ?? new RateLimiter(FLAG_EVENTS_PER_MIN, this.logger);
this.storage = (cache ? undefined : storage) ?? getLocalStorageAdapter();
this.storage = (cache ? undefined : storage) ?? getDefaultStorageAdapter();
this.cache =
cache ??
this.setupCache(this.config.staleTimeMs, this.config.expireTimeMs);
Expand Down
15 changes: 15 additions & 0 deletions packages/browser-sdk/src/storage.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { IS_SERVER } from "./config";

export type StorageAdapter = {
getItem(key: string): Promise<string | null>;
setItem(key: string, value: string): Promise<void>;
removeItem?(key: string): Promise<void>;
};

export function createNoopStorageAdapter(): StorageAdapter {
return {
getItem: async () => null,
setItem: async () => undefined,
removeItem: async () => undefined,
};
}

export function getLocalStorageAdapter(): StorageAdapter {
if (
typeof localStorage === "undefined" ||
Expand All @@ -24,3 +34,8 @@ export function getLocalStorageAdapter(): StorageAdapter {
},
};
}

export function getDefaultStorageAdapter(): StorageAdapter {
if (IS_SERVER) return createNoopStorageAdapter();
return getLocalStorageAdapter();
}
6 changes: 5 additions & 1 deletion packages/browser-sdk/test/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { ReflagClient } from "../src/client";
import { FlagsClient } from "../src/flag/flags";
Expand All @@ -23,6 +23,10 @@ describe("ReflagClient", () => {
vi.clearAllMocks();
});

afterEach(() => {
vi.unstubAllGlobals();
});

describe("updateUser", () => {
it("should update the user context", async () => {
// and send new user data and trigger flag update
Expand Down
45 changes: 45 additions & 0 deletions packages/browser-sdk/test/storage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { afterEach, describe, expect, it, vi } from "vitest";

async function loadStorageModule() {
vi.resetModules();
return import("../src/storage");
}

describe("storage adapters", () => {
afterEach(() => {
vi.unstubAllGlobals();
});

it("noop adapter ignores writes", async () => {
const { createNoopStorageAdapter } = await loadStorageModule();
const adapter = createNoopStorageAdapter();

expect(await adapter.getItem("key")).toBeNull();
await adapter.setItem("key", "value");
expect(await adapter.getItem("key")).toBeNull();

await adapter.removeItem?.("key");
expect(await adapter.getItem("key")).toBeNull();
});

it("localStorage adapter throws when localStorage is unavailable", async () => {
const { getLocalStorageAdapter } = await loadStorageModule();
vi.stubGlobal("localStorage", undefined);
expect(() => getLocalStorageAdapter()).toThrowError(
"localStorage is not available. Provide a custom storage adapter.",
);
});

it("default adapter falls back to noop on server runtimes", async () => {
vi.resetModules();
vi.stubGlobal("window", undefined);
vi.stubGlobal("document", undefined);
vi.stubGlobal("localStorage", undefined);

const { getDefaultStorageAdapter } = await import("../src/storage");
const adapter = getDefaultStorageAdapter();

await adapter.setItem("fallback", "ok");
expect(await adapter.getItem("fallback")).toBeNull();
});
});
4 changes: 2 additions & 2 deletions packages/react-native-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reflag/react-native-sdk",
"version": "0.1.2",
"version": "0.1.3",
"license": "MIT",
"repository": {
"type": "git",
Expand Down Expand Up @@ -32,7 +32,7 @@
},
"dependencies": {
"@react-native-async-storage/async-storage": "^2.2.0",
"@reflag/react-sdk": "1.4.2"
"@reflag/react-sdk": "1.4.3"
},
"peerDependencies": {
"react": "*",
Expand Down
4 changes: 2 additions & 2 deletions packages/react-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reflag/react-sdk",
"version": "1.4.2",
"version": "1.4.3",
"license": "MIT",
"repository": {
"type": "git",
Expand Down Expand Up @@ -37,7 +37,7 @@
}
},
"dependencies": {
"@reflag/browser-sdk": "1.4.2"
"@reflag/browser-sdk": "1.4.3"
},
"peerDependencies": {
"react": "*",
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5036,7 +5036,7 @@ __metadata:
languageName: node
linkType: hard

"@reflag/browser-sdk@npm:1.4.2, @reflag/browser-sdk@workspace:packages/browser-sdk":
"@reflag/browser-sdk@npm:1.4.3, @reflag/browser-sdk@workspace:packages/browser-sdk":
version: 0.0.0-use.local
resolution: "@reflag/browser-sdk@workspace:packages/browser-sdk"
dependencies:
Expand Down Expand Up @@ -5223,7 +5223,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.2"
"@reflag/react-sdk": "npm:1.4.3"
"@reflag/tsconfig": "npm:^0.0.2"
"@types/react": "npm:^19.0.12"
eslint: "npm:^9.21.0"
Expand All @@ -5235,11 +5235,11 @@ __metadata:
languageName: unknown
linkType: soft

"@reflag/react-sdk@npm:1.4.2, @reflag/react-sdk@workspace:^, @reflag/react-sdk@workspace:packages/react-sdk":
"@reflag/react-sdk@npm:1.4.3, @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:
"@reflag/browser-sdk": "npm:1.4.2"
"@reflag/browser-sdk": "npm:1.4.3"
"@reflag/eslint-config": "npm:^0.0.2"
"@reflag/tsconfig": "npm:^0.0.2"
"@testing-library/react": "npm:^15.0.7"
Expand Down