From ebc4c8d915193bc29e241174abb418ba6e212d02 Mon Sep 17 00:00:00 2001 From: mfbz Date: Thu, 22 May 2025 14:59:47 +0200 Subject: [PATCH 1/3] Refactored discovery folder --- .../src/discovery/{index.js => index.ts} | 0 .../{services.test.js => services.test.ts} | 5 +- .../discovery/{services.js => services.ts} | 25 ++++- .../discovery/services/{authn.js => authn.ts} | 98 +++++++++++-------- packages/fcl-core/src/discovery/utils.js | 43 -------- packages/fcl-core/src/discovery/utils.ts | 61 ++++++++++++ 6 files changed, 143 insertions(+), 89 deletions(-) rename packages/fcl-core/src/discovery/{index.js => index.ts} (100%) rename packages/fcl-core/src/discovery/{services.test.js => services.test.ts} (95%) rename packages/fcl-core/src/discovery/{services.js => services.ts} (70%) rename packages/fcl-core/src/discovery/services/{authn.js => authn.ts} (52%) delete mode 100644 packages/fcl-core/src/discovery/utils.js create mode 100644 packages/fcl-core/src/discovery/utils.ts diff --git a/packages/fcl-core/src/discovery/index.js b/packages/fcl-core/src/discovery/index.ts similarity index 100% rename from packages/fcl-core/src/discovery/index.js rename to packages/fcl-core/src/discovery/index.ts diff --git a/packages/fcl-core/src/discovery/services.test.js b/packages/fcl-core/src/discovery/services.test.ts similarity index 95% rename from packages/fcl-core/src/discovery/services.test.js rename to packages/fcl-core/src/discovery/services.test.ts index 5690ff899..d899a0053 100644 --- a/packages/fcl-core/src/discovery/services.test.js +++ b/packages/fcl-core/src/discovery/services.test.ts @@ -78,7 +78,6 @@ describe("getServices", () => { afterEach(() => { windowSpy.mockRestore() chainIdSpy.mockRestore() - global.fetch.mockClear() }) it("it should get only services of type authn", async () => { @@ -92,9 +91,9 @@ describe("getServices", () => { Promise.resolve({ json: () => Promise.resolve(mockData), }) - ) + ) as jest.Mock - const response = await getServices({type: ["authn"]}) + const response = await getServices({types: ["authn"]}) expect(global.fetch).toHaveBeenCalledTimes(1) }) }) diff --git a/packages/fcl-core/src/discovery/services.js b/packages/fcl-core/src/discovery/services.ts similarity index 70% rename from packages/fcl-core/src/discovery/services.js rename to packages/fcl-core/src/discovery/services.ts index ef624261e..6e7f9abae 100644 --- a/packages/fcl-core/src/discovery/services.js +++ b/packages/fcl-core/src/discovery/services.ts @@ -5,8 +5,29 @@ import {getChainId} from "../utils" import {VERSION} from "../VERSION" import {makeDiscoveryServices} from "./utils" import {URL} from "../utils/url" +import {Service} from "@onflow/typedefs" -export async function getServices({types}) { +export interface GetServicesParams { + types: string[] +} + +export interface DiscoveryRequestBody { + type: string[] + fclVersion: string + include: string[] + exclude: string[] + features: { + suggested: string[] + } + clientServices: Service[] + supportedStrategies: string[] + userAgent?: string + network: string +} + +export async function getServices({ + types, +}: GetServicesParams): Promise { const endpoint = await config.get("discovery.authn.endpoint") invariant( Boolean(endpoint), @@ -34,6 +55,6 @@ export async function getServices({types}) { supportedStrategies: getServiceRegistry().getStrategies(), userAgent: window?.navigator?.userAgent, network: await getChainId(), - }), + } as DiscoveryRequestBody), }).then(d => d.json()) } diff --git a/packages/fcl-core/src/discovery/services/authn.js b/packages/fcl-core/src/discovery/services/authn.ts similarity index 52% rename from packages/fcl-core/src/discovery/services/authn.js rename to packages/fcl-core/src/discovery/services/authn.ts index 899233bce..83bcbd38e 100644 --- a/packages/fcl-core/src/discovery/services/authn.js +++ b/packages/fcl-core/src/discovery/services/authn.ts @@ -6,9 +6,11 @@ import { SUBSCRIBE, UNSUBSCRIBE, send, + Letter as ActorLetter, } from "@onflow/util-actor" import {getServices} from "../services" import {LEVELS, log} from "@onflow/util-logger" +import {Service} from "@onflow/typedefs" export const SERVICE_ACTOR_KEYS = { AUTHN: "authn", @@ -16,9 +18,41 @@ export const SERVICE_ACTOR_KEYS = { SNAPSHOT: "SNAPSHOT", UPDATED: "UPDATED", UPDATE_RESULTS: "UPDATE_RESULTS", +} as const + +export interface ActorContext { + self: () => string + receive: () => Promise + send: ( + to: string | null | undefined, + tag: string, + data?: any, + opts?: Record + ) => Promise | undefined + merge: (data: Record) => void + broadcast: (tag: string, data: Record) => void + subscribe: (from: string) => void + unsubscribe: (from: string) => void + all: () => Record + fatalError: (error: Error) => void +} + +export interface ServiceData { + results: Service[] +} + +export type SubscriptionCallback = ( + data: Service[] | null, + error: Error | null +) => void + +export interface Authn { + subscribe: (cb: SubscriptionCallback) => () => void + snapshot: () => Promise + update: () => void } -const warn = (fact, msg) => { +const warn = (fact: boolean, msg: string): void => { if (fact) { console.warn( ` @@ -33,13 +67,13 @@ const warn = (fact, msg) => { } } -const fetchServicesFromDiscovery = async () => { +const fetchServicesFromDiscovery = async (): Promise => { try { const services = await getServices({types: [SERVICE_ACTOR_KEYS.AUTHN]}) send(SERVICE_ACTOR_KEYS.AUTHN, SERVICE_ACTOR_KEYS.UPDATE_RESULTS, { results: services, }) - } catch (error) { + } catch (error: any) { log({ title: `${error.name} Error fetching Discovery API services.`, message: error.message, @@ -49,7 +83,7 @@ const fetchServicesFromDiscovery = async () => { } const HANDLERS = { - [INIT]: async ctx => { + [INIT]: async (ctx: ActorContext) => { warn( typeof window === "undefined", '"fcl.discovery" is only available in the browser.' @@ -63,55 +97,37 @@ const HANDLERS = { }) } }, - [SERVICE_ACTOR_KEYS.UPDATE_RESULTS]: (ctx, _letter, data) => { + [SERVICE_ACTOR_KEYS.UPDATE_RESULTS]: ( + ctx: ActorContext, + _letter: ActorLetter, + data: ServiceData + ) => { ctx.merge(data) ctx.broadcast(SERVICE_ACTOR_KEYS.UPDATED, {...ctx.all()}) }, - [SUBSCRIBE]: (ctx, letter) => { - ctx.subscribe(letter.from) - ctx.send(letter.from, SERVICE_ACTOR_KEYS.UPDATED, {...ctx.all()}) + [SUBSCRIBE]: (ctx: ActorContext, letter: ActorLetter) => { + ctx.subscribe(letter.from!) + ctx.send(letter.from!, SERVICE_ACTOR_KEYS.UPDATED, {...ctx.all()}) }, - [UNSUBSCRIBE]: (ctx, letter) => ctx.unsubscribe(letter.from), - [SERVICE_ACTOR_KEYS.SNAPSHOT]: async (ctx, letter) => - letter.reply({...ctx.all()}), + [UNSUBSCRIBE]: (ctx: ActorContext, letter: ActorLetter) => + ctx.unsubscribe(letter.from!), + [SERVICE_ACTOR_KEYS.SNAPSHOT]: async ( + ctx: ActorContext, + letter: ActorLetter + ) => letter.reply({...ctx.all()}), } const spawnProviders = () => spawn(HANDLERS, SERVICE_ACTOR_KEYS.AUTHN) /** - * @typedef {import("@onflow/typedefs").Service} Service - */ - -/** - * @callback SubscriptionCallback - * @returns {Service[]} - */ - -/** - * @description - * Discovery methods for interacting with Authn. - * - * @typedef {object} Authn - * @property {Function} subscribe - Subscribe to Discovery authn services - * @property {Function} snapshot - Get the current Discovery authn services spanshot - * @property {Function} update - Trigger an update of authn services + * @description Discovery methods for interacting with Authn. */ -const authn = { - /** - * @description - Subscribe to Discovery authn services - * @param {Function} cb - * @returns {SubscriptionCallback} - */ +const authn: Authn = { + // Subscribe to Discovery authn services subscribe: cb => subscriber(SERVICE_ACTOR_KEYS.AUTHN, spawnProviders, cb), - /** - * @description - Get the current Discovery authn services spanshot - * @returns {Service[]} - */ + // Get the current Discovery authn services snapshot snapshot: () => snapshoter(SERVICE_ACTOR_KEYS.AUTHN, spawnProviders), - /** - * @description - Trigger an update of authn services - * @returns {void} - */ + // Trigger an update of authn services update: () => { // Only fetch services if the window is loaded // Otherwise, this will be called by the INIT handler diff --git a/packages/fcl-core/src/discovery/utils.js b/packages/fcl-core/src/discovery/utils.js deleted file mode 100644 index 3bc1c8e8e..000000000 --- a/packages/fcl-core/src/discovery/utils.js +++ /dev/null @@ -1,43 +0,0 @@ -import {config} from "@onflow/config" -import {invariant} from "@onflow/util-invariant" -import {getServiceRegistry} from "../current-user/exec-service/plugins" - -export const makeDiscoveryServices = async () => { - const extensionServices = window?.fcl_extensions || [] - return [...extensionServices, ...getServiceRegistry().getServices()] -} - -export async function getDiscoveryService(service) { - const discoveryAuthnInclude = await config.get("discovery.authn.include", []) - const discoveryAuthnExclude = await config.get("discovery.authn.exclude", []) - const discoveryFeaturesSuggested = await config.get( - "discovery.features.suggested", - [] - ) - const discoveryWalletMethod = await config.first([ - "discovery.wallet.method", - "discovery.wallet.method.default", - ]) - const method = service?.method ? service.method : discoveryWalletMethod - const endpoint = - service?.endpoint ?? - (await config.first(["discovery.wallet", "challenge.handshake"])) - - invariant( - endpoint, - ` - If no service is passed to "authenticate," then "discovery.wallet" must be defined in fcl config. - See: "https://docs.onflow.org/fcl/reference/api/#setting-configuration-values" - ` - ) - - return { - ...service, - type: "authn", - endpoint, - method, - discoveryAuthnInclude, - discoveryAuthnExclude, - discoveryFeaturesSuggested, - } -} diff --git a/packages/fcl-core/src/discovery/utils.ts b/packages/fcl-core/src/discovery/utils.ts new file mode 100644 index 000000000..bd6ebd20e --- /dev/null +++ b/packages/fcl-core/src/discovery/utils.ts @@ -0,0 +1,61 @@ +import {config} from "@onflow/config" +import {invariant} from "@onflow/util-invariant" +import {getServiceRegistry} from "../current-user/exec-service/plugins" +import {Service} from "@onflow/typedefs" + +export interface DiscoveryService extends Service { + discoveryAuthnInclude: string[] + discoveryAuthnExclude: string[] + discoveryFeaturesSuggested: string[] +} + +export const makeDiscoveryServices = async (): Promise => { + const extensionServices = ((window as any)?.fcl_extensions || []) as Service[] + return [ + ...extensionServices, + ...(getServiceRegistry().getServices() as Service[]), + ] +} + +export async function getDiscoveryService( + service?: Partial +): Promise { + const discoveryAuthnInclude = (await config.get( + "discovery.authn.include", + [] + )) as string[] + const discoveryAuthnExclude = (await config.get( + "discovery.authn.exclude", + [] + )) as string[] + const discoveryFeaturesSuggested = (await config.get( + "discovery.features.suggested", + [] + )) as string[] + const discoveryWalletMethod = await config.first( + ["discovery.wallet.method", "discovery.wallet.method.default"], + undefined + ) + const method = service?.method ? service.method : discoveryWalletMethod + const endpoint = + service?.endpoint ?? + (await config.first(["discovery.wallet", "challenge.handshake"], undefined)) + + invariant( + endpoint as any, + ` + If no service is passed to "authenticate," then "discovery.wallet" must be defined in fcl config. + See: "https://docs.onflow.org/fcl/reference/api/#setting-configuration-values" + ` + ) + + return { + ...service, + type: "authn", + endpoint, + method, + discoveryAuthnInclude, + discoveryAuthnExclude, + discoveryFeaturesSuggested, + } as DiscoveryService +} From 87abf5ec901d6c6cec6af256c7a9811b7096ae1c Mon Sep 17 00:00:00 2001 From: mfbz Date: Tue, 27 May 2025 22:18:14 +0200 Subject: [PATCH 2/3] Reused ActorContext from util-actor package --- .../fcl-core/src/discovery/services/authn.ts | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/packages/fcl-core/src/discovery/services/authn.ts b/packages/fcl-core/src/discovery/services/authn.ts index 83bcbd38e..1da615826 100644 --- a/packages/fcl-core/src/discovery/services/authn.ts +++ b/packages/fcl-core/src/discovery/services/authn.ts @@ -7,6 +7,7 @@ import { UNSUBSCRIBE, send, Letter as ActorLetter, + ActorContext, } from "@onflow/util-actor" import {getServices} from "../services" import {LEVELS, log} from "@onflow/util-logger" @@ -20,23 +21,6 @@ export const SERVICE_ACTOR_KEYS = { UPDATE_RESULTS: "UPDATE_RESULTS", } as const -export interface ActorContext { - self: () => string - receive: () => Promise - send: ( - to: string | null | undefined, - tag: string, - data?: any, - opts?: Record - ) => Promise | undefined - merge: (data: Record) => void - broadcast: (tag: string, data: Record) => void - subscribe: (from: string) => void - unsubscribe: (from: string) => void - all: () => Record - fatalError: (error: Error) => void -} - export interface ServiceData { results: Service[] } @@ -117,7 +101,7 @@ const HANDLERS = { ) => letter.reply({...ctx.all()}), } -const spawnProviders = () => spawn(HANDLERS, SERVICE_ACTOR_KEYS.AUTHN) +const spawnProviders = () => spawn(HANDLERS as any, SERVICE_ACTOR_KEYS.AUTHN) /** * @description Discovery methods for interacting with Authn. From 94de42b6f28381f8bd99374d1e9a4946ca7721cc Mon Sep 17 00:00:00 2001 From: mfbz Date: Thu, 12 Jun 2025 00:26:22 +0200 Subject: [PATCH 3/3] Minor fix --- packages/fcl-core/src/discovery/services.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fcl-core/src/discovery/services.ts b/packages/fcl-core/src/discovery/services.ts index 6e7f9abae..42189e25a 100644 --- a/packages/fcl-core/src/discovery/services.ts +++ b/packages/fcl-core/src/discovery/services.ts @@ -36,7 +36,7 @@ export async function getServices({ const include = await config.get("discovery.authn.include", []) const exclude = await config.get("discovery.authn.exclude", []) - const url = new URL(endpoint) + const url = new URL(endpoint as string) return fetch(url, { method: "POST",