From 7b666e655196adc239bbfc60c1ca317f0511e505 Mon Sep 17 00:00:00 2001 From: blueogin Date: Thu, 15 Oct 2026 09:49:26 -0400 Subject: [PATCH 1/4] feat: add streaming widget integration and update related configurations --- .env.example | 3 ++ README.md | 4 ++ next.config.mjs | 1 + package.json | 1 + src/analytics/types.ts | 4 ++ .../Home/components/WalletSection.tsx | 2 + src/ui/assets/index.ts | 3 +- .../fixtures/streamingWidgetRegisterMock.ts | 13 +++++ src/widgets/registry.test.ts | 53 +++++++++++++++++++ src/widgets/registry.ts | 35 +++++++++++- vitest.config.ts | 5 ++ 11 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/widgets/fixtures/streamingWidgetRegisterMock.ts diff --git a/.env.example b/.env.example index 64a1486..14826d9 100644 --- a/.env.example +++ b/.env.example @@ -120,3 +120,6 @@ NEXT_PUBLIC_AI_CREDITS_WIDGET_DASHBOARD_ENABLED=false # Hide the dashboard button while keeping /{locale}/superfluid-campaign accessible. NEXT_PUBLIC_SUPERFLUID_CAMPAIGN_WIDGET_DASHBOARD_ENABLED=false + +# Hide the dashboard button while keeping /{locale}/streaming accessible. +NEXT_PUBLIC_STREAMING_WIDGET_DASHBOARD_ENABLED=false diff --git a/README.md b/README.md index 24466c4..ee2d3fb 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,10 @@ The Superfluid button follows the same pattern with `NEXT_PUBLIC_SUPERFLUID_CAMPAIGN_WIDGET_DASHBOARD_ENABLED`; its authenticated `/{locale}/superfluid-campaign` path remains available when the button is hidden. +The Streaming button follows the same pattern with +`NEXT_PUBLIC_STREAMING_WIDGET_DASHBOARD_ENABLED`; its authenticated +`/{locale}/streaming` path remains available when the button is hidden. + ## GoodWidget local-tarball workflow Widget packages that are not yet published to npm (e.g. `@goodwidget/ai-credits-widget`) diff --git a/next.config.mjs b/next.config.mjs index 5c3976a..9797199 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -10,6 +10,7 @@ const nextConfig = { "@goodwidget/citizen-claim-widget", "@goodwidget/ai-credits-widget", "@goodwidget/goodreserve-widget", + "@goodwidget/streaming-widget", "@goodwidget/core", "@goodwidget/embed", "@goodwidget/ui", diff --git a/package.json b/package.json index 11858be..ac21563 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@goodwidget/core": "0.1.5", "@goodwidget/embed": "0.1.4", "@goodwidget/goodreserve-widget": "0.1.7", + "@goodwidget/streaming-widget": "0.1.0", "@goodwidget/superfluid-campaign-widget": "0.1.7", "@goodwidget/ui": "0.1.6", "@hcaptcha/react-hcaptcha": "^2.0.2", diff --git a/src/analytics/types.ts b/src/analytics/types.ts index d69c5a9..a1cca8b 100644 --- a/src/analytics/types.ts +++ b/src/analytics/types.ts @@ -51,6 +51,7 @@ export enum AnalyticsEventTypes { AICreditsTabSelected = "AICredits_Tab_Selected", GoodReserveTabSelected = "GoodReserve_Tab_Selected", SuperfluidCampaignTabSelected = "SuperfluidCampaign_Tab_Selected", + StreamingTabSelected = "Streaming_Tab_Selected", } export enum AvailableMethodsNames { @@ -273,3 +274,6 @@ export type AnalyticsEvent = | { type: AnalyticsEventTypes.SuperfluidCampaignTabSelected } + | { + type: AnalyticsEventTypes.StreamingTabSelected + } diff --git a/src/sections/Home/components/WalletSection.tsx b/src/sections/Home/components/WalletSection.tsx index a5834b9..2a00b59 100644 --- a/src/sections/Home/components/WalletSection.tsx +++ b/src/sections/Home/components/WalletSection.tsx @@ -45,12 +45,14 @@ type WidgetTabSelectedEventType = | AnalyticsEventTypes.AICreditsTabSelected | AnalyticsEventTypes.GoodReserveTabSelected | AnalyticsEventTypes.SuperfluidCampaignTabSelected + | AnalyticsEventTypes.StreamingTabSelected const widgetActionEventType: Record = { "goodwidget.ai-credits": AnalyticsEventTypes.AICreditsTabSelected, "goodwidget.goodreserve": AnalyticsEventTypes.GoodReserveTabSelected, "goodwidget.superfluid-campaign": AnalyticsEventTypes.SuperfluidCampaignTabSelected, + "goodwidget.streaming": AnalyticsEventTypes.StreamingTabSelected, } export default function WalletSection({ diff --git a/src/ui/assets/index.ts b/src/ui/assets/index.ts index 6a39cbe..4b18a2c 100644 --- a/src/ui/assets/index.ts +++ b/src/ui/assets/index.ts @@ -41,7 +41,7 @@ import { import { FaCaretDown, FaCaretUp } from "react-icons/fa" import { HiDotsHorizontal } from "react-icons/hi" import { IoDocumentOutline } from "react-icons/io5" -import { LuSparkles } from "react-icons/lu" +import { LuSparkles, LuWaves } from "react-icons/lu" import { MdOutlineContactSupport, MdOutlinePrivacyTip } from "react-icons/md" import { RiArrowDownLine, RiArrowUpLine } from "react-icons/ri" import { TbCube3dSphere, TbLogout } from "react-icons/tb" @@ -121,6 +121,7 @@ export const UIIcons = { Cash: BsCashStack, Legal: IoDocumentOutline, Sparkles: LuSparkles, + Waves: LuWaves, Logout: TbLogout, Privacy: MdOutlinePrivacyTip, Questionmark: BsFillQuestionCircleFill, diff --git a/src/widgets/fixtures/streamingWidgetRegisterMock.ts b/src/widgets/fixtures/streamingWidgetRegisterMock.ts new file mode 100644 index 0000000..0f1239b --- /dev/null +++ b/src/widgets/fixtures/streamingWidgetRegisterMock.ts @@ -0,0 +1,13 @@ +/** + * Vitest mock for `@goodwidget/streaming-widget/register`. + * + * The real module performs a Custom Element side-effect registration which + * requires a browser DOM. This no-op stub satisfies the import during unit + * tests without triggering DOM APIs. + */ +export const goodWidgetMetadata = { + packageName: "@goodwidget/streaming-widget", + packageVersion: "0.1.0", +} + +export const register = (tagName?: string): string => tagName ?? "gw-streaming" diff --git a/src/widgets/registry.test.ts b/src/widgets/registry.test.ts index bf16fb6..3297820 100644 --- a/src/widgets/registry.test.ts +++ b/src/widgets/registry.test.ts @@ -273,4 +273,57 @@ describe("widget registry", () => { ]) }) }) + + describe("Streaming widget", () => { + it("is present in the live WIDGETS array with the correct widgetId", () => { + const streaming = WIDGETS.find( + (w) => w.widgetId === "goodwidget.streaming", + ) + expect(streaming).toBeDefined() + expect(streaming).toMatchObject({ + widgetId: "goodwidget.streaming", + packageName: "@goodwidget/streaming-widget", + packageVersion: "0.1.0", + routeSlug: "streaming", + entry: { tagName: "gw-streaming" }, + providerPolicy: { + chainIds: [42220, 8453], + }, + }) + }) + + it("passes registry validation (createWidgetRegistry does not throw)", () => { + const streaming = WIDGETS.find( + (w) => w.widgetId === "goodwidget.streaming", + ) + expect(streaming).toBeDefined() + if (!streaming) return + expect(() => createWidgetRegistry([streaming])).not.toThrow() + }) + + it("appears in widgetDashboardActions only when dashboardVisible", () => { + const streaming = WIDGETS.find( + (w) => w.widgetId === "goodwidget.streaming", + ) + const action = widgetDashboardActions.find( + (a) => a.widgetId === "goodwidget.streaming", + ) + if (streaming?.dashboardVisible === false) { + expect(action).toBeUndefined() + } else { + expect(action).toBeDefined() + expect(action?.routeSlug).toBe("streaming") + expect(action?.label).toBe("Streaming") + } + }) + + it("uses the shared provider method allowlist", () => { + const streaming = WIDGETS.find( + (w) => w.widgetId === "goodwidget.streaming", + ) + expect(streaming?.providerPolicy.requiredMethods).toEqual([ + ...WIDGET_PROVIDER_METHOD_LIST, + ]) + }) + }) }) diff --git a/src/widgets/registry.ts b/src/widgets/registry.ts index 42e0d47..0ade103 100644 --- a/src/widgets/registry.ts +++ b/src/widgets/registry.ts @@ -267,10 +267,43 @@ const goodReserveWidget = defineWidget({ }, }) +/** + * Superfluid streaming widget: money streams, GDA pool memberships, and Super + * Token balances. The package registers its Custom Element as a side effect + * when its register entry is imported. + */ +const streamingWidget = defineWidget({ + widgetId: "goodwidget.streaming", + packageName: "@goodwidget/streaming-widget", + packageVersion: "0.1.0", + routeSlug: "streaming", + displayName: "Streaming", + description: "Manage money streams, pools, and Super Token balances", + dashboardVisible: readBooleanEnv( + process.env.NEXT_PUBLIC_STREAMING_WIDGET_DASHBOARD_ENABLED, + false, + ), + icon: { kind: "system", name: "Waves" }, + integrationMode: "web-component", + entry: { + tagName: "gw-streaming", + load: () => import("@goodwidget/streaming-widget/register"), + }, + providerPolicy: { + chainIds: [CELO_CHAIN_ID, BASE_CHAIN_ID], + requiredMethods: WIDGET_PROVIDER_METHOD_LIST, + }, +}) + export const WIDGETS: readonly RegisteredWidget[] = process.env.NEXT_PUBLIC_PLAYWRIGHT_TEST_MODE === "true" ? [testFixtureWidget] - : [superfluidCampaignWidget, aiCreditsWidget, goodReserveWidget] + : [ + superfluidCampaignWidget, + aiCreditsWidget, + goodReserveWidget, + streamingWidget, + ] export const widgetRegistry = createWidgetRegistry(WIDGETS) export const getWidgetByRoute = ( diff --git a/vitest.config.ts b/vitest.config.ts index c43d5fd..a0d498d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -29,6 +29,11 @@ export default defineConfig({ __dirname, "./src/widgets/fixtures/aiCreditsWidgetRegisterMock.ts", ), + // Same for the Streaming widget register module. + "@goodwidget/streaming-widget/register": path.resolve( + __dirname, + "./src/widgets/fixtures/streamingWidgetRegisterMock.ts", + ), }, }, }) From dfb9bb7931d54f650853b123fc292d5b09722186 Mon Sep 17 00:00:00 2001 From: blueogin Date: Thu, 15 Oct 2026 10:04:03 -0400 Subject: [PATCH 2/4] feat: add streaming widget and related dependencies to yarn.lock --- yarn.lock | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/yarn.lock b/yarn.lock index 397ee10..d23bdcd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -928,6 +928,19 @@ __metadata: languageName: node linkType: hard +"@goodsdks/streaming-sdk@npm:1.1.0": + version: 1.1.0 + resolution: "@goodsdks/streaming-sdk@npm:1.1.0" + dependencies: + "@sfpro/sdk": "npm:^0.1.0" + graphql: "npm:^16.9.0" + graphql-request: "npm:^7.1.2" + peerDependencies: + viem: "*" + checksum: 10c0/ed7236e787a47474308d0950342c67c9cfdc9d68bc7aa01464e7bd0bbc08ee97c6f880a2e7e9355dd09d2824885eef0a7e3868f5b7607c7aa6773b0fc8658538 + languageName: node + linkType: hard + "@goodwidget/ai-credits-widget@npm:0.1.12": version: 0.1.12 resolution: "@goodwidget/ai-credits-widget@npm:0.1.12" @@ -1028,6 +1041,22 @@ __metadata: languageName: node linkType: hard +"@goodwidget/streaming-widget@npm:0.1.0": + version: 0.1.0 + resolution: "@goodwidget/streaming-widget@npm:0.1.0" + dependencies: + "@goodsdks/streaming-sdk": "npm:1.1.0" + "@goodwidget/core": "npm:0.1.5" + "@goodwidget/embed": "npm:0.1.4" + "@goodwidget/ui": "npm:0.1.6" + viem: "npm:^2.0.0" + peerDependencies: + react: ">=18.0.0" + react-dom: ">=18.0.0" + checksum: 10c0/44bf404bb201e97b5352bcb63b64869a1d306c91fa917f4ba0c13d695f2a6d5adf0f31421d540932151e22ce5dc1b334ab3c3c5b1a9e4d4448d5cc6868b95ea3 + languageName: node + linkType: hard + "@goodwidget/superfluid-campaign-widget@npm:0.1.7": version: 0.1.7 resolution: "@goodwidget/superfluid-campaign-widget@npm:0.1.7" @@ -1119,6 +1148,15 @@ __metadata: languageName: node linkType: hard +"@graphql-typed-document-node/core@npm:^3.2.0": + version: 3.2.0 + resolution: "@graphql-typed-document-node/core@npm:3.2.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/94e9d75c1f178bbae8d874f5a9361708a3350c8def7eaeb6920f2c820e82403b7d4f55b3735856d68e145e86c85cbfe2adc444fdc25519cd51f108697e99346c + languageName: node + linkType: hard + "@hcaptcha/react-hcaptcha@npm:^2.0.2": version: 2.0.2 resolution: "@hcaptcha/react-hcaptcha@npm:2.0.2" @@ -3160,6 +3198,27 @@ __metadata: languageName: node linkType: hard +"@sfpro/sdk@npm:^0.1.0": + version: 0.1.9 + resolution: "@sfpro/sdk@npm:0.1.9" + peerDependencies: + "@wagmi/core": ^2 + react: ">=18" + viem: ^2 + wagmi: ^2 + peerDependenciesMeta: + "@wagmi/core": + optional: true + react: + optional: true + viem: + optional: true + wagmi: + optional: true + checksum: 10c0/b800a428948d18674fb9ca2a1cec013cd56cdfd844f9b859249d7c174d1f4f99a2eafe74f542f4c11122c7c049389f68d5bbdc72278bcbbbb89586379034abe7 + languageName: node + linkType: hard + "@signinwithethereum/siwe-parser@npm:^4.2.0": version: 4.2.0 resolution: "@signinwithethereum/siwe-parser@npm:4.2.0" @@ -10050,6 +10109,7 @@ __metadata: "@goodwidget/core": "npm:0.1.5" "@goodwidget/embed": "npm:0.1.4" "@goodwidget/goodreserve-widget": "npm:0.1.7" + "@goodwidget/streaming-widget": "npm:0.1.0" "@goodwidget/superfluid-campaign-widget": "npm:0.1.7" "@goodwidget/ui": "npm:0.1.6" "@hcaptcha/react-hcaptcha": "npm:^2.0.2" @@ -10132,6 +10192,24 @@ __metadata: languageName: node linkType: hard +"graphql-request@npm:^7.1.2": + version: 7.4.0 + resolution: "graphql-request@npm:7.4.0" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.2.0" + peerDependencies: + graphql: 14 - 16 + checksum: 10c0/066531d70b9c4656251e51c950ce9bee3df05291e12e2b983608d75ac3a70d700efd6488d273b043235dc125658973db38b21eba59e808863831a5a19a6a7b41 + languageName: node + linkType: hard + +"graphql@npm:^16.9.0": + version: 16.14.2 + resolution: "graphql@npm:16.14.2" + checksum: 10c0/a95a96961eaff55cc9fe9d31fae6f33499ac988b972d07ea5085024cb1333f515b902f376e7393a5489aa82200a8aff3eb96580e4d1b69d702ed19b6eb1ce97a + languageName: node + linkType: hard + "h3@npm:^1.15.10": version: 1.15.11 resolution: "h3@npm:1.15.11" From 9081c73a876c351dac983611d2d788ee9333f0cd Mon Sep 17 00:00:00 2001 From: blueogin Date: Thu, 15 Oct 2026 10:27:38 -0400 Subject: [PATCH 3/4] feat: refactor streaming widget package name and version handling --- src/widgets/registry.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/widgets/registry.ts b/src/widgets/registry.ts index 0ade103..2a99e47 100644 --- a/src/widgets/registry.ts +++ b/src/widgets/registry.ts @@ -272,10 +272,13 @@ const goodReserveWidget = defineWidget({ * Token balances. The package registers its Custom Element as a side effect * when its register entry is imported. */ +const STREAMING_WIDGET_PACKAGE = "@goodwidget/streaming-widget" +const STREAMING_WIDGET_VERSION = "0.1.0" + const streamingWidget = defineWidget({ widgetId: "goodwidget.streaming", - packageName: "@goodwidget/streaming-widget", - packageVersion: "0.1.0", + packageName: STREAMING_WIDGET_PACKAGE, + packageVersion: STREAMING_WIDGET_VERSION, routeSlug: "streaming", displayName: "Streaming", description: "Manage money streams, pools, and Super Token balances", @@ -287,7 +290,19 @@ const streamingWidget = defineWidget({ integrationMode: "web-component", entry: { tagName: "gw-streaming", - load: () => import("@goodwidget/streaming-widget/register"), + /** + * Unlike every other GoodWidget package, streaming-widget 0.1.0 does not + * export `goodWidgetMetadata` from its register entry, so the module fails + * both the host type contract and `assertWidgetModuleMetadata`. Supply the + * identity here; drop this once the package exports its own. + */ + load: async () => ({ + ...(await import("@goodwidget/streaming-widget/register")), + goodWidgetMetadata: { + packageName: STREAMING_WIDGET_PACKAGE, + packageVersion: STREAMING_WIDGET_VERSION, + }, + }), }, providerPolicy: { chainIds: [CELO_CHAIN_ID, BASE_CHAIN_ID], From 35b48b57ee4a74df21c626387bc7b6f26b394517 Mon Sep 17 00:00:00 2001 From: blueogin Date: Thu, 15 Oct 2026 10:58:13 -0400 Subject: [PATCH 4/4] feat: update streaming widget version to 0.1.1 and adjust related references --- package.json | 2 +- .../fixtures/streamingWidgetRegisterMock.ts | 5 +++-- src/widgets/registry.test.ts | 2 +- src/widgets/registry.ts | 21 +++---------------- yarn.lock | 10 ++++----- 5 files changed, 13 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index ac21563..c6f85b4 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@goodwidget/core": "0.1.5", "@goodwidget/embed": "0.1.4", "@goodwidget/goodreserve-widget": "0.1.7", - "@goodwidget/streaming-widget": "0.1.0", + "@goodwidget/streaming-widget": "0.1.1", "@goodwidget/superfluid-campaign-widget": "0.1.7", "@goodwidget/ui": "0.1.6", "@hcaptcha/react-hcaptcha": "^2.0.2", diff --git a/src/widgets/fixtures/streamingWidgetRegisterMock.ts b/src/widgets/fixtures/streamingWidgetRegisterMock.ts index 0f1239b..0a1f284 100644 --- a/src/widgets/fixtures/streamingWidgetRegisterMock.ts +++ b/src/widgets/fixtures/streamingWidgetRegisterMock.ts @@ -7,7 +7,8 @@ */ export const goodWidgetMetadata = { packageName: "@goodwidget/streaming-widget", - packageVersion: "0.1.0", + packageVersion: "0.1.1", } -export const register = (tagName?: string): string => tagName ?? "gw-streaming" +export const register = async (tagName?: string): Promise => + tagName ?? "gw-streaming" diff --git a/src/widgets/registry.test.ts b/src/widgets/registry.test.ts index 3297820..2033b7c 100644 --- a/src/widgets/registry.test.ts +++ b/src/widgets/registry.test.ts @@ -283,7 +283,7 @@ describe("widget registry", () => { expect(streaming).toMatchObject({ widgetId: "goodwidget.streaming", packageName: "@goodwidget/streaming-widget", - packageVersion: "0.1.0", + packageVersion: "0.1.1", routeSlug: "streaming", entry: { tagName: "gw-streaming" }, providerPolicy: { diff --git a/src/widgets/registry.ts b/src/widgets/registry.ts index 2a99e47..c7448ae 100644 --- a/src/widgets/registry.ts +++ b/src/widgets/registry.ts @@ -272,13 +272,10 @@ const goodReserveWidget = defineWidget({ * Token balances. The package registers its Custom Element as a side effect * when its register entry is imported. */ -const STREAMING_WIDGET_PACKAGE = "@goodwidget/streaming-widget" -const STREAMING_WIDGET_VERSION = "0.1.0" - const streamingWidget = defineWidget({ widgetId: "goodwidget.streaming", - packageName: STREAMING_WIDGET_PACKAGE, - packageVersion: STREAMING_WIDGET_VERSION, + packageName: "@goodwidget/streaming-widget", + packageVersion: "0.1.1", routeSlug: "streaming", displayName: "Streaming", description: "Manage money streams, pools, and Super Token balances", @@ -290,19 +287,7 @@ const streamingWidget = defineWidget({ integrationMode: "web-component", entry: { tagName: "gw-streaming", - /** - * Unlike every other GoodWidget package, streaming-widget 0.1.0 does not - * export `goodWidgetMetadata` from its register entry, so the module fails - * both the host type contract and `assertWidgetModuleMetadata`. Supply the - * identity here; drop this once the package exports its own. - */ - load: async () => ({ - ...(await import("@goodwidget/streaming-widget/register")), - goodWidgetMetadata: { - packageName: STREAMING_WIDGET_PACKAGE, - packageVersion: STREAMING_WIDGET_VERSION, - }, - }), + load: () => import("@goodwidget/streaming-widget/register"), }, providerPolicy: { chainIds: [CELO_CHAIN_ID, BASE_CHAIN_ID], diff --git a/yarn.lock b/yarn.lock index d23bdcd..671a4f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1041,9 +1041,9 @@ __metadata: languageName: node linkType: hard -"@goodwidget/streaming-widget@npm:0.1.0": - version: 0.1.0 - resolution: "@goodwidget/streaming-widget@npm:0.1.0" +"@goodwidget/streaming-widget@npm:0.1.1": + version: 0.1.1 + resolution: "@goodwidget/streaming-widget@npm:0.1.1" dependencies: "@goodsdks/streaming-sdk": "npm:1.1.0" "@goodwidget/core": "npm:0.1.5" @@ -1053,7 +1053,7 @@ __metadata: peerDependencies: react: ">=18.0.0" react-dom: ">=18.0.0" - checksum: 10c0/44bf404bb201e97b5352bcb63b64869a1d306c91fa917f4ba0c13d695f2a6d5adf0f31421d540932151e22ce5dc1b334ab3c3c5b1a9e4d4448d5cc6868b95ea3 + checksum: 10c0/736eed474360e938a07e8a4d27dafabe0baa49e0dcd9323da2370cd73a719a01f1ade4363a7e2d72a635cc85de309baa3e40b00a96b79702e2657e20d70f98bd languageName: node linkType: hard @@ -10109,7 +10109,7 @@ __metadata: "@goodwidget/core": "npm:0.1.5" "@goodwidget/embed": "npm:0.1.4" "@goodwidget/goodreserve-widget": "npm:0.1.7" - "@goodwidget/streaming-widget": "npm:0.1.0" + "@goodwidget/streaming-widget": "npm:0.1.1" "@goodwidget/superfluid-campaign-widget": "npm:0.1.7" "@goodwidget/ui": "npm:0.1.6" "@hcaptcha/react-hcaptcha": "npm:^2.0.2"