commit
884ff0ed3e
22 changed files with 567 additions and 16 deletions
38
package-lock.json
generated
38
package-lock.json
generated
|
|
@ -47,6 +47,7 @@
|
|||
"websocket": "^1.0.34",
|
||||
"websocket-polyfill": "^0.0.3",
|
||||
"why-is-node-running": "^3.2.0",
|
||||
"wrtc": "^0.4.7",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -1882,6 +1883,24 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/domexception": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
|
||||
"integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
|
||||
"deprecated": "Use your platform's native DOMException instead",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"webidl-conversions": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/domexception/node_modules/webidl-conversions": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
|
|
@ -5742,6 +5761,25 @@
|
|||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/wrtc": {
|
||||
"version": "0.4.7",
|
||||
"resolved": "https://registry.npmjs.org/wrtc/-/wrtc-0.4.7.tgz",
|
||||
"integrity": "sha512-P6Hn7VT4lfSH49HxLHcHhDq+aFf/jd9dPY7lDHeFhZ22N3858EKuwm2jmnlPzpsRGEPaoF6XwkcxY5SYnt4f/g==",
|
||||
"bundleDependencies": [
|
||||
"node-pre-gyp"
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"node-pre-gyp": "^0.13.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8.11.2 || >=10.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"domexception": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@
|
|||
"websocket": "^1.0.34",
|
||||
"websocket-polyfill": "^0.0.3",
|
||||
"why-is-node-running": "^3.2.0",
|
||||
"wrtc": "^0.4.7",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -250,6 +250,16 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [DebitResponse](#DebitResponse)
|
||||
- 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
|
||||
- auth type: __User__
|
||||
- input: [CallbackUrl](#CallbackUrl)
|
||||
|
|
@ -298,6 +308,7 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
|
||||
- __Metrics__:
|
||||
- expected context content
|
||||
- __app_id__: _string_
|
||||
- __operator_id__: _string_
|
||||
|
||||
- __User__:
|
||||
|
|
@ -808,6 +819,20 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [SetMockInvoiceAsPaidRequest](#SetMockInvoiceAsPaidRequest)
|
||||
- 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
|
||||
- auth type: __User__
|
||||
- http method: __post__
|
||||
|
|
@ -1318,6 +1343,7 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __app_id__: _string_
|
||||
- __metrics_name__: _string_
|
||||
- __page__: _number_
|
||||
- __request_id__: _number_ *this field is optional
|
||||
|
||||
### UpdateChannelPolicyRequest
|
||||
- __policy__: _[ChannelPolicy](#ChannelPolicy)_
|
||||
|
|
@ -1391,6 +1417,15 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __negative_balance__: _number_
|
||||
- __no_balance__: _number_
|
||||
- __total__: _number_
|
||||
|
||||
### WebRtcAnswer
|
||||
- __answer__: _string_ *this field is optional
|
||||
|
||||
### WebRtcCandidate
|
||||
- __candidate__: _string_
|
||||
|
||||
### WebRtcMessage
|
||||
- __message__: _[WebRtcMessage_message](#WebRtcMessage_message)_
|
||||
## Enums
|
||||
### The enumerators used in the messages
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ type Client struct {
|
|||
SetMockAppBalance func(req SetMockAppBalanceRequest) error
|
||||
SetMockAppUserBalance func(req SetMockAppUserBalanceRequest) error
|
||||
SetMockInvoiceAsPaid func(req SetMockInvoiceAsPaidRequest) error
|
||||
SubToWebRtcCandidates func() (*WebRtcCandidate, error)
|
||||
SubmitWebRtcMessage func(req WebRtcMessage) (*WebRtcAnswer, error)
|
||||
UpdateCallbackUrl func(req CallbackUrl) (*CallbackUrl, error)
|
||||
UpdateChannelPolicy func(req UpdateChannelPolicyRequest) error
|
||||
UpdateUserOffer func(req OfferConfig) error
|
||||
|
|
@ -1863,6 +1865,36 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
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) {
|
||||
auth, err := params.RetrieveUserAuth()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ type GuestWithPubContext struct {
|
|||
Pub string `json:"pub"`
|
||||
}
|
||||
type MetricsContext struct {
|
||||
App_id string `json:"app_id"`
|
||||
Operator_id string `json:"operator_id"`
|
||||
}
|
||||
type UserContext struct {
|
||||
|
|
@ -558,6 +559,7 @@ type SingleUsageMetricReq struct {
|
|||
App_id string `json:"app_id"`
|
||||
Metrics_name string `json:"metrics_name"`
|
||||
Page int64 `json:"page"`
|
||||
Request_id int64 `json:"request_id"`
|
||||
}
|
||||
type UpdateChannelPolicyRequest struct {
|
||||
Policy *ChannelPolicy `json:"policy"`
|
||||
|
|
@ -632,6 +634,15 @@ type UsersInfo struct {
|
|||
No_balance int64 `json:"no_balance"`
|
||||
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
|
||||
|
||||
const (
|
||||
|
|
@ -696,3 +707,15 @@ type UpdateChannelPolicyRequest_update struct {
|
|||
All *Empty `json:"all"`
|
||||
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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1742,6 +1742,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
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.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')
|
||||
app.post('/api/user/cb/update', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'UpdateCallbackUrl', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
|
|||
|
|
@ -891,6 +891,21 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
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)> => {
|
||||
const auth = await params.retrieveUserAuth()
|
||||
if (auth === null) throw new Error('retrieveUserAuth() returned null')
|
||||
|
|
|
|||
|
|
@ -680,6 +680,36 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
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)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
|
|
|
|||
|
|
@ -1075,6 +1075,35 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
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 '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':
|
||||
try {
|
||||
if (!methods.UpdateCallbackUrl) throw new Error('method: UpdateCallbackUrl is not implemented')
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@ export type GuestWithPubContext = {
|
|||
export type GuestWithPubMethodInputs = LinkNPubThroughToken_Input | UseInviteLink_Input
|
||||
export type GuestWithPubMethodOutputs = LinkNPubThroughToken_Output | UseInviteLink_Output
|
||||
export type MetricsContext = {
|
||||
app_id: string
|
||||
operator_id: string
|
||||
}
|
||||
export type MetricsMethodInputs = GetAppsMetrics_Input | GetErrorStats_Input | GetLndMetrics_Input | GetSingleUsageMetrics_Input | GetUsageMetrics_Input
|
||||
export type MetricsMethodOutputs = GetAppsMetrics_Output | GetErrorStats_Output | GetLndMetrics_Output | GetSingleUsageMetrics_Output | GetUsageMetrics_Output
|
||||
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 | SubmitWebRtcMessage_Output
|
||||
export type UserContext = {
|
||||
app_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_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_Output = ResultError | ({ status: 'OK' } & CallbackUrl)
|
||||
|
||||
|
|
@ -351,6 +358,8 @@ export type ServerMethods = {
|
|||
SetMockAppBalance?: (req: SetMockAppBalance_Input & {ctx: AppContext }) => Promise<void>
|
||||
SetMockAppUserBalance?: (req: SetMockAppUserBalance_Input & {ctx: AppContext }) => 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>
|
||||
UpdateChannelPolicy?: (req: UpdateChannelPolicy_Input & {ctx: AdminContext }) => Promise<void>
|
||||
UpdateUserOffer?: (req: UpdateUserOffer_Input & {ctx: UserContext }) => Promise<void>
|
||||
|
|
@ -3182,13 +3191,16 @@ export type SingleUsageMetricReq = {
|
|||
app_id: string
|
||||
metrics_name: string
|
||||
page: number
|
||||
request_id?: number
|
||||
}
|
||||
export const SingleUsageMetricReqOptionalFields: [] = []
|
||||
export type SingleUsageMetricReqOptionalField = 'request_id'
|
||||
export const SingleUsageMetricReqOptionalFields: SingleUsageMetricReqOptionalField[] = ['request_id']
|
||||
export type SingleUsageMetricReqOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
checkOptionalsAreSet?: SingleUsageMetricReqOptionalField[]
|
||||
app_id_CustomCheck?: (v: string) => boolean
|
||||
metrics_name_CustomCheck?: (v: string) => boolean
|
||||
page_CustomCheck?: (v: number) => boolean
|
||||
request_id_CustomCheck?: (v?: number) => boolean
|
||||
}
|
||||
export const SingleUsageMetricReqValidate = (o?: SingleUsageMetricReq, opts: SingleUsageMetricReqOptions = {}, path: string = 'SingleUsageMetricReq::root.'): Error | null => {
|
||||
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
|
||||
|
|
@ -3203,6 +3215,9 @@ export const SingleUsageMetricReqValidate = (o?: SingleUsageMetricReq, opts: Sin
|
|||
if (typeof o.page !== 'number') return new Error(`${path}.page: is not a number`)
|
||||
if (opts.page_CustomCheck && !opts.page_CustomCheck(o.page)) return new Error(`${path}.page: custom check failed`)
|
||||
|
||||
if ((o.request_id || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('request_id')) && typeof o.request_id !== 'number') return new Error(`${path}.request_id: is not a number`)
|
||||
if (opts.request_id_CustomCheck && !opts.request_id_CustomCheck(o.request_id)) return new Error(`${path}.request_id: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
@ -3627,6 +3642,62 @@ export const UsersInfoValidate = (o?: UsersInfo, opts: UsersInfoOptions = {}, pa
|
|||
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 {
|
||||
DENIED = 'denied',
|
||||
INVOICE = 'invoice',
|
||||
|
|
@ -3825,3 +3896,38 @@ export const UpdateChannelPolicyRequest_updateValidate = (o?: UpdateChannelPolic
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,10 +41,13 @@ option (file_options) = {
|
|||
id: "metrics",
|
||||
name: "Metrics",
|
||||
//encrypted:true,
|
||||
context:{
|
||||
context:[{
|
||||
key:"operator_id",
|
||||
value:"string"
|
||||
}
|
||||
},{
|
||||
key:"app_id",
|
||||
value:"string"
|
||||
}]
|
||||
},
|
||||
{
|
||||
id:"app",
|
||||
|
|
@ -204,6 +207,18 @@ service LightningPub {
|
|||
option (http_route) = "/api/reports/lnd";
|
||||
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) {
|
||||
option (auth_type) = "Admin";
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ message SingleUsageMetricReq {
|
|||
string app_id = 1;
|
||||
string metrics_name = 2;
|
||||
int64 page = 3;
|
||||
optional int64 request_id = 4;
|
||||
}
|
||||
|
||||
message UsageMetric {
|
||||
|
|
@ -206,6 +207,21 @@ message LndChannels {
|
|||
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{
|
||||
|
|
|
|||
|
|
@ -67,6 +67,6 @@ const metricsAuth = async (header: string | undefined): Promise<MetricsContext>
|
|||
if (h !== metricsToken) {
|
||||
throw new Error("metrics token invalid")
|
||||
}
|
||||
return { operator_id: "metrics1" }
|
||||
return { operator_id: "http_operator", app_id: "" }
|
||||
}
|
||||
export default serverOptions
|
||||
|
|
@ -27,7 +27,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
|||
if (!adminNpub) { throw new Error("admin access not configured") }
|
||||
if (pub !== adminNpub) { throw new Error("Metrics unavailable") }
|
||||
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,
|
||||
NostrGuestWithPubAuthGuard: async (appId, pub) => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,25 @@ import { bytesToHex, concatBytes } from '@noble/hashes/utils'
|
|||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
export const utf8Decoder: TextDecoder = new TextDecoder('utf-8')
|
||||
export const utf8Encoder: TextEncoder = new TextEncoder()
|
||||
export type DataPacket = { dataId: number, packetNum: number, totalPackets: number, data: Uint8Array }
|
||||
export const encodeTLVDataPacket = (packInfo: DataPacket): TLV => {
|
||||
const { data, dataId, packetNum, totalPackets } = packInfo
|
||||
const tlv: TLV = {}
|
||||
tlv[2] = [integerToUint8Array(dataId)]
|
||||
tlv[3] = [integerToUint8Array(packetNum)]
|
||||
tlv[4] = [integerToUint8Array(totalPackets)]
|
||||
tlv[5] = [data]
|
||||
return tlv
|
||||
}
|
||||
|
||||
export const decodeTLVDataPacket = (tlv: TLV): DataPacket => {
|
||||
return {
|
||||
dataId: parseInt(bytesToHex(tlv[2][0]), 16),
|
||||
packetNum: parseInt(bytesToHex(tlv[3][0]), 16),
|
||||
totalPackets: parseInt(bytesToHex(tlv[4][0]), 16),
|
||||
data: tlv[5][0]
|
||||
}
|
||||
}
|
||||
|
||||
export const encodeListTLV = (list: Uint8Array[]): TLV => {
|
||||
const tlv: TLV = {}
|
||||
|
|
@ -57,12 +76,14 @@ export const integerToUint8Array = (number: number): Uint8Array => {
|
|||
}
|
||||
|
||||
export type TLV = { [t: number]: Uint8Array[] }
|
||||
export const parseTLV = (data: Uint8Array): TLV => {
|
||||
export type TLbV = { [t: number]: Uint8Array[] }
|
||||
export const parseTLV = (data: Uint8Array, log = false): TLV => {
|
||||
const result: TLV = {}
|
||||
let rest = data
|
||||
while (rest.length > 0) {
|
||||
const t = rest[0]
|
||||
const l = rest[1]
|
||||
if (log) console.log({ t, l })
|
||||
const v = rest.slice(2, 2 + l)
|
||||
rest = rest.slice(2 + l)
|
||||
if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`)
|
||||
|
|
@ -78,6 +99,7 @@ export const encodeTLV = (tlv: TLV): Uint8Array => {
|
|||
.reverse()
|
||||
.forEach(([t, vs]) => {
|
||||
vs.forEach(v => {
|
||||
if (v.length > 255) throw new Error(`value too long to encode in TLV ${t}`)
|
||||
const entry = new Uint8Array(v.length + 2)
|
||||
entry.set([parseInt(t)], 0)
|
||||
entry.set([v.length], 1)
|
||||
|
|
@ -88,3 +110,34 @@ export const encodeTLV = (tlv: TLV): Uint8Array => {
|
|||
|
||||
return concatBytes(...entries)
|
||||
}
|
||||
export const parseTLbV = (data: Uint8Array, log = false): TLV => {
|
||||
const result: TLV = {}
|
||||
let rest = data
|
||||
while (rest.length > 0) {
|
||||
const t = rest[0]
|
||||
const l = parseInt(bytesToHex(rest.slice(1, 5)), 16)
|
||||
if (log) console.log({ t, l })
|
||||
const v = rest.slice(5, 5 + l)
|
||||
rest = rest.slice(5 + l)
|
||||
if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`)
|
||||
result[t] = result[t] || []
|
||||
result[t].push(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const encodeTLbV = (tlv: TLV): Uint8Array => {
|
||||
const entries: Uint8Array[] = []
|
||||
Object.entries(tlv)
|
||||
.reverse()
|
||||
.forEach(([t, vs]) => {
|
||||
vs.forEach(v => {
|
||||
const entry = new Uint8Array(v.length + 5)
|
||||
entry.set([parseInt(t)], 0)
|
||||
entry.set(integerToUint8Array(v.length), 1)
|
||||
entry.set(v, 5)
|
||||
entries.push(entry)
|
||||
})
|
||||
})
|
||||
return concatBytes(...entries)
|
||||
}
|
||||
|
|
@ -53,7 +53,6 @@ export default class {
|
|||
const user = await this.storage.userStorage.GetUser(ctx.user_id)
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||
const appUser = await this.storage.applicationStorage.GetAppUserFromUser(app, user.user_id)
|
||||
console.log("User Identifier/pointer here", appUser?.identifier)
|
||||
|
||||
if (!appUser) {
|
||||
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
|
||||
|
|
|
|||
|
|
@ -194,9 +194,7 @@ export class DebitManager {
|
|||
if (!this._nostrSend) {
|
||||
throw new Error("No nostrSend attached")
|
||||
}
|
||||
console.log({ pointerdata, event })
|
||||
const res = await this.payNdebitInvoice(event, pointerdata)
|
||||
console.log({ debitRes: res })
|
||||
if (res.status === 'fail' || res.status === 'authOk') {
|
||||
const e = newNdebitResponse(JSON.stringify(res.debitRes), event)
|
||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
|
|||
import { DebitManager } from "./debitManager.js"
|
||||
import { NofferData } from "nostr-tools/lib/types/nip69.js"
|
||||
import { OfferManager } from "./offerManager.js"
|
||||
import webRTC from "../webRTC/index.js"
|
||||
|
||||
type UserOperationsSub = {
|
||||
id: string
|
||||
|
|
@ -54,6 +55,7 @@ export default class {
|
|||
utils: Utils
|
||||
rugPullTracker: RugPullTracker
|
||||
unlocker: Unlocker
|
||||
webRTC: webRTC
|
||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||
constructor(settings: MainSettings, storage: Storage, adminManager: AdminManager, utils: Utils, unlocker: Unlocker) {
|
||||
this.settings = settings
|
||||
|
|
@ -74,6 +76,7 @@ export default class {
|
|||
this.appUserManager = new AppUserManager(this.storage, this.settings, 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.webRTC = new webRTC(this.storage)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +97,7 @@ export default class {
|
|||
this.liquidityProvider.attachNostrSend(f)
|
||||
this.debitManager.attachNostrSend(f)
|
||||
this.offerManager.attachNostrSend(f)
|
||||
this.webRTC.attachNostrSend(f)
|
||||
}
|
||||
|
||||
htlcCb: HtlcCb = (e) => {
|
||||
|
|
|
|||
|
|
@ -212,7 +212,6 @@ export default class Handler {
|
|||
tags: [['p', data.pub]],
|
||||
}
|
||||
} else {
|
||||
console.log(data)
|
||||
toSign = data.event
|
||||
if (data.encrypt) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,16 @@ import main from '../main/index.js'
|
|||
import Main from '../main/index.js'
|
||||
export default (mainHandler: Main): Types.ServerMethods => {
|
||||
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 }) => {
|
||||
return mainHandler.metricsManager.GetUsageMetrics(req)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -132,12 +132,15 @@ export default class {
|
|||
return metrics
|
||||
}
|
||||
|
||||
LoadMetricsFile = async (app: string, method: string, chunk: number): Promise<Types.UsageMetricTlv> => {
|
||||
LoadRawMetricsFile = async (app: string, method: string, chunk: number): Promise<Buffer> => {
|
||||
if (!this.metaReady || !this.metricsMeta[app] || !this.metricsMeta[app][method] || !this.metricsMeta[app][method].chunks.includes(chunk)) {
|
||||
throw new Error("metrics not found")
|
||||
}
|
||||
const fullPath = [this.metricsPath, app, method, `${chunk}.mtlv`].join("/")
|
||||
const tlv = fs.readFileSync(fullPath)
|
||||
return fs.readFileSync(fullPath)
|
||||
}
|
||||
LoadMetricsFile = async (app: string, method: string, chunk: number): Promise<Types.UsageMetricTlv> => {
|
||||
const tlv = await this.LoadRawMetricsFile(app, method, chunk)
|
||||
const decoded = decodeListTLV(parseTLV(tlv))
|
||||
return {
|
||||
base_64_tlvs: decoded.map(d => Buffer.from(d).toString('base64')),
|
||||
|
|
|
|||
123
src/services/webRTC/index.ts
Normal file
123
src/services/webRTC/index.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
//@ts-ignore
|
||||
import wrtc from 'wrtc'
|
||||
import Storage from '../storage/index.js'
|
||||
import { ERROR, getLogger } from "../helpers/logger.js"
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { NostrSend, SendData, SendInitiator } from "../nostr/handler.js"
|
||||
import { encodeTLbV, encodeTLV, encodeTLVDataPacket } from '../helpers/tlv.js'
|
||||
type IceCandidate = { type: string, candidate?: string, sdpMid?: string, sdpMLineIndex?: number }
|
||||
const configuration = { 'iceServers': [{ 'urls': 'stun:relay.webwormhole.io' }] }
|
||||
type UserInfo = { userPub: string, appId: string }
|
||||
export default class webRTC {
|
||||
private storage: Storage
|
||||
private log = getLogger({ component: 'webRTC' })
|
||||
private connections: Record<string, RTCPeerConnection> = {}
|
||||
private _nostrSend: NostrSend
|
||||
constructor(storage: Storage) {
|
||||
this.storage = storage
|
||||
}
|
||||
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(undefined);
|
||||
} else {
|
||||
await conn.addIceCandidate(iceCandidate);
|
||||
}
|
||||
return {}
|
||||
}
|
||||
private connect = async (u: UserInfo, offer: string): Promise<Types.WebRtcAnswer> => {
|
||||
const key = this.getConnectionsKey(u)
|
||||
this.log("connect", key)
|
||||
if (this.connections[key]) {
|
||||
this.connections[key].close()
|
||||
}
|
||||
const conn = new wrtc.RTCPeerConnection(configuration) as RTCPeerConnection
|
||||
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 = () => {
|
||||
if (conn.connectionState === 'disconnected') {
|
||||
conn.close()
|
||||
delete this.connections[key]
|
||||
}
|
||||
}
|
||||
conn.ondatachannel = (event) => {
|
||||
const channel = event.channel
|
||||
channel.addEventListener('message', async (event) => {
|
||||
try {
|
||||
const j = JSON.parse(event.data) as Types.SingleUsageMetricReq
|
||||
const err = Types.SingleUsageMetricReqValidate(j, {
|
||||
app_id_CustomCheck: id => id === u.appId,
|
||||
metrics_name_CustomCheck: name => name !== ""
|
||||
})
|
||||
if (err) {
|
||||
this.log(ERROR, 'SingleUsageMetricReqValidate', err)
|
||||
return
|
||||
}
|
||||
const res = await this.storage.metricsEventStorage.LoadRawMetricsFile(j.app_id, j.metrics_name, j.page)
|
||||
const id = j.request_id || Math.floor(Math.random() * 100_000_000)
|
||||
let i = 0
|
||||
const packets: Buffer[] = []
|
||||
while (i < res.length) {
|
||||
const chunk = res.slice(i, Math.min(i + 15_000, res.length))
|
||||
packets.push(chunk)
|
||||
i += 15_000
|
||||
}
|
||||
for (let i = 0; i < packets.length; i++) {
|
||||
const packet = packets[i]
|
||||
const tlv = encodeTLVDataPacket({ dataId: id, packetNum: i + 1, totalPackets: packets.length, data: packet })
|
||||
const bytes = encodeTLbV(tlv)
|
||||
channel.send(bytes)
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.log(ERROR, 'ondatachannel', e.message || e)
|
||||
}
|
||||
})
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue