Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
78ba573
feat(browser-sdk,react-sdk): extend the web SDKs to support the new `…
pavkam Jan 29, 2025
ecb8a4c
Forward `toolbar` config in react-sdk (#299)
laander Jan 29, 2025
f384857
Bump react + browser SDK to v3.0.0-alpha.2 (#300)
laander Jan 29, 2025
81f5ddc
feat(node-sdk): support for remote configuration (#295)
pavkam Jan 29, 2025
ee38109
chore(node-sdk): bump version (#302)
pavkam Feb 3, 2025
eca331d
chore: allow publishing of non `main` branches` (#304)
roncohen Feb 4, 2025
ca22529
fix: docs ci script (#298)
matus-vacula Jan 28, 2025
2bb5088
chore(deps): bump next from 14.2.15 to 14.2.21 in /packages/react-sdk…
dependabot[bot] Jan 28, 2025
e81ec95
Bump year (#281)
makwarth Jan 28, 2025
f13880a
feat(browser-sdk,node-sdk): add avatar support for user and company c…
pavkam Jan 28, 2025
15119f8
chore(deps): bump nanoid from 3.3.7 to 3.3.8 in /packages/node-sdk/ex…
dependabot[bot] Jan 20, 2025
579eb1c
fix(node-sdk): ensure timers don't keep process alive (#303)
roncohen Feb 3, 2025
d9381e4
chore(browser-sdk,node-sdk): version 2.5.2 (#306)
roncohen Feb 4, 2025
6b1800a
Remove deprecated and flatten options (#308)
roncohen Feb 6, 2025
de93d76
feat(node-sdk): allow passing `meta` to `getFeature(s)` (#312)
pavkam Feb 9, 2025
bae0be7
chore(deps-dev): bump vitest from 2.0.4 to 2.1.9 (#314)
dependabot[bot] Feb 10, 2025
2fcef62
chore(deps-dev): bump vitest from 2.1.4 to 2.1.9 in /packages/node-sd…
dependabot[bot] Feb 10, 2025
f366edb
feat: use remote feature list instead of locally defined list (#315)
roncohen Feb 12, 2025
87e78a5
Merge branch 'main' into browser-react-3.0.alpha
pavkam Feb 12, 2025
28ffa72
chore(deps): upgrade @bucketco/browser-sdk to 3.0.0-alpha.2
pavkam Feb 12, 2025
c463ebf
feat(browser-sdk,react-sdk): check events (#316)
pavkam Feb 12, 2025
f1ef0f7
feat(openfeature-web-provider): improve flag resolution and context h…
pavkam Feb 12, 2025
f4e579c
chore(openfeature-browser-provider): bump version to 0.4.0-alpha.1
pavkam Feb 12, 2025
ab5ba42
refactor(react-sdk): simplify BucketProvider props and improve type d…
pavkam Feb 13, 2025
a92f028
chore(browser-sdk,react-sdk): bump SDK versions to 3.0.0-alpha.4 and …
pavkam Feb 13, 2025
adf6fe8
fix(browser-sdk,react-sdk): remove featureId option for requestFeedba…
roncohen Feb 13, 2025
bff834c
fix(react-sdk): throw error on missing provider (#322)
roncohen Feb 13, 2025
bc0dbde
fix(browser-sdk): remove featureDefinition list (#323)
roncohen Feb 13, 2025
c6fa42a
fix(react-sdk): improve type definitions for useFeature hook (#324)
pavkam Feb 17, 2025
1e21da8
feat(browser-sdk): Event listeners (#325)
roncohen Feb 17, 2025
7340206
feat: hooks
roncohen Feb 15, 2025
672b6c0
more hooks
roncohen Feb 15, 2025
568c5f9
missing files
roncohen Feb 15, 2025
c769a8b
docs
roncohen Feb 15, 2025
f19dd98
update tests
roncohen Feb 15, 2025
b622416
export hook types, better examples etc.
roncohen Feb 16, 2025
f7e2899
refactored hooks system
roncohen Feb 16, 2025
1e3f599
updated hooks
roncohen Feb 16, 2025
b2fb888
missed a type import
roncohen Feb 16, 2025
869c1a1
missed the example index.html
roncohen Feb 16, 2025
fad9666
one more
roncohen Feb 16, 2025
7a84fe4
update react sdk too
roncohen Feb 16, 2025
f6e592c
fix test
roncohen Feb 17, 2025
121a36e
feat: in use
roncohen Feb 15, 2025
9d09ff4
working on tests
roncohen Feb 15, 2025
a5949f2
wip
roncohen Feb 16, 2025
2de4ab2
introduce: in-use API for use with React
roncohen Feb 17, 2025
32cafbf
fix tests
roncohen Feb 17, 2025
e1079a3
Merge branch 'browser-react-3.0.alpha' into in-use
roncohen Feb 17, 2025
7e020bf
fix: docs
roncohen Feb 17, 2025
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
Prev Previous commit
Next Next commit
feat(browser-sdk): Event listeners (#325)
Event listeners allow capturing different events that happen in the Browser SDK.

They are useful for a number of things like building client side
integrations to analytics or error-logging systems.
  • Loading branch information
roncohen authored Feb 17, 2025
commit 1e21da8ca2fb45cea9d33a1f04cfddd1812cc7d2
29 changes: 28 additions & 1 deletion packages/browser-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ Bucket can assist you with collecting your user's feedback by offering a pre-bui

Feedback can be submitted to Bucket using the SDK:

```js
```ts
bucketClient.feedback({
featureId: "my_feature_id", // String (required), copy from Feature feedback tab
score: 5, // Number: 1-5 (optional)
Expand All @@ -308,6 +308,33 @@ If you are not using the Bucket Browser SDK, you can still submit feedback using

See details in [Feedback HTTP API](https://docs.bucket.co/reference/http-tracking-api#feedback)

### Event listeners

Event listeners allow for capturing various events occurring in the `BucketClient`. This is useful to build integrations with other system or for various debugging purposes. There are 5 kinds of events:

- FeaturesUpdated
- User
- Company
- Check
- Track

Use the `on()` method to add an event listener to respond to certain events. See the API reference for details on each hook.

```ts
import { BucketClient, CheckEvent, RawFeatures } from "@bucketco/browser-sdk";

const client = new BucketClient({
// options
});

// or add the hooks after construction:
const unsub = client.on("enabledCheck", (check: CheckEvent) =>
console.log(`Check event ${check}`),
);
// use the returned function to unsubscribe, or call `off()` with the same arguments again
unsub();
```

### Zero PII

The Bucket Browser SDK doesn't collect any metadata and HTTP IP addresses are _not_ being stored.
Expand Down
9 changes: 3 additions & 6 deletions packages/browser-sdk/example/typescript/app.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { BucketClient } from "../../src";
import { BucketClient, CheckEvent, RawFeatures } from "../../src";

const urlParams = new URLSearchParams(window.location.search);
const publishableKey = urlParams.get("publishableKey");
const featureKey = urlParams.get("featureKey") ?? "huddles";

const featureList = ["huddles"];

if (!publishableKey) {
throw Error("publishableKey is missing");
}
Expand All @@ -18,7 +16,6 @@ const bucket = new BucketClient({
show: true,
position: { placement: "bottom-right" },
},
featureList,
});

document
Expand All @@ -37,8 +34,8 @@ bucket.initialize().then(() => {
if (loadingElem) loadingElem.style.display = "none";
});

bucket.onFeaturesUpdated(() => {
const { isEnabled } = bucket.getFeature("huddles");
bucket.on("featuresUpdated", (features: RawFeatures) => {
const { isEnabled } = features[featureKey];

const startHuddleElem = document.getElementById("start-huddle");
if (isEnabled) {
Expand Down
19 changes: 12 additions & 7 deletions packages/browser-sdk/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@

<script type="module">
import { BucketClient } from "./src/index.ts";
const featureList = ["huddles"];

window.bucket = new BucketClient({
publishableKey,
Expand All @@ -48,22 +47,28 @@
placement: "bottom-right",
},
},
featureList,
});

bucket.initialize().then(() => {
console.log("Bucket initialized");
document.getElementById("loading").style.display = "none";
});

bucket.onFeaturesUpdated(() => {
const { isEnabled } = bucket.getFeature("huddles");
if (isEnabled) {
bucket.on("enabledCheck", (check) =>
console.log(`Check event for ${check.key}`),
);

bucket.on("featuresUpdated", (features) => {
console.log("Features updated");
const feature = bucket.getFeature(featureKey);

const startHuddleElem = document.getElementById("start-huddle");
if (feature.isEnabled) {
// show the start-huddle button
document.getElementById("start-huddle").style.display = "block";
if (startHuddleElem) startHuddleElem.style.display = "block";
} else {
// hide the start-huddle button
document.getElementById("start-huddle").style.display = "none";
if (startHuddleElem) startHuddleElem.style.display = "none";
}
});
</script>
Expand Down
72 changes: 55 additions & 17 deletions packages/browser-sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as feedbackLib from "./feedback/ui";
import { ToolbarPosition } from "./toolbar/Toolbar";
import { API_BASE_URL, APP_BASE_URL, SSE_REALTIME_BASE_URL } from "./config";
import { BucketContext, CompanyContext, UserContext } from "./context";
import { HookArgs, HooksManager } from "./hooksManager";
import { HttpClient } from "./httpClient";
import { Logger, loggerWithPrefix, quietConsoleLogger } from "./logger";
import { showToolbarToggle } from "./toolbar";
Expand Down Expand Up @@ -356,6 +357,8 @@ export class BucketClient {

public readonly logger: Logger;

private readonly hooks: HooksManager;

/**
* Create a new BucketClient instance.
*/
Expand Down Expand Up @@ -433,6 +436,12 @@ export class BucketClient {
typeof opts.toolbar === "object" ? opts.toolbar.position : undefined,
});
}

// Register hooks
this.hooks = new HooksManager();
this.featuresClient.onUpdated(() => {
this.hooks.trigger("featuresUpdated", this.featuresClient.getFeatures());
});
}

/**
Expand Down Expand Up @@ -463,6 +472,31 @@ export class BucketClient {
}
}

/**
* Add a hook to the client.
*
* @param hook Hook to add.
*/
on<THookType extends keyof HookArgs>(
type: THookType,
handler: (args0: HookArgs[THookType]) => void,
) {
this.hooks.addHook(type, handler);
}

/**
* Remove a hook from the client.
*
* @param hook Hook to add.
* @returns A function to remove the hook.
*/
off<THookType extends keyof HookArgs>(
type: THookType,
handler: (args0: HookArgs[THookType]) => void,
) {
return this.hooks.removeHook(type, handler);
}

/**
* Get the current configuration.
*/
Expand Down Expand Up @@ -534,18 +568,6 @@ export class BucketClient {
await this.featuresClient.setContext(this.context);
}

/**
* Register a callback to be called when the features are updated.
* Features are not guaranteed to have actually changed when the callback is called.
*
* Calling `client.stop()` will remove all listeners added here.
*
* @param cb The callback to call when the update completes.
*/
onFeaturesUpdated(cb: () => void) {
return this.featuresClient.onUpdated(cb);
}

/**
* Track an event in Bucket.
*
Expand All @@ -572,6 +594,13 @@ export class BucketClient {

const res = await this.httpClient.post({ path: `/event`, body: payload });
this.logger.debug(`sent event`, res);

this.hooks.trigger("track", {
eventName,
attributes,
user: this.context.user,
company: this.context.company,
});
return res;
}

Expand Down Expand Up @@ -684,7 +713,8 @@ export class BucketClient {
getFeature(key: string): Feature {
const f = this.getFeatures()[key];

const fClient = this.featuresClient;
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
const value = f?.isEnabledOverride ?? f?.isEnabled ?? false;
const config = f?.config
? {
Expand All @@ -695,9 +725,9 @@ export class BucketClient {

return {
get isEnabled() {
fClient
self
.sendCheckEvent({
action: "check",
action: "check-is-enabled",
key,
version: f?.targetingVersion,
ruleEvaluationResults: f?.ruleEvaluationResults,
Expand All @@ -710,7 +740,7 @@ export class BucketClient {
return value;
},
get config() {
fClient
self
.sendCheckEvent({
action: "check-config",
key,
Expand Down Expand Up @@ -749,7 +779,12 @@ export class BucketClient {
}

sendCheckEvent(checkEvent: CheckEvent) {
return this.featuresClient.sendCheckEvent(checkEvent);
return this.featuresClient.sendCheckEvent(checkEvent, () => {
this.hooks.trigger(
checkEvent.action == "check-config" ? "configCheck" : "enabledCheck",
checkEvent,
);
});
}

/**
Expand Down Expand Up @@ -787,6 +822,8 @@ export class BucketClient {
};
const res = await this.httpClient.post({ path: `/user`, body: payload });
this.logger.debug(`sent user`, res);

this.hooks.trigger("user", this.context.user);
return res;
}

Expand Down Expand Up @@ -817,6 +854,7 @@ export class BucketClient {

const res = await this.httpClient.post({ path: `/company`, body: payload });
this.logger.debug(`sent company`, res);
this.hooks.trigger("company", this.context.company);
return res;
}
}
19 changes: 6 additions & 13 deletions packages/browser-sdk/src/feature/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export type FetchedFeature = {
};
};

const FEATURES_UPDATED_EVENT = "features-updated";
const FEATURES_UPDATED_EVENT = "featuresUpdated";

export type FetchedFeatures = Record<string, FetchedFeature | undefined>;

Expand Down Expand Up @@ -145,7 +145,7 @@ export interface CheckEvent {
/**
* Action to perform.
*/
action: "check" | "check-config";
action: "check-is-enabled" | "check-config";

/**
* Feature key.
Expand Down Expand Up @@ -299,20 +299,12 @@ export class FeaturesClient {
* Features are not guaranteed to have actually changed when the callback is called.
*
* @param callback this will be called when the features are updated.
* @param options passed as-is to addEventListener, except the abort signal is not supported.
* @returns a function that can be called to remove the listener
*/
onUpdated(callback: () => void, options?: AddEventListenerOptions | boolean) {
onUpdated(callback: () => void) {
this.eventTarget.addEventListener(FEATURES_UPDATED_EVENT, callback, {
signal: this.abortController.signal,
});
return () => {
this.eventTarget.removeEventListener(
FEATURES_UPDATED_EVENT,
callback,
options,
);
};
}

getFeatures(): RawFeatures {
Expand Down Expand Up @@ -365,10 +357,10 @@ export class FeaturesClient {
*
*
* @param checkEvent - The feature to send the event for.
* @param cb - Callback to call after the event is sent. Might be skipped if the event was rate limited.
*/
async sendCheckEvent(checkEvent: CheckEvent) {
async sendCheckEvent(checkEvent: CheckEvent, cb: () => void) {
const rateLimitKey = `check-event:${this.fetchParams().toString()}:${checkEvent.key}:${checkEvent.version}:${checkEvent.value}`;

await this.rateLimiter.rateLimited(rateLimitKey, async () => {
const payload = {
action: checkEvent.action,
Expand All @@ -390,6 +382,7 @@ export class FeaturesClient {
});

this.logger.debug(`sent feature event`, payload);
cb();
});

return checkEvent.value;
Expand Down
Loading
Loading