Skip to content

Refactored fcl-core discovery folder files to TypeScript #2462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ describe("getServices", () => {
afterEach(() => {
windowSpy.mockRestore()
chainIdSpy.mockRestore()
global.fetch.mockClear()
})

it("it should get only services of type authn", async () => {
Expand All @@ -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)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -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<Service[]> {
const endpoint = await config.get("discovery.authn.endpoint")
invariant(
Boolean(endpoint),
Expand All @@ -15,7 +36,7 @@ export async function getServices({types}) {

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",
Expand All @@ -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())
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,37 @@ import {
SUBSCRIBE,
UNSUBSCRIBE,
send,
Letter as ActorLetter,
ActorContext,
} 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",
RESULTS: "results",
SNAPSHOT: "SNAPSHOT",
UPDATED: "UPDATED",
UPDATE_RESULTS: "UPDATE_RESULTS",
} as const

export interface ServiceData {
results: Service[]
}

const warn = (fact, msg) => {
export type SubscriptionCallback = (
data: Service[] | null,
error: Error | null
) => void

export interface Authn {
subscribe: (cb: SubscriptionCallback) => () => void
snapshot: () => Promise<ServiceData>
update: () => void
}

const warn = (fact: boolean, msg: string): void => {
if (fact) {
console.warn(
`
Expand All @@ -33,13 +51,13 @@ const warn = (fact, msg) => {
}
}

const fetchServicesFromDiscovery = async () => {
const fetchServicesFromDiscovery = async (): Promise<void> => {
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,
Expand All @@ -49,7 +67,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.'
Expand All @@ -63,55 +81,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[]}
*/
const spawnProviders = () => spawn(HANDLERS as any, SERVICE_ACTOR_KEYS.AUTHN)

/**
* @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
Expand Down
43 changes: 0 additions & 43 deletions packages/fcl-core/src/discovery/utils.js

This file was deleted.

61 changes: 61 additions & 0 deletions packages/fcl-core/src/discovery/utils.ts
Original file line number Diff line number Diff line change
@@ -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<Service[]> => {
const extensionServices = ((window as any)?.fcl_extensions || []) as Service[]
return [
...extensionServices,
...(getServiceRegistry().getServices() as Service[]),
]
}

export async function getDiscoveryService(
service?: Partial<Service>
): Promise<DiscoveryService> {
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
}