Skip to content

Commit 4ba0f89

Browse files
authored
Merge branch 'main' into node-24.13.0
2 parents a953f87 + cec4a80 commit 4ba0f89

52 files changed

Lines changed: 6006 additions & 999 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/publish.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ on:
55
branches:
66
- main
77

8+
permissions:
9+
id-token: write # Required publishing to npm using OIDC
10+
contents: read
11+
812
jobs:
913
release:
1014
runs-on: ubuntu-latest
@@ -20,14 +24,14 @@ jobs:
2024
cache-dependency-path: "**/yarn.lock"
2125
registry-url: "https://registry.npmjs.org"
2226
scope: "@reflag"
27+
- name: update npm - remove this once we upgrade Node.js
28+
run: npm install -g npm@11.8.0
29+
- name: npm version
30+
run: npm version
2331
- name: Install dependencies
2432
run: yarn install --immutable
2533
- name: Build packages
2634
run: yarn build
27-
- name: npm login
28-
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
29-
env:
30-
NPM_TOKEN: ${{ secrets.REFLAG_NPM_TOKEN }}
3135
- name: Publish
3236
run: yarn lerna publish from-package --no-private --yes
3337
- name: Build docs

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dist-ssr
2020
!.vscode/settings.json
2121
.idea
2222
.DS_Store
23+
.expo/
2324
*.suo
2425
*.ntvs*
2526
*.njsproj

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
**/gen
33
**/node_modules
44
**/dist
5+
**/.expo

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"license": "MIT",
66
"workspaces": [
77
"packages/*",
8-
"packages/react-sdk/dev/*",
8+
"packages/react-native-sdk/dev/*",
99
"packages/openfeature-browser-provider/example"
1010
],
1111
"scripts": {
@@ -25,10 +25,16 @@
2525
"devDependencies": {
2626
"lerna": "^8.1.3",
2727
"prettier": "^3.5.2",
28+
"react": "19.1.0",
29+
"react-dom": "19.1.0",
2830
"typedoc": "0.27.6",
2931
"typedoc-plugin-frontmatter": "^1.1.2",
3032
"typedoc-plugin-markdown": "^4.4.2",
3133
"typedoc-plugin-mdn-links": "^4.0.7",
3234
"typescript": "^5.7.3"
35+
},
36+
"resolutions": {
37+
"react": "19.1.0",
38+
"react-dom": "19.1.0"
3339
}
3440
}

packages/browser-sdk/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@reflag/browser-sdk",
3-
"version": "1.2.0",
3+
"version": "1.4.0",
44
"packageManager": "yarn@4.1.1",
55
"license": "MIT",
66
"repository": {
@@ -12,7 +12,7 @@
1212
},
1313
"scripts": {
1414
"dev": "vite",
15-
"build": "tsc --project tsconfig.build.json && vite build",
15+
"build": "tsc --project tsconfig.native.json && tsc --project tsconfig.build.json && vite build",
1616
"test": "vitest run",
1717
"test:watch": "vitest",
1818
"test:e2e": "yarn build && playwright test",
@@ -29,8 +29,10 @@
2929
],
3030
"main": "./dist/reflag-browser-sdk.umd.js",
3131
"types": "./dist/types/src/index.d.ts",
32+
"react-native": "./dist/index.native.js",
3233
"exports": {
3334
".": {
35+
"react-native": "./dist/index.native.js",
3436
"import": "./dist/reflag-browser-sdk.mjs",
3537
"require": "./dist/reflag-browser-sdk.umd.js",
3638
"types": "./dist/types/src/index.d.ts"
@@ -63,6 +65,7 @@
6365
"typescript": "^5.7.3",
6466
"vite": "^5.3.5",
6567
"vite-plugin-dts": "^4.0.0-beta.1",
68+
"vite-plugin-static-copy": "^1.0.6",
6669
"vitest": "^2.0.4"
6770
}
6871
}

packages/browser-sdk/src/client.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { ReflagContext, ReflagDeprecatedContext } from "./context";
2626
import { HookArgs, HooksManager, State } from "./hooksManager";
2727
import { HttpClient } from "./httpClient";
2828
import { Logger, loggerWithPrefix, quietConsoleLogger } from "./logger";
29+
import { StorageAdapter } from "./storage";
2930
import { showToolbarToggle } from "./toolbar";
3031

3132
const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
@@ -297,6 +298,12 @@ export type InitOptions = ReflagDeprecatedContext & {
297298
* Pre-fetched flags to be used instead of fetching them from the server.
298299
*/
299300
bootstrappedFlags?: RawFlags;
301+
302+
/**
303+
* Optional storage adapter used for caching flags and overrides.
304+
* Useful for React Native (AsyncStorage).
305+
*/
306+
storage?: StorageAdapter;
300307
};
301308

302309
const defaultConfig: Config = {
@@ -366,7 +373,9 @@ export interface Flag {
366373

367374
function shouldShowToolbar(opts: InitOptions) {
368375
const toolbarOpts = opts.toolbar;
369-
if (typeof window === "undefined") return false;
376+
if (typeof window === "undefined" || typeof window.location === "undefined") {
377+
return false;
378+
}
370379
if (typeof toolbarOpts === "boolean") return toolbarOpts;
371380
if (typeof toolbarOpts?.show === "boolean") return toolbarOpts.show;
372381
return window.location.hostname === "localhost";
@@ -391,6 +400,8 @@ export class ReflagClient {
391400

392401
private readonly hooks: HooksManager;
393402

403+
private toolbarToggleShown = false;
404+
394405
/**
395406
* Create a new ReflagClient instance.
396407
*/
@@ -439,6 +450,7 @@ export class ReflagClient {
439450
timeoutMs: opts.timeoutMs,
440451
fallbackFlags: opts.fallbackFlags,
441452
offline: this.config.offline,
453+
storage: opts.storage,
442454
},
443455
);
444456

@@ -466,12 +478,9 @@ export class ReflagClient {
466478
}
467479

468480
if (shouldShowToolbar(opts)) {
469-
this.logger.info("opening toolbar toggler");
470-
showToolbarToggle({
471-
reflagClient: this,
472-
position:
473-
typeof opts.toolbar === "object" ? opts.toolbar.position : undefined,
474-
});
481+
const position =
482+
typeof opts.toolbar === "object" ? opts.toolbar.position : undefined;
483+
this.showToolbarToggle(position);
475484
}
476485

477486
// Register hooks
@@ -870,6 +879,13 @@ export class ReflagClient {
870879
return this.flagsClient.getFlags();
871880
}
872881

882+
/**
883+
* Force refresh flags from the API, bypassing cache.
884+
*/
885+
refresh() {
886+
return this.flagsClient.refreshFlags();
887+
}
888+
873889
/**
874890
* @deprecated Use `getFlag` instead.
875891
*/
@@ -947,6 +963,19 @@ export class ReflagClient {
947963
};
948964
}
949965

966+
showToolbarToggle(position?: ToolbarPosition) {
967+
if (this.toolbarToggleShown) {
968+
return;
969+
}
970+
this.toolbarToggleShown = true;
971+
this.logger.info("opening toolbar toggler");
972+
973+
showToolbarToggle({
974+
reflagClient: this,
975+
position,
976+
});
977+
}
978+
950979
private setState(state: State) {
951980
this.state = state;
952981
this.hooks.trigger("stateUpdated", state);

packages/browser-sdk/src/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export const SDK_VERSION = `browser-sdk/${version}`;
1010
export const FLAG_EVENTS_PER_MIN = 1;
1111
export const FLAGS_EXPIRE_MS = 30 * 24 * 60 * 60 * 1000; // expire entirely after 30 days
1212

13-
export const IS_SERVER = typeof window === "undefined";
13+
export const IS_SERVER =
14+
typeof window === "undefined" || typeof document === "undefined";
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { OpenFeedbackFormOptions } from "./types";
2+
3+
export function openFeedbackForm(_options: OpenFeedbackFormOptions): void {
4+
// React Native doesn't support the web feedback UI.
5+
// Users should implement their own UI and use `feedback` or `useSendFeedback`.
6+
console.warn(
7+
"[Reflag] Feedback UI is not supported in React Native. " +
8+
"Use `feedback` or `useSendFeedback` with a custom UI instead.",
9+
);
10+
}

packages/browser-sdk/src/flag/flagCache.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
import { StorageAdapter } from "../storage";
2+
13
import { RawFlags } from "./flags";
24

3-
interface StorageItem {
4-
get(): string | null;
5-
set(value: string): void;
6-
}
5+
const DEFAULT_STORAGE_KEY = "__reflag_fetched_flags";
76

87
interface cacheEntry {
98
expireAt: number;
@@ -52,7 +51,8 @@ export interface CacheResult {
5251
}
5352

5453
export class FlagCache {
55-
private storage: StorageItem;
54+
private storage: StorageAdapter;
55+
private readonly storageKey: string;
5656
private readonly staleTimeMs: number;
5757
private readonly expireTimeMs: number;
5858

@@ -61,16 +61,17 @@ export class FlagCache {
6161
staleTimeMs,
6262
expireTimeMs,
6363
}: {
64-
storage: StorageItem;
64+
storage: StorageAdapter;
6565
staleTimeMs: number;
6666
expireTimeMs: number;
6767
}) {
6868
this.storage = storage;
69+
this.storageKey = DEFAULT_STORAGE_KEY;
6970
this.staleTimeMs = staleTimeMs;
7071
this.expireTimeMs = expireTimeMs;
7172
}
7273

73-
set(
74+
async set(
7475
key: string,
7576
{
7677
flags,
@@ -81,7 +82,7 @@ export class FlagCache {
8182
let cacheData: CacheData = {};
8283

8384
try {
84-
const cachedResponseRaw = this.storage.get();
85+
const cachedResponseRaw = await this.storage.getItem(this.storageKey);
8586
if (cachedResponseRaw) {
8687
cacheData = validateCacheData(JSON.parse(cachedResponseRaw)) ?? {};
8788
}
@@ -99,14 +100,14 @@ export class FlagCache {
99100
Object.entries(cacheData).filter(([_k, v]) => v.expireAt > Date.now()),
100101
);
101102

102-
this.storage.set(JSON.stringify(cacheData));
103+
await this.storage.setItem(this.storageKey, JSON.stringify(cacheData));
103104

104105
return cacheData;
105106
}
106107

107-
get(key: string): CacheResult | undefined {
108+
async get(key: string): Promise<CacheResult | undefined> {
108109
try {
109-
const cachedResponseRaw = this.storage.get();
110+
const cachedResponseRaw = await this.storage.getItem(this.storageKey);
110111
if (cachedResponseRaw) {
111112
const cachedResponse = validateCacheData(JSON.parse(cachedResponseRaw));
112113
if (

0 commit comments

Comments
 (0)