Skip to content

Commit 9f72e02

Browse files
authored
Merge branch 'browser-react-3.0.alpha' into extent-check-events-browser
2 parents 98e57e3 + 28ffa72 commit 9f72e02

14 files changed

Lines changed: 333 additions & 392 deletions

File tree

packages/browser-sdk/src/client.ts

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
CheckEvent,
3+
FallbackFeatureOverride,
34
FeaturesClient,
4-
FeaturesOptions,
55
RawFeatures,
66
} from "./feature/features";
77
import {
@@ -185,7 +185,7 @@ export type FeatureDefinitions = Readonly<Array<string>>;
185185
/**
186186
* BucketClient initialization options.
187187
*/
188-
export interface InitOptions {
188+
export type InitOptions = {
189189
/**
190190
* Publishable key for authentication
191191
*/
@@ -218,12 +218,6 @@ export interface InitOptions {
218218
*/
219219
logger?: Logger;
220220

221-
/**
222-
* @deprecated
223-
* Use `apiBaseUrl` instead.
224-
*/
225-
host?: string;
226-
227221
/**
228222
* Base URL of Bucket servers. You can override this to use your mocked server.
229223
*/
@@ -235,10 +229,32 @@ export interface InitOptions {
235229
appBaseUrl?: string;
236230

237231
/**
238-
* @deprecated
239-
* Use `sseBaseUrl` instead.
232+
* Feature keys for which `isEnabled` should fallback to true
233+
* if SDK fails to fetch features from Bucket servers. If a record
234+
* is supplied instead of array, the values of each key represent the
235+
* configuration values and `isEnabled` is assume `true`.
236+
*/
237+
fallbackFeatures?: string[] | Record<string, FallbackFeatureOverride>;
238+
239+
/**
240+
* Timeout in milliseconds when fetching features
241+
*/
242+
timeoutMs?: number;
243+
244+
/**
245+
* If set to true stale features will be returned while refetching features
246+
*/
247+
staleWhileRevalidate?: boolean;
248+
249+
/**
250+
* If set, features will be cached between page loads for this duration
251+
*/
252+
expireTimeMs?: number;
253+
254+
/**
255+
* Stale features will be returned if staleWhileRevalidate is true if no new features can be fetched
240256
*/
241-
sseHost?: string;
257+
staleTimeMs?: number;
242258

243259
/**
244260
* Base URL of Bucket servers for SSE connections used by AutoFeedback.
@@ -250,11 +266,6 @@ export interface InitOptions {
250266
*/
251267
feedback?: FeedbackOptions;
252268

253-
/**
254-
* Feature flag specific configuration
255-
*/
256-
features?: FeaturesOptions;
257-
258269
/**
259270
* Version of the SDK
260271
*/
@@ -266,17 +277,15 @@ export interface InitOptions {
266277
enableTracking?: boolean;
267278

268279
/**
269-
* Toolbar configuration (alpha)
270-
* @ignore
280+
* Toolbar configuration
271281
*/
272282
toolbar?: ToolbarOptions;
273283

274284
/**
275-
* Local-first definition of features (alpha)
276-
* @ignore
285+
* Local-first definition of features
277286
*/
278-
featureList?: FeatureDefinitions;
279-
}
287+
features?: FeatureDefinitions;
288+
};
280289

281290
const defaultConfig: Config = {
282291
apiBaseUrl: API_BASE_URL,
@@ -335,7 +344,7 @@ function shouldShowToolbar(opts: InitOptions) {
335344
if (typeof toolbarOpts?.show === "boolean") return toolbarOpts.show;
336345

337346
return (
338-
opts.featureList !== undefined && window?.location?.hostname === "localhost"
347+
opts.features !== undefined && window?.location?.hostname === "localhost"
339348
);
340349
}
341350

@@ -369,9 +378,9 @@ export class BucketClient {
369378
};
370379

371380
this.config = {
372-
apiBaseUrl: opts?.apiBaseUrl ?? opts?.host ?? defaultConfig.apiBaseUrl,
373-
appBaseUrl: opts?.appBaseUrl ?? opts?.host ?? defaultConfig.appBaseUrl,
374-
sseBaseUrl: opts?.sseBaseUrl ?? opts?.sseHost ?? defaultConfig.sseBaseUrl,
381+
apiBaseUrl: opts?.apiBaseUrl ?? defaultConfig.apiBaseUrl,
382+
appBaseUrl: opts?.appBaseUrl ?? defaultConfig.appBaseUrl,
383+
sseBaseUrl: opts?.sseBaseUrl ?? defaultConfig.sseBaseUrl,
375384
enableTracking: opts?.enableTracking ?? defaultConfig.enableTracking,
376385
};
377386

@@ -395,9 +404,13 @@ export class BucketClient {
395404
company: this.context.company,
396405
other: this.context.otherContext,
397406
},
398-
opts?.featureList || [],
399407
this.logger,
400-
opts?.features,
408+
{
409+
expireTimeMs: opts.expireTimeMs,
410+
staleTimeMs: opts.staleTimeMs,
411+
fallbackFeatures: opts.fallbackFeatures,
412+
timeoutMs: opts.timeoutMs,
413+
},
401414
);
402415

403416
if (

packages/browser-sdk/src/feature/features.ts

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -90,36 +90,6 @@ export type FallbackFeatureOverride =
9090
}
9191
| true;
9292

93-
export type FeaturesOptions = {
94-
/**
95-
* Feature keys for which `isEnabled` should fallback to true
96-
* if SDK fails to fetch features from Bucket servers. If a record
97-
* is supplied instead of array, the values of each key represent the
98-
* configuration values and `isEnabled` is assume `true`.
99-
*/
100-
fallbackFeatures?: string[] | Record<string, FallbackFeatureOverride>;
101-
102-
/**
103-
* Timeout in milliseconds when fetching features
104-
*/
105-
timeoutMs?: number;
106-
107-
/**
108-
* If set to true stale features will be returned while refetching features
109-
*/
110-
staleWhileRevalidate?: boolean;
111-
112-
/**
113-
* If set, features will be cached between page loads for this duration
114-
*/
115-
expireTimeMs?: number;
116-
117-
/**
118-
* Stale features will be returned if staleWhileRevalidate is true if no new features can be fetched
119-
*/
120-
staleTimeMs?: number;
121-
};
122-
12393
type Config = {
12494
fallbackFeatures: Record<string, FallbackFeatureOverride>;
12595
timeoutMs: number;
@@ -252,9 +222,12 @@ export class FeaturesClient {
252222
constructor(
253223
private httpClient: HttpClient,
254224
private context: context,
255-
private featureDefinitions: Readonly<string[]>,
256225
logger: Logger,
257-
options?: FeaturesOptions & {
226+
options?: {
227+
fallbackFeatures?: Record<string, FallbackFeatureOverride> | string[];
228+
timeoutMs?: number;
229+
staleTimeMs?: number;
230+
expireTimeMs?: number;
258231
cache?: FeatureCache;
259232
rateLimiter?: RateLimiter;
260233
},
@@ -296,9 +269,7 @@ export class FeaturesClient {
296269
try {
297270
const storedFeatureOverrides = getOverridesCache();
298271
for (const key in storedFeatureOverrides) {
299-
if (this.featureDefinitions.includes(key)) {
300-
this.featureOverrides[key] = storedFeatureOverrides[key];
301-
}
272+
this.featureOverrides[key] = storedFeatureOverrides[key];
302273
}
303274
} catch (e) {
304275
this.logger.warn("error getting feature overrides from cache", e);
@@ -356,7 +327,7 @@ export class FeaturesClient {
356327
const params = this.fetchParams();
357328
try {
358329
const res = await this.httpClient.get({
359-
path: "/features/enabled",
330+
path: "/features/evaluated",
360331
timeoutMs: this.config.timeoutMs,
361332
params,
362333
});
@@ -438,17 +409,6 @@ export class FeaturesClient {
438409
};
439410
}
440411

441-
// add any features that aren't in the fetched features
442-
for (const key of this.featureDefinitions) {
443-
if (!mergedFeatures[key]) {
444-
mergedFeatures[key] = {
445-
key,
446-
isEnabled: false,
447-
isEnabledOverride: this.featureOverrides[key] ?? null,
448-
};
449-
}
450-
}
451-
452412
this.features = mergedFeatures;
453413

454414
this.eventTarget.dispatchEvent(new Event(FEATURES_UPDATED_EVENT));

packages/browser-sdk/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export type { BucketContext, CompanyContext, UserContext } from "./context";
66
export type {
77
CheckEvent,
88
FallbackFeatureOverride,
9-
FeaturesOptions,
109
RawFeature,
1110
RawFeatures,
1211
} from "./feature/features";

packages/browser-sdk/test/e2e/acceptance.browser.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ test("Acceptance", async ({ page }) => {
1111
const successfulRequests: string[] = [];
1212

1313
// Mock API calls with assertions
14-
await page.route(`${API_BASE_URL}/features/enabled*`, async (route) => {
14+
await page.route(`${API_BASE_URL}/features/evaluated*`, async (route) => {
1515
successfulRequests.push("FEATURES");
1616
await route.fulfill({
1717
status: 200,

packages/browser-sdk/test/e2e/feedback-widget.browser.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async function getOpenedWidgetContainer(
3333
await route.fulfill({ status: 200 });
3434
});
3535

36-
await page.route(`${API_HOST}/features/enabled*`, async (route) => {
36+
await page.route(`${API_HOST}/features/evaluated*`, async (route) => {
3737
await route.fulfill({
3838
status: 200,
3939
body: JSON.stringify({
@@ -70,7 +70,7 @@ async function getGiveFeedbackPageContainer(
7070
await route.fulfill({ status: 200 });
7171
});
7272

73-
await page.route(`${API_HOST}/features/enabled*`, async (route) => {
73+
await page.route(`${API_HOST}/features/evaluated*`, async (route) => {
7474
await route.fulfill({
7575
status: 200,
7676
body: JSON.stringify({

packages/browser-sdk/test/features.test.ts

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { afterAll, beforeEach, describe, expect, test, vi } from "vitest";
22

33
import { version } from "../package.json";
4-
import { FeatureDefinitions } from "../src/client";
54
import {
65
FEATURES_EXPIRE_MS,
76
FeaturesClient,
8-
FeaturesOptions,
97
FetchedFeature,
108
RawFeature,
119
} from "../src/feature/features";
@@ -37,9 +35,8 @@ function featuresClientFactory() {
3735
cache,
3836
httpClient,
3937
newFeaturesClient: function newFeaturesClient(
40-
options?: FeaturesOptions,
41-
context?: any,
42-
featureList: FeatureDefinitions = [],
38+
context?: Record<string, any>,
39+
options?: { staleWhileRevalidate?: boolean; fallbackFeatures?: any },
4340
) {
4441
return new FeaturesClient(
4542
httpClient,
@@ -49,7 +46,6 @@ function featuresClientFactory() {
4946
other: { eventId: "big-conference1" },
5047
...context,
5148
},
52-
featureList,
5349
testLogger,
5450
{
5551
cache,
@@ -92,7 +88,7 @@ describe("FeaturesClient", () => {
9288
publishableKey: "pk",
9389
});
9490

95-
expect(path).toEqual("/features/enabled");
91+
expect(path).toEqual("/features/evaluated");
9692
expect(timeoutMs).toEqual(5000);
9793
});
9894

@@ -121,14 +117,11 @@ describe("FeaturesClient", () => {
121117

122118
test("ignores undefined context", async () => {
123119
const { newFeaturesClient, httpClient } = featuresClientFactory();
124-
const featuresClient = newFeaturesClient(
125-
{},
126-
{
127-
user: undefined,
128-
company: undefined,
129-
other: undefined,
130-
},
131-
);
120+
const featuresClient = newFeaturesClient({
121+
user: undefined,
122+
company: undefined,
123+
other: undefined,
124+
});
132125
await featuresClient.initialize();
133126
expect(featuresClient.getFeatures()).toEqual(featuresResult);
134127

@@ -142,7 +135,7 @@ describe("FeaturesClient", () => {
142135
publishableKey: "pk",
143136
});
144137

145-
expect(path).toEqual("/features/enabled");
138+
expect(path).toEqual("/features/evaluated");
146139
expect(timeoutMs).toEqual(5000);
147140
});
148141

@@ -153,7 +146,7 @@ describe("FeaturesClient", () => {
153146
new Error("Failed to fetch features"),
154147
);
155148

156-
const featuresClient = newFeaturesClient({
149+
const featuresClient = newFeaturesClient(undefined, {
157150
fallbackFeatures: ["huddle"],
158151
});
159152

@@ -174,7 +167,7 @@ describe("FeaturesClient", () => {
174167
vi.mocked(httpClient.get).mockRejectedValue(
175168
new Error("Failed to fetch features"),
176169
);
177-
const featuresClient = newFeaturesClient({
170+
const featuresClient = newFeaturesClient(undefined, {
178171
fallbackFeatures: {
179172
huddle: {
180173
key: "john",
@@ -373,7 +366,7 @@ describe("FeaturesClient", () => {
373366
const { newFeaturesClient } = featuresClientFactory();
374367

375368
// localStorage.clear();
376-
const client = newFeaturesClient(undefined, undefined, ["featureB"]);
369+
const client = newFeaturesClient(undefined);
377370
await client.initialize();
378371

379372
let updated = false;

packages/browser-sdk/test/init.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe("init", () => {
4040

4141
server.use(
4242
http.get(
43-
"https://example.com/features/enabled",
43+
"https://example.com/features/evaluated",
4444
({ request }: { request: StrictRequest<DefaultBodyType> }) => {
4545
usedSpecialHost = true;
4646
return getFeatures({ request });

packages/browser-sdk/test/mocks/handlers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export const handlers = [
155155
});
156156
}),
157157
http.get("https://front.bucket.co/features/enabled", getFeatures),
158+
http.get("https://front.bucket.co/features/evaluated", getFeatures),
158159
http.post(
159160
"https://front.bucket.co/feedback/prompting-init",
160161
({ request }) => {

packages/node-sdk/example/serve.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import app from "./app";
44
// Initialize Bucket SDK before starting the server,
55
// so that features are available when the server starts.
66
bucket.initialize().then(() => {
7-
console.log("Bucket initialized");
8-
97
// Start listening for requests only after Bucket is initialized,
108
// which guarantees that features are available.
119
app.listen(process.env.PORT ?? 3000, () => {

packages/react-sdk/dev/nextjs-flag-demo/components/Providers.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const Providers = ({ publishableKey, children }: Props) => {
1414
publishableKey={publishableKey}
1515
company={{ id: "acme_inc" }}
1616
user={{ id: "john doe" }}
17-
featureOptions={{ fallbackFeatures: ["fallback-feature"] }}
17+
fallbackFeatures={["fallback-feature"]}
1818
>
1919
{children}
2020
</BucketProvider>

0 commit comments

Comments
 (0)