feat(extensions): add LNURL-withdraw extension #6
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