wrtc test

This commit is contained in:
boufni95 2025-01-27 21:16:53 +00:00
parent aa47bbe7c2
commit e7aef1e3ab
15 changed files with 423 additions and 6 deletions

View file

@ -250,6 +250,16 @@ The nostr server will send back a message response, and inside the body there wi
- input: [DebitResponse](#DebitResponse) - input: [DebitResponse](#DebitResponse)
- This methods has an __empty__ __response__ body - This methods has an __empty__ __response__ body
- SubToWebRtcCandidates
- auth type: __Metrics__
- This methods has an __empty__ __request__ body
- output: [WebRtcCandidate](#WebRtcCandidate)
- SubmitWebRtcMessage
- auth type: __Metrics__
- input: [WebRtcMessage](#WebRtcMessage)
- output: [WebRtcAnswer](#WebRtcAnswer)
- UpdateCallbackUrl - UpdateCallbackUrl
- auth type: __User__ - auth type: __User__
- input: [CallbackUrl](#CallbackUrl) - input: [CallbackUrl](#CallbackUrl)
@ -298,6 +308,7 @@ The nostr server will send back a message response, and inside the body there wi
- __Metrics__: - __Metrics__:
- expected context content - expected context content
- __app_id__: _string_
- __operator_id__: _string_ - __operator_id__: _string_
- __User__: - __User__:
@ -808,6 +819,20 @@ The nostr server will send back a message response, and inside the body there wi
- input: [SetMockInvoiceAsPaidRequest](#SetMockInvoiceAsPaidRequest) - input: [SetMockInvoiceAsPaidRequest](#SetMockInvoiceAsPaidRequest)
- This methods has an __empty__ __response__ body - This methods has an __empty__ __response__ body
- SubToWebRtcCandidates
- auth type: __Metrics__
- http method: __post__
- http route: __/api/upgrade/wrtc/candidates__
- This methods has an __empty__ __request__ body
- output: [WebRtcCandidate](#WebRtcCandidate)
- SubmitWebRtcMessage
- auth type: __Metrics__
- http method: __post__
- http route: __/api/upgrade/wrtc__
- input: [WebRtcMessage](#WebRtcMessage)
- output: [WebRtcAnswer](#WebRtcAnswer)
- UpdateCallbackUrl - UpdateCallbackUrl
- auth type: __User__ - auth type: __User__
- http method: __post__ - http method: __post__
@ -1391,6 +1416,15 @@ The nostr server will send back a message response, and inside the body there wi
- __negative_balance__: _number_ - __negative_balance__: _number_
- __no_balance__: _number_ - __no_balance__: _number_
- __total__: _number_ - __total__: _number_
### WebRtcAnswer
- __answer__: _string_ *this field is optional
### WebRtcCandidate
- __candidate__: _string_
### WebRtcMessage
- __message__: _[WebRtcMessage_message](#WebRtcMessage_message)_
## Enums ## Enums
### The enumerators used in the messages ### The enumerators used in the messages

View file

@ -123,6 +123,8 @@ type Client struct {
SetMockAppBalance func(req SetMockAppBalanceRequest) error SetMockAppBalance func(req SetMockAppBalanceRequest) error
SetMockAppUserBalance func(req SetMockAppUserBalanceRequest) error SetMockAppUserBalance func(req SetMockAppUserBalanceRequest) error
SetMockInvoiceAsPaid func(req SetMockInvoiceAsPaidRequest) error SetMockInvoiceAsPaid func(req SetMockInvoiceAsPaidRequest) error
SubToWebRtcCandidates func() (*WebRtcCandidate, error)
SubmitWebRtcMessage func(req WebRtcMessage) (*WebRtcAnswer, error)
UpdateCallbackUrl func(req CallbackUrl) (*CallbackUrl, error) UpdateCallbackUrl func(req CallbackUrl) (*CallbackUrl, error)
UpdateChannelPolicy func(req UpdateChannelPolicyRequest) error UpdateChannelPolicy func(req UpdateChannelPolicyRequest) error
UpdateUserOffer func(req OfferConfig) error UpdateUserOffer func(req OfferConfig) error
@ -1863,6 +1865,36 @@ func NewClient(params ClientParams) *Client {
} }
return nil return nil
}, },
// server streaming method: SubToWebRtcCandidates not implemented
SubmitWebRtcMessage: func(req WebRtcMessage) (*WebRtcAnswer, error) {
auth, err := params.RetrieveMetricsAuth()
if err != nil {
return nil, err
}
finalRoute := "/api/upgrade/wrtc"
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
if err != nil {
return nil, err
}
result := ResultError{}
err = json.Unmarshal(resBody, &result)
if err != nil {
return nil, err
}
if result.Status == "ERROR" {
return nil, fmt.Errorf(result.Reason)
}
res := WebRtcAnswer{}
err = json.Unmarshal(resBody, &res)
if err != nil {
return nil, err
}
return &res, nil
},
UpdateCallbackUrl: func(req CallbackUrl) (*CallbackUrl, error) { UpdateCallbackUrl: func(req CallbackUrl) (*CallbackUrl, error) {
auth, err := params.RetrieveUserAuth() auth, err := params.RetrieveUserAuth()
if err != nil { if err != nil {

View file

@ -19,6 +19,7 @@ type GuestWithPubContext struct {
Pub string `json:"pub"` Pub string `json:"pub"`
} }
type MetricsContext struct { type MetricsContext struct {
App_id string `json:"app_id"`
Operator_id string `json:"operator_id"` Operator_id string `json:"operator_id"`
} }
type UserContext struct { type UserContext struct {
@ -632,6 +633,15 @@ type UsersInfo struct {
No_balance int64 `json:"no_balance"` No_balance int64 `json:"no_balance"`
Total int64 `json:"total"` Total int64 `json:"total"`
} }
type WebRtcAnswer struct {
Answer string `json:"answer"`
}
type WebRtcCandidate struct {
Candidate string `json:"candidate"`
}
type WebRtcMessage struct {
Message *WebRtcMessage_message `json:"message"`
}
type DebitResponse_response_type string type DebitResponse_response_type string
const ( const (
@ -696,3 +706,15 @@ type UpdateChannelPolicyRequest_update struct {
All *Empty `json:"all"` All *Empty `json:"all"`
Channel_point *string `json:"channel_point"` Channel_point *string `json:"channel_point"`
} }
type WebRtcMessage_message_type string
const (
CANDIDATE WebRtcMessage_message_type = "candidate"
OFFER WebRtcMessage_message_type = "offer"
)
type WebRtcMessage_message struct {
Type WebRtcMessage_message_type `json:"type"`
Candidate *string `json:"candidate"`
Offer *string `json:"offer"`
}

View file

@ -1742,6 +1742,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }]) opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
}) })
if (!opts.allowNotImplementedMethods && !methods.SubmitWebRtcMessage) throw new Error('method: SubmitWebRtcMessage is not implemented')
app.post('/api/upgrade/wrtc', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'SubmitWebRtcMessage', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.SubmitWebRtcMessage) throw new Error('method: SubmitWebRtcMessage is not implemented')
const authContext = await opts.MetricsAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.WebRtcMessageValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
const response = await methods.SubmitWebRtcMessage({rpcName:'SubmitWebRtcMessage', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.UpdateCallbackUrl) throw new Error('method: UpdateCallbackUrl is not implemented') if (!opts.allowNotImplementedMethods && !methods.UpdateCallbackUrl) throw new Error('method: UpdateCallbackUrl is not implemented')
app.post('/api/user/cb/update', async (req, res) => { app.post('/api/user/cb/update', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'UpdateCallbackUrl', batch: false, nostr: false, batchSize: 0} const info: Types.RequestInfo = { rpcName: 'UpdateCallbackUrl', batch: false, nostr: false, batchSize: 0}

View file

@ -891,6 +891,21 @@ export default (params: ClientParams) => ({
} }
return { status: 'ERROR', reason: 'invalid response' } return { status: 'ERROR', reason: 'invalid response' }
}, },
SubToWebRtcCandidates: async (cb: (v:ResultError | ({ status: 'OK' }& Types.WebRtcCandidate)) => void): Promise<void> => { throw new Error('http streams are not supported')},
SubmitWebRtcMessage: async (request: Types.WebRtcMessage): Promise<ResultError | ({ status: 'OK' }& Types.WebRtcAnswer)> => {
const auth = await params.retrieveMetricsAuth()
if (auth === null) throw new Error('retrieveMetricsAuth() returned null')
let finalRoute = '/api/upgrade/wrtc'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.WebRtcAnswerValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
UpdateCallbackUrl: async (request: Types.CallbackUrl): Promise<ResultError | ({ status: 'OK' }& Types.CallbackUrl)> => { UpdateCallbackUrl: async (request: Types.CallbackUrl): Promise<ResultError | ({ status: 'OK' }& Types.CallbackUrl)> => {
const auth = await params.retrieveUserAuth() const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null') if (auth === null) throw new Error('retrieveUserAuth() returned null')

View file

@ -680,6 +680,36 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
} }
return { status: 'ERROR', reason: 'invalid response' } return { status: 'ERROR', reason: 'invalid response' }
}, },
SubToWebRtcCandidates: async (cb: (res:ResultError | ({ status: 'OK' }& Types.WebRtcCandidate)) => void): Promise<void> => {
const auth = await params.retrieveNostrMetricsAuth()
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
const nostrRequest: NostrRequest = {}
subscribe(params.pubDestination, {rpcName:'SubToWebRtcCandidates',authIdentifier:auth, ...nostrRequest }, (data) => {
if (data.status === 'ERROR' && typeof data.reason === 'string') return cb(data)
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return cb({ status: 'OK', ...result })
const error = Types.WebRtcCandidateValidate(result)
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
}
return cb({ status: 'ERROR', reason: 'invalid response' })
})
},
SubmitWebRtcMessage: async (request: Types.WebRtcMessage): Promise<ResultError | ({ status: 'OK' }& Types.WebRtcAnswer)> => {
const auth = await params.retrieveNostrMetricsAuth()
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'SubmitWebRtcMessage',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.WebRtcAnswerValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
UpdateCallbackUrl: async (request: Types.CallbackUrl): Promise<ResultError | ({ status: 'OK' }& Types.CallbackUrl)> => { UpdateCallbackUrl: async (request: Types.CallbackUrl): Promise<ResultError | ({ status: 'OK' }& Types.CallbackUrl)> => {
const auth = await params.retrieveNostrUserAuth() const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')

View file

@ -1075,6 +1075,35 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }]) opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } }catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break break
case 'SubToWebRtcCandidates':
try {
if (!methods.SubToWebRtcCandidates) throw new Error('method: SubToWebRtcCandidates is not implemented')
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
stats.validate = stats.guard
methods.SubToWebRtcCandidates({rpcName:'SubToWebRtcCandidates', ctx:authContext ,cb: (response, err) => {
stats.handle = process.hrtime.bigint()
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)} else { res({status: 'OK', ...response});opts.metricsCallback([{ ...info, ...stats, ...authContext }])}
}})
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'SubmitWebRtcMessage':
try {
if (!methods.SubmitWebRtcMessage) throw new Error('method: SubmitWebRtcMessage is not implemented')
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.WebRtcMessageValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.SubmitWebRtcMessage({rpcName:'SubmitWebRtcMessage', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'UpdateCallbackUrl': case 'UpdateCallbackUrl':
try { try {
if (!methods.UpdateCallbackUrl) throw new Error('method: UpdateCallbackUrl is not implemented') if (!methods.UpdateCallbackUrl) throw new Error('method: UpdateCallbackUrl is not implemented')

View file

@ -25,10 +25,11 @@ export type GuestWithPubContext = {
export type GuestWithPubMethodInputs = LinkNPubThroughToken_Input | UseInviteLink_Input export type GuestWithPubMethodInputs = LinkNPubThroughToken_Input | UseInviteLink_Input
export type GuestWithPubMethodOutputs = LinkNPubThroughToken_Output | UseInviteLink_Output export type GuestWithPubMethodOutputs = LinkNPubThroughToken_Output | UseInviteLink_Output
export type MetricsContext = { export type MetricsContext = {
app_id: string
operator_id: string operator_id: string
} }
export type MetricsMethodInputs = GetAppsMetrics_Input | GetErrorStats_Input | GetLndMetrics_Input | GetSingleUsageMetrics_Input | GetUsageMetrics_Input export type MetricsMethodInputs = GetAppsMetrics_Input | GetErrorStats_Input | GetLndMetrics_Input | GetSingleUsageMetrics_Input | GetUsageMetrics_Input | SubmitWebRtcMessage_Input
export type MetricsMethodOutputs = GetAppsMetrics_Output | GetErrorStats_Output | GetLndMetrics_Output | GetSingleUsageMetrics_Output | GetUsageMetrics_Output export type MetricsMethodOutputs = GetAppsMetrics_Output | GetErrorStats_Output | GetLndMetrics_Output | GetSingleUsageMetrics_Output | GetUsageMetrics_Output | SubmitWebRtcMessage_Output
export type UserContext = { export type UserContext = {
app_id: string app_id: string
app_user_id: string app_user_id: string
@ -267,6 +268,12 @@ export type SetMockAppUserBalance_Output = ResultError | { status: 'OK' }
export type SetMockInvoiceAsPaid_Input = {rpcName:'SetMockInvoiceAsPaid', req: SetMockInvoiceAsPaidRequest} export type SetMockInvoiceAsPaid_Input = {rpcName:'SetMockInvoiceAsPaid', req: SetMockInvoiceAsPaidRequest}
export type SetMockInvoiceAsPaid_Output = ResultError | { status: 'OK' } export type SetMockInvoiceAsPaid_Output = ResultError | { status: 'OK' }
export type SubToWebRtcCandidates_Input = {rpcName:'SubToWebRtcCandidates', cb:(res: WebRtcCandidate, err:Error|null)=> void}
export type SubToWebRtcCandidates_Output = ResultError | { status: 'OK' }
export type SubmitWebRtcMessage_Input = {rpcName:'SubmitWebRtcMessage', req: WebRtcMessage}
export type SubmitWebRtcMessage_Output = ResultError | ({ status: 'OK' } & WebRtcAnswer)
export type UpdateCallbackUrl_Input = {rpcName:'UpdateCallbackUrl', req: CallbackUrl} export type UpdateCallbackUrl_Input = {rpcName:'UpdateCallbackUrl', req: CallbackUrl}
export type UpdateCallbackUrl_Output = ResultError | ({ status: 'OK' } & CallbackUrl) export type UpdateCallbackUrl_Output = ResultError | ({ status: 'OK' } & CallbackUrl)
@ -351,6 +358,8 @@ export type ServerMethods = {
SetMockAppBalance?: (req: SetMockAppBalance_Input & {ctx: AppContext }) => Promise<void> SetMockAppBalance?: (req: SetMockAppBalance_Input & {ctx: AppContext }) => Promise<void>
SetMockAppUserBalance?: (req: SetMockAppUserBalance_Input & {ctx: AppContext }) => Promise<void> SetMockAppUserBalance?: (req: SetMockAppUserBalance_Input & {ctx: AppContext }) => Promise<void>
SetMockInvoiceAsPaid?: (req: SetMockInvoiceAsPaid_Input & {ctx: GuestContext }) => Promise<void> SetMockInvoiceAsPaid?: (req: SetMockInvoiceAsPaid_Input & {ctx: GuestContext }) => Promise<void>
SubToWebRtcCandidates?: (req: SubToWebRtcCandidates_Input & {ctx: MetricsContext }) => Promise<void>
SubmitWebRtcMessage?: (req: SubmitWebRtcMessage_Input & {ctx: MetricsContext }) => Promise<WebRtcAnswer>
UpdateCallbackUrl?: (req: UpdateCallbackUrl_Input & {ctx: UserContext }) => Promise<CallbackUrl> UpdateCallbackUrl?: (req: UpdateCallbackUrl_Input & {ctx: UserContext }) => Promise<CallbackUrl>
UpdateChannelPolicy?: (req: UpdateChannelPolicy_Input & {ctx: AdminContext }) => Promise<void> UpdateChannelPolicy?: (req: UpdateChannelPolicy_Input & {ctx: AdminContext }) => Promise<void>
UpdateUserOffer?: (req: UpdateUserOffer_Input & {ctx: UserContext }) => Promise<void> UpdateUserOffer?: (req: UpdateUserOffer_Input & {ctx: UserContext }) => Promise<void>
@ -3627,6 +3636,62 @@ export const UsersInfoValidate = (o?: UsersInfo, opts: UsersInfoOptions = {}, pa
return null return null
} }
export type WebRtcAnswer = {
answer?: string
}
export type WebRtcAnswerOptionalField = 'answer'
export const WebRtcAnswerOptionalFields: WebRtcAnswerOptionalField[] = ['answer']
export type WebRtcAnswerOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: WebRtcAnswerOptionalField[]
answer_CustomCheck?: (v?: string) => boolean
}
export const WebRtcAnswerValidate = (o?: WebRtcAnswer, opts: WebRtcAnswerOptions = {}, path: string = 'WebRtcAnswer::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if ((o.answer || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('answer')) && typeof o.answer !== 'string') return new Error(`${path}.answer: is not a string`)
if (opts.answer_CustomCheck && !opts.answer_CustomCheck(o.answer)) return new Error(`${path}.answer: custom check failed`)
return null
}
export type WebRtcCandidate = {
candidate: string
}
export const WebRtcCandidateOptionalFields: [] = []
export type WebRtcCandidateOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
candidate_CustomCheck?: (v: string) => boolean
}
export const WebRtcCandidateValidate = (o?: WebRtcCandidate, opts: WebRtcCandidateOptions = {}, path: string = 'WebRtcCandidate::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.candidate !== 'string') return new Error(`${path}.candidate: is not a string`)
if (opts.candidate_CustomCheck && !opts.candidate_CustomCheck(o.candidate)) return new Error(`${path}.candidate: custom check failed`)
return null
}
export type WebRtcMessage = {
message: WebRtcMessage_message
}
export const WebRtcMessageOptionalFields: [] = []
export type WebRtcMessageOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
message_Options?: WebRtcMessage_messageOptions
}
export const WebRtcMessageValidate = (o?: WebRtcMessage, opts: WebRtcMessageOptions = {}, path: string = 'WebRtcMessage::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
const messageErr = WebRtcMessage_messageValidate(o.message, opts.message_Options, `${path}.message`)
if (messageErr !== null) return messageErr
return null
}
export enum DebitResponse_response_type { export enum DebitResponse_response_type {
DENIED = 'denied', DENIED = 'denied',
INVOICE = 'invoice', INVOICE = 'invoice',
@ -3825,3 +3890,38 @@ export const UpdateChannelPolicyRequest_updateValidate = (o?: UpdateChannelPolic
} }
return null return null
} }
export enum WebRtcMessage_message_type {
CANDIDATE = 'candidate',
OFFER = 'offer',
}
export const enumCheckWebRtcMessage_message_type = (e?: WebRtcMessage_message_type): boolean => {
for (const v in WebRtcMessage_message_type) if (e === v) return true
return false
}
export type WebRtcMessage_message =
{type:WebRtcMessage_message_type.CANDIDATE, candidate:string}|
{type:WebRtcMessage_message_type.OFFER, offer:string}
export type WebRtcMessage_messageOptions = {
candidate_CustomCheck?: (v: string) => boolean
offer_CustomCheck?: (v: string) => boolean
}
export const WebRtcMessage_messageValidate = (o?: WebRtcMessage_message, opts:WebRtcMessage_messageOptions = {}, path: string = 'WebRtcMessage_message::root.'): Error | null => {
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
const stringType: string = o.type
switch (o.type) {
case WebRtcMessage_message_type.CANDIDATE:
if (typeof o.candidate !== 'string') return new Error(`${path}.candidate: is not a string`)
if (opts.candidate_CustomCheck && !opts.candidate_CustomCheck(o.candidate)) return new Error(`${path}.candidate: custom check failed`)
break
case WebRtcMessage_message_type.OFFER:
if (typeof o.offer !== 'string') return new Error(`${path}.offer: is not a string`)
if (opts.offer_CustomCheck && !opts.offer_CustomCheck(o.offer)) return new Error(`${path}.offer: custom check failed`)
break
default:
return new Error(path + ': unknown type '+ stringType)
}
return null
}

View file

@ -41,10 +41,13 @@ option (file_options) = {
id: "metrics", id: "metrics",
name: "Metrics", name: "Metrics",
//encrypted:true, //encrypted:true,
context:{ context:[{
key:"operator_id", key:"operator_id",
value:"string" value:"string"
} },{
key:"app_id",
value:"string"
}]
}, },
{ {
id:"app", id:"app",
@ -204,6 +207,18 @@ service LightningPub {
option (http_route) = "/api/reports/lnd"; option (http_route) = "/api/reports/lnd";
option (nostr) = true; option (nostr) = true;
} }
rpc SubmitWebRtcMessage(structs.WebRtcMessage) returns (structs.WebRtcAnswer) {
option (auth_type) = "Metrics";
option (http_method) = "post";
option (http_route) = "/api/upgrade/wrtc";
option (nostr) = true;
}
rpc SubToWebRtcCandidates(structs.Empty) returns (stream structs.WebRtcCandidate) {
option (auth_type) = "Metrics";
option (http_method) = "post";
option (http_route) = "/api/upgrade/wrtc/candidates";
option (nostr) = true;
}
rpc CreateOneTimeInviteLink(structs.CreateOneTimeInviteLinkRequest) returns (structs.CreateOneTimeInviteLinkResponse) { rpc CreateOneTimeInviteLink(structs.CreateOneTimeInviteLinkRequest) returns (structs.CreateOneTimeInviteLinkResponse) {
option (auth_type) = "Admin"; option (auth_type) = "Admin";

View file

@ -206,6 +206,21 @@ message LndChannels {
repeated OpenChannel open_channels = 1; repeated OpenChannel open_channels = 1;
} }
message WebRtcMessage {
oneof message {
string offer = 1;
string candidate = 2;
}
}
message WebRtcAnswer {
optional string answer = 1;
}
message WebRtcCandidate {
string candidate = 1;
}
message OpenChannelRequest{ message OpenChannelRequest{

View file

@ -67,6 +67,6 @@ const metricsAuth = async (header: string | undefined): Promise<MetricsContext>
if (h !== metricsToken) { if (h !== metricsToken) {
throw new Error("metrics token invalid") throw new Error("metrics token invalid")
} }
return { operator_id: "metrics1" } return { operator_id: "http_operator", app_id: "" }
} }
export default serverOptions export default serverOptions

View file

@ -27,7 +27,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
if (!adminNpub) { throw new Error("admin access not configured") } if (!adminNpub) { throw new Error("admin access not configured") }
if (pub !== adminNpub) { throw new Error("Metrics unavailable") } if (pub !== adminNpub) { throw new Error("Metrics unavailable") }
log("operator access from", pub) log("operator access from", pub)
return { operator_id: pub } return { operator_id: pub, app_id: appId || "" }
}, },
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null, metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
NostrGuestWithPubAuthGuard: async (appId, pub) => { NostrGuestWithPubAuthGuard: async (appId, pub) => {

View file

@ -25,6 +25,7 @@ import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
import { DebitManager } from "./debitManager.js" import { DebitManager } from "./debitManager.js"
import { NofferData } from "nostr-tools/lib/types/nip69.js" import { NofferData } from "nostr-tools/lib/types/nip69.js"
import { OfferManager } from "./offerManager.js" import { OfferManager } from "./offerManager.js"
import webRTC from "../webRTC/index.js"
type UserOperationsSub = { type UserOperationsSub = {
id: string id: string
@ -54,6 +55,7 @@ export default class {
utils: Utils utils: Utils
rugPullTracker: RugPullTracker rugPullTracker: RugPullTracker
unlocker: Unlocker unlocker: Unlocker
webRTC: webRTC
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") } nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
constructor(settings: MainSettings, storage: Storage, adminManager: AdminManager, utils: Utils, unlocker: Unlocker) { constructor(settings: MainSettings, storage: Storage, adminManager: AdminManager, utils: Utils, unlocker: Unlocker) {
this.settings = settings this.settings = settings
@ -74,6 +76,7 @@ export default class {
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager) this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
this.debitManager = new DebitManager(this.storage, this.lnd, this.applicationManager) this.debitManager = new DebitManager(this.storage, this.lnd, this.applicationManager)
this.offerManager = new OfferManager(this.storage, this.lnd, this.applicationManager, this.productManager) this.offerManager = new OfferManager(this.storage, this.lnd, this.applicationManager, this.productManager)
this.webRTC = new webRTC()
} }
@ -94,6 +97,7 @@ export default class {
this.liquidityProvider.attachNostrSend(f) this.liquidityProvider.attachNostrSend(f)
this.debitManager.attachNostrSend(f) this.debitManager.attachNostrSend(f)
this.offerManager.attachNostrSend(f) this.offerManager.attachNostrSend(f)
this.webRTC.attachNostrSend(f)
} }
htlcCb: HtlcCb = (e) => { htlcCb: HtlcCb = (e) => {

View file

@ -4,6 +4,16 @@ import main from '../main/index.js'
import Main from '../main/index.js' import Main from '../main/index.js'
export default (mainHandler: Main): Types.ServerMethods => { export default (mainHandler: Main): Types.ServerMethods => {
return { return {
SubmitWebRtcMessage: async ({ ctx, req }) => {
const err = Types.WebRtcMessageValidate(req, {
message_Options: {
candidate_CustomCheck: candidate => candidate !== '',
offer_CustomCheck: offer => offer !== '',
}
})
return mainHandler.webRTC.OnMessage({ userPub: ctx.operator_id, appId: ctx.app_id }, req.message)
},
SubToWebRtcCandidates: async ({ ctx }) => { },
GetUsageMetrics: async ({ ctx, req }) => { GetUsageMetrics: async ({ ctx, req }) => {
return mainHandler.metricsManager.GetUsageMetrics(req) return mainHandler.metricsManager.GetUsageMetrics(req)
}, },

View file

@ -0,0 +1,89 @@
import { getLogger } from "../helpers/logger.js"
import * as Types from '../../../proto/autogenerated/ts/types.js'
import { NostrSend, SendData, SendInitiator } from "../nostr/handler.js"
type IceCandidate = { type: string, candidate?: string, sdpMid?: string, sdpMLineIndex?: number }
const configuration = { 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }] }
type UserInfo = { userPub: string, appId: string }
export default class webRTC {
private log = getLogger({ component: 'webRTC' })
private connections: Record<string, RTCPeerConnection> = {}
private _nostrSend: NostrSend
attachNostrSend(f: NostrSend) {
this._nostrSend = f
}
private nostrSend: NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
if (!this._nostrSend) {
throw new Error("No nostrSend attached")
}
this._nostrSend(initiator, data, relays)
}
private sendCandidate = (u: UserInfo, candidate: string) => {
const message: Types.WebRtcCandidate & { requestId: string, status: 'OK' } = { candidate, requestId: "SubToWebRtcCandidates", status: 'OK' }
this.nostrSend({ type: 'app', appId: u.appId }, { type: 'content', content: JSON.stringify(message), pub: u.userPub })
}
OnMessage = async (u: UserInfo, message: Types.WebRtcMessage_message): Promise<Types.WebRtcAnswer> => {
if (message.type === Types.WebRtcMessage_message_type.OFFER) {
return this.connect(u, message.offer)
} else if (message.type === Types.WebRtcMessage_message_type.CANDIDATE) {
return this.onCandidate(u, message.candidate)
}
return {}
}
private onCandidate = async (u: UserInfo, candidate: string): Promise<Types.WebRtcAnswer> => {
const key = this.getConnectionsKey(u)
if (!this.connections[key]) {
throw new Error('Connection not found')
}
const conn = this.connections[key]
const iceCandidate: IceCandidate = JSON.parse(candidate)
if (!iceCandidate.candidate) {
await conn.addIceCandidate(null);
} else {
await conn.addIceCandidate(iceCandidate);
}
return {}
}
private connect = async (u: UserInfo, offer: string): Promise<Types.WebRtcAnswer> => {
const key = this.getConnectionsKey(u)
if (this.connections[key]) {
throw new Error('Connection already exists')
}
const conn = new RTCPeerConnection(configuration)
conn.onicecandidate = (event) => {
const message: IceCandidate = {
type: 'candidate'
};
if (event.candidate) {
message.candidate = event.candidate.candidate;
message.sdpMid = event.candidate.sdpMid || undefined;
message.sdpMLineIndex = event.candidate.sdpMLineIndex || undefined;
}
this.sendCandidate(u, JSON.stringify(message))
}
conn.onconnectionstatechange = (event) => {
console.log('onconnectionstatechange', event)
}
conn.ondatachannel = (event) => {
console.log('ondatachannel', event)
}
conn.oniceconnectionstatechange = (event) => {
console.log('oniceconnectionstatechange', event)
}
conn.onicegatheringstatechange = (event) => {
console.log('onicegatheringstatechange', event)
}
await conn.setRemoteDescription(JSON.parse(offer))
const answer = await conn.createAnswer()
await conn.setLocalDescription(answer)
this.connections[key] = conn
return { answer: JSON.stringify(answer) }
}
getConnectionsKey = (u: UserInfo) => {
return u.appId + ":" + u.userPub
}
}