feat: route Nostr RPC to extension methods
Initialize extension system before nostrMiddleware so registered RPC methods are available. Extension methods (e.g. withdraw.createLink) are intercepted and routed to the extension loader before falling through to the standard nostrTransport. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fda5dd6e85
commit
fc5a8d795b
2 changed files with 69 additions and 35 deletions
71
src/index.ts
71
src/index.ts
|
|
@ -35,41 +35,8 @@ const start = async () => {
|
||||||
|
|
||||||
const { mainHandler, localProviderClient, wizard, adminManager } = keepOn
|
const { mainHandler, localProviderClient, wizard, adminManager } = keepOn
|
||||||
const serverMethods = GetServerMethods(mainHandler)
|
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
|
let extensionLoader: ExtensionLoader | null = null
|
||||||
const mainPort = settingsManager.getSettings().serviceSettings.servicePort
|
const mainPort = settingsManager.getSettings().serviceSettings.servicePort
|
||||||
const extensionPort = mainPort + 1
|
const extensionPort = mainPort + 1
|
||||||
|
|
@ -103,6 +70,42 @@ const start = async () => {
|
||||||
log(`extension system initialization failed: ${e}`)
|
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
|
// Create Express app for extension HTTP routes
|
||||||
const extensionApp = express()
|
const extensionApp = express()
|
||||||
extensionApp.use(cors()) // Enable CORS for all origins (ATM apps, wallets, etc.)
|
extensionApp.use(cors()) // Enable CORS for all origins (ATM apps, wallets, etc.)
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,15 @@ import * as Types from '../proto/autogenerated/ts/types.js'
|
||||||
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
||||||
import { ERROR, getLogger } from "./services/helpers/logger.js";
|
import { ERROR, getLogger } from "./services/helpers/logger.js";
|
||||||
import { NdebitData, NofferData, NmanageRequest } from "@shocknet/clink-sdk";
|
import { NdebitData, NofferData, NmanageRequest } from "@shocknet/clink-sdk";
|
||||||
|
import type { ExtensionLoader } from "./extensions/loader.js"
|
||||||
type ExportedCalls = { Stop: () => void, Send: NostrSend, Ping: () => Promise<void>, Reset: (settings: NostrSettings) => void }
|
type ExportedCalls = { Stop: () => void, Send: NostrSend, Ping: () => Promise<void>, Reset: (settings: NostrSettings) => void }
|
||||||
type ClientEventCallback = (e: { requestId: string }, fromPub: string) => 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 log = getLogger({})
|
||||||
const nostrTransport = NewNostrTransport(serverMethods, {
|
const nostrTransport = NewNostrTransport(serverMethods, {
|
||||||
NostrUserAuthGuard: async (appId, pub) => {
|
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)
|
log(ERROR, "authIdentifier does not match", j.authIdentifier || "--", event.pub)
|
||||||
return
|
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 => {
|
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 }) })
|
nostr.Send({ type: 'app', appId: event.appId }, { type: 'content', pub: event.pub, content: JSON.stringify({ ...res, requestId: j.requestId }) })
|
||||||
}, event.startAtNano, event.startAtMs)
|
}, event.startAtNano, event.startAtMs)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue