Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6d39abf
Add refresh control and RN fixes
roncohen Feb 4, 2026
b8865d5
Add native typecheck and storage fallback
roncohen Feb 4, 2026
0364046
Use memory storage adapter fallback
roncohen Feb 4, 2026
8b7bc0e
Fix flags tests and tweak RN docs
roncohen Feb 4, 2026
985e73f
Update Expo demo safe area and add refresh
roncohen Feb 4, 2026
09b0681
Add react native SDK package
roncohen Feb 4, 2026
0bf918d
Move Expo demo to react-native SDK
roncohen Feb 4, 2026
da76551
Require localStorage or custom storage
roncohen Feb 4, 2026
e1bc304
simplify browser sdk native entry
roncohen Feb 4, 2026
057fb9a
disable auto feedback in react native wrapper
roncohen Feb 4, 2026
58da320
add todo for async override persistence
roncohen Feb 4, 2026
e8c77a3
clean up browser native typecheck
roncohen Feb 4, 2026
e339fc1
remove react sdk native entry script
roncohen Feb 4, 2026
789ed04
fix yarn lockfile after rebase
roncohen Feb 4, 2026
e26cbf2
replace react sdk native entry script
roncohen Feb 4, 2026
9999f76
add app state listener to expo demo
roncohen Feb 5, 2026
e653063
refresh flags on app foreground
roncohen Feb 5, 2026
eb12e6e
add app state refresh cookbook
roncohen Feb 5, 2026
14e9bf3
clear dist before expo dep builds
roncohen Feb 5, 2026
ee4dd24
shim abort controller for react native
roncohen Feb 5, 2026
e8b6d44
update react native sdk cookbook
roncohen Feb 5, 2026
7a1bbb5
add more context
roncohen Feb 5, 2026
a49942e
remove redundant native index ts
roncohen Feb 5, 2026
fb8a44a
bump browser sdk version
roncohen Feb 5, 2026
cda9923
bump browser sdk to 1.4.0
roncohen Feb 5, 2026
52a68c4
bump react sdk to 1.4.0
roncohen Feb 5, 2026
3a66f9a
ignore dist in react native sdk prettier
roncohen Feb 5, 2026
54d162c
fix: lint
roncohen Feb 5, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dist-ssr
!.vscode/settings.json
.idea
.DS_Store
.expo/
*.suo
*.ntvs*
*.njsproj
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
**/gen
**/node_modules
**/dist
**/.expo
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"license": "MIT",
"workspaces": [
"packages/*",
"packages/react-sdk/dev/*",
"packages/react-native-sdk/dev/*",
"packages/openfeature-browser-provider/example"
],
"scripts": {
Expand All @@ -25,10 +25,16 @@
"devDependencies": {
"lerna": "^8.1.3",
"prettier": "^3.5.2",
"react": "19.1.0",
"react-dom": "19.1.0",
"typedoc": "0.27.6",
"typedoc-plugin-frontmatter": "^1.1.2",
"typedoc-plugin-markdown": "^4.4.2",
"typedoc-plugin-mdn-links": "^4.0.7",
"typescript": "^5.7.3"
},
"resolutions": {
"react": "19.1.0",
"react-dom": "19.1.0"
}
}
7 changes: 5 additions & 2 deletions 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.3.0",
"version": "1.4.0",
"packageManager": "yarn@4.1.1",
"license": "MIT",
"repository": {
Expand All @@ -12,7 +12,7 @@
},
"scripts": {
"dev": "vite",
"build": "tsc --project tsconfig.build.json && vite build",
"build": "tsc --project tsconfig.native.json && tsc --project tsconfig.build.json && vite build",
"test": "vitest run",
"test:watch": "vitest",
"test:e2e": "yarn build && playwright test",
Expand All @@ -29,8 +29,10 @@
],
"main": "./dist/reflag-browser-sdk.umd.js",
"types": "./dist/types/src/index.d.ts",
"react-native": "./dist/index.native.js",
"exports": {
".": {
"react-native": "./dist/index.native.js",
"import": "./dist/reflag-browser-sdk.mjs",
"require": "./dist/reflag-browser-sdk.umd.js",
"types": "./dist/types/src/index.d.ts"
Expand Down Expand Up @@ -63,6 +65,7 @@
"typescript": "^5.7.3",
"vite": "^5.3.5",
"vite-plugin-dts": "^4.0.0-beta.1",
"vite-plugin-static-copy": "^1.0.6",
"vitest": "^2.0.4"
}
}
19 changes: 18 additions & 1 deletion packages/browser-sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ReflagContext, ReflagDeprecatedContext } from "./context";
import { HookArgs, HooksManager, State } from "./hooksManager";
import { HttpClient } from "./httpClient";
import { Logger, loggerWithPrefix, quietConsoleLogger } from "./logger";
import { StorageAdapter } from "./storage";
import { showToolbarToggle } from "./toolbar";

const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
Expand Down Expand Up @@ -297,6 +298,12 @@ export type InitOptions = ReflagDeprecatedContext & {
* Pre-fetched flags to be used instead of fetching them from the server.
*/
bootstrappedFlags?: RawFlags;

/**
* Optional storage adapter used for caching flags and overrides.
* Useful for React Native (AsyncStorage).
*/
storage?: StorageAdapter;
};

const defaultConfig: Config = {
Expand Down Expand Up @@ -366,7 +373,9 @@ export interface Flag {

function shouldShowToolbar(opts: InitOptions) {
const toolbarOpts = opts.toolbar;
if (typeof window === "undefined") return false;
if (typeof window === "undefined" || typeof window.location === "undefined") {
return false;
}
if (typeof toolbarOpts === "boolean") return toolbarOpts;
if (typeof toolbarOpts?.show === "boolean") return toolbarOpts.show;
return window.location.hostname === "localhost";
Expand Down Expand Up @@ -441,6 +450,7 @@ export class ReflagClient {
timeoutMs: opts.timeoutMs,
fallbackFlags: opts.fallbackFlags,
offline: this.config.offline,
storage: opts.storage,
},
);

Expand Down Expand Up @@ -869,6 +879,13 @@ export class ReflagClient {
return this.flagsClient.getFlags();
}

/**
* Force refresh flags from the API, bypassing cache.
*/
refresh() {
return this.flagsClient.refreshFlags();
}

/**
* @deprecated Use `getFlag` instead.
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/browser-sdk/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export const SDK_VERSION = `browser-sdk/${version}`;
export const FLAG_EVENTS_PER_MIN = 1;
export const FLAGS_EXPIRE_MS = 30 * 24 * 60 * 60 * 1000; // expire entirely after 30 days

export const IS_SERVER = typeof window === "undefined";
export const IS_SERVER =
typeof window === "undefined" || typeof document === "undefined";
10 changes: 10 additions & 0 deletions packages/browser-sdk/src/feedback/ui/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { OpenFeedbackFormOptions } from "./types";

export function openFeedbackForm(_options: OpenFeedbackFormOptions): void {
// React Native doesn't support the web feedback UI.
// Users should implement their own UI and use `feedback` or `useSendFeedback`.
console.warn(
"[Reflag] Feedback UI is not supported in React Native. " +
"Use `feedback` or `useSendFeedback` with a custom UI instead.",
);
}
23 changes: 12 additions & 11 deletions packages/browser-sdk/src/flag/flagCache.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { StorageAdapter } from "../storage";

import { RawFlags } from "./flags";

interface StorageItem {
get(): string | null;
set(value: string): void;
}
const DEFAULT_STORAGE_KEY = "__reflag_fetched_flags";

interface cacheEntry {
expireAt: number;
Expand Down Expand Up @@ -52,7 +51,8 @@ export interface CacheResult {
}

export class FlagCache {
private storage: StorageItem;
private storage: StorageAdapter;
private readonly storageKey: string;
private readonly staleTimeMs: number;
private readonly expireTimeMs: number;

Expand All @@ -61,16 +61,17 @@ export class FlagCache {
staleTimeMs,
expireTimeMs,
}: {
storage: StorageItem;
storage: StorageAdapter;
staleTimeMs: number;
expireTimeMs: number;
}) {
this.storage = storage;
this.storageKey = DEFAULT_STORAGE_KEY;
this.staleTimeMs = staleTimeMs;
this.expireTimeMs = expireTimeMs;
}

set(
async set(
key: string,
{
flags,
Expand All @@ -81,7 +82,7 @@ export class FlagCache {
let cacheData: CacheData = {};

try {
const cachedResponseRaw = this.storage.get();
const cachedResponseRaw = await this.storage.getItem(this.storageKey);
if (cachedResponseRaw) {
cacheData = validateCacheData(JSON.parse(cachedResponseRaw)) ?? {};
}
Expand All @@ -99,14 +100,14 @@ export class FlagCache {
Object.entries(cacheData).filter(([_k, v]) => v.expireAt > Date.now()),
);

this.storage.set(JSON.stringify(cacheData));
await this.storage.setItem(this.storageKey, JSON.stringify(cacheData));

return cacheData;
}

get(key: string): CacheResult | undefined {
async get(key: string): Promise<CacheResult | undefined> {
try {
const cachedResponseRaw = this.storage.get();
const cachedResponseRaw = await this.storage.getItem(this.storageKey);
if (cachedResponseRaw) {
const cachedResponse = validateCacheData(JSON.parse(cachedResponseRaw));
if (
Expand Down
Loading