diff --git a/src/index.ts b/src/index.ts index 2788c5d0..5fbb21dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,41 +35,8 @@ const start = async () => { const { mainHandler, localProviderClient, wizard, adminManager } = keepOn const serverMethods = GetServerMethods(mainHandler) - log("initializing nostr middleware") - const relays = settingsManager.getSettings().nostrRelaySettings.relays - const maxEventContentLength = settingsManager.getSettings().nostrRelaySettings.maxEventContentLength - const apps: AppInfo[] = keepOn.apps.map(app => { - return { - appId: app.appId, - privateKey: app.privateKey, - publicKey: app.publicKey, - name: app.name, - provider: app.publicKey === localProviderClient.publicKey ? { - clientId: `client_${localProviderClient.appId}`, - pubkey: settingsManager.getSettings().liquiditySettings.liquidityProviderPub, - relayUrl: settingsManager.getSettings().liquiditySettings.providerRelayUrl - } : undefined - } - }) - const { Send, Stop, Ping, Reset } = nostrMiddleware(serverMethods, mainHandler, - { - relays, maxEventContentLength, apps - }, - (e, p) => mainHandler.liquidityProvider.onEvent(e, p) - ) - exitHandler(() => { Stop(); mainHandler.Stop() }) - log("starting server") - mainHandler.attachNostrSend(Send) - mainHandler.attachNostrProcessPing(Ping) - mainHandler.attachNostrReset(Reset) - mainHandler.StartBeacons() - const appNprofile = nprofileEncode({ pubkey: localProviderClient.publicKey, relays }) - if (wizard) { - wizard.AddConnectInfo(appNprofile, relays) - } - adminManager.setAppNprofile(appNprofile) - // Initialize extension system + // Initialize extension system BEFORE nostrMiddleware so RPC methods are available let extensionLoader: ExtensionLoader | null = null const mainPort = settingsManager.getSettings().serviceSettings.servicePort const extensionPort = mainPort + 1 @@ -103,6 +70,42 @@ const start = async () => { log(`extension system initialization failed: ${e}`) } + // Initialize nostr middleware with extension loader for RPC routing + log("initializing nostr middleware") + const relays = settingsManager.getSettings().nostrRelaySettings.relays + const maxEventContentLength = settingsManager.getSettings().nostrRelaySettings.maxEventContentLength + const apps: AppInfo[] = keepOn.apps.map(app => { + return { + appId: app.appId, + privateKey: app.privateKey, + publicKey: app.publicKey, + name: app.name, + provider: app.publicKey === localProviderClient.publicKey ? { + clientId: `client_${localProviderClient.appId}`, + pubkey: settingsManager.getSettings().liquiditySettings.liquidityProviderPub, + relayUrl: settingsManager.getSettings().liquiditySettings.providerRelayUrl + } : undefined + } + }) + const { Send, Stop, Ping, Reset } = nostrMiddleware(serverMethods, mainHandler, + { + relays, maxEventContentLength, apps + }, + (e, p) => mainHandler.liquidityProvider.onEvent(e, p), + { extensionLoader: extensionLoader || undefined } + ) + exitHandler(() => { Stop(); mainHandler.Stop() }) + log("starting server") + mainHandler.attachNostrSend(Send) + mainHandler.attachNostrProcessPing(Ping) + mainHandler.attachNostrReset(Reset) + mainHandler.StartBeacons() + const appNprofile = nprofileEncode({ pubkey: localProviderClient.publicKey, relays }) + if (wizard) { + wizard.AddConnectInfo(appNprofile, relays) + } + adminManager.setAppNprofile(appNprofile) + // Create Express app for extension HTTP routes const extensionApp = express() extensionApp.use(cors()) // Enable CORS for all origins (ATM apps, wallets, etc.) diff --git a/src/nostrMiddleware.ts b/src/nostrMiddleware.ts index 4dd3b281..a7131375 100644 --- a/src/nostrMiddleware.ts +++ b/src/nostrMiddleware.ts @@ -5,9 +5,15 @@ import * as Types from '../proto/autogenerated/ts/types.js' import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js'; import { ERROR, getLogger } from "./services/helpers/logger.js"; import { NdebitData, NofferData, NmanageRequest } from "@shocknet/clink-sdk"; +import type { ExtensionLoader } from "./extensions/loader.js" type ExportedCalls = { Stop: () => void, Send: NostrSend, Ping: () => Promise, Reset: (settings: NostrSettings) => void } type ClientEventCallback = (e: { requestId: string }, fromPub: string) => void -export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: ClientEventCallback): ExportedCalls => { + +export type NostrMiddlewareOptions = { + extensionLoader?: ExtensionLoader +} + +export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: ClientEventCallback, options?: NostrMiddlewareOptions): ExportedCalls => { const log = getLogger({}) const nostrTransport = NewNostrTransport(serverMethods, { NostrUserAuthGuard: async (appId, pub) => { @@ -95,6 +101,31 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett log(ERROR, "authIdentifier does not match", j.authIdentifier || "--", event.pub) return } + + // Check if this is an extension RPC method + const extensionLoader = options?.extensionLoader + if (extensionLoader && j.rpcName && extensionLoader.hasMethod(j.rpcName)) { + // Route to extension + log(`[Nostr] Routing to extension method: ${j.rpcName}`) + extensionLoader.callMethod(j.rpcName, j.body || {}, event.appId, event.pub) + .then(result => { + const response = { status: 'OK', requestId: j.requestId, ...result } + nostr.Send( + { type: 'app', appId: event.appId }, + { type: 'content', pub: event.pub, content: JSON.stringify(response) } + ) + }) + .catch(err => { + log(ERROR, `Extension method ${j.rpcName} failed:`, err.message) + const response = { status: 'ERROR', requestId: j.requestId, reason: err.message } + nostr.Send( + { type: 'app', appId: event.appId }, + { type: 'content', pub: event.pub, content: JSON.stringify(response) } + ) + }) + return + } + nostrTransport({ ...j, appId: event.appId }, res => { nostr.Send({ type: 'app', appId: event.appId }, { type: 'content', pub: event.pub, content: JSON.stringify({ ...res, requestId: j.requestId }) }) }, event.startAtNano, event.startAtMs)