@undefineds.co/xpod 0.3.67 → 0.3.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/components-ignore.json +1 -0
- package/dist/api/ApiServer.d.ts +9 -1
- package/dist/api/ApiServer.js +22 -1
- package/dist/api/ApiServer.js.map +1 -1
- package/dist/api/container/routes.js +13 -0
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/handlers/DeviceNotificationRuntime.d.ts +26 -0
- package/dist/api/handlers/DeviceNotificationRuntime.js +77 -0
- package/dist/api/handlers/DeviceNotificationRuntime.js.map +1 -0
- package/dist/api/handlers/DeviceNotificationTicketHandler.d.ts +50 -0
- package/dist/api/handlers/DeviceNotificationTicketHandler.js +130 -0
- package/dist/api/handlers/DeviceNotificationTicketHandler.js.map +1 -0
- package/dist/api/handlers/DeviceNotificationTicketHandler.jsonld +117 -0
- package/dist/api/runs/InngestRunExecutionBackend.d.ts +2 -2
- package/dist/api/tasks/InngestTaskScheduler.d.ts +4 -4
- package/dist/components/components.jsonld +6 -1
- package/dist/components/context.jsonld +122 -0
- package/dist/http/DeviceNotificationWebSocketServer.d.ts +29 -0
- package/dist/http/DeviceNotificationWebSocketServer.js +138 -0
- package/dist/http/DeviceNotificationWebSocketServer.js.map +1 -0
- package/dist/http/DeviceNotificationWebSocketServer.jsonld +148 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/notifications/DeviceNotificationHub.d.ts +74 -0
- package/dist/notifications/DeviceNotificationHub.js +313 -0
- package/dist/notifications/DeviceNotificationHub.js.map +1 -0
- package/dist/notifications/DeviceNotificationHub.jsonld +258 -0
- package/dist/notifications/DeviceNotificationResourceListener.d.ts +21 -0
- package/dist/notifications/DeviceNotificationResourceListener.js +38 -0
- package/dist/notifications/DeviceNotificationResourceListener.js.map +1 -0
- package/dist/notifications/DeviceNotificationResourceListener.jsonld +98 -0
- package/dist/notifications/device-notification-protocol.d.ts +56 -0
- package/dist/notifications/device-notification-protocol.js +127 -0
- package/dist/notifications/device-notification-protocol.js.map +1 -0
- package/dist/notifications/device-notification-protocol.jsonld +29 -0
- package/dist/notifications/index.d.ts +3 -0
- package/dist/notifications/index.js +20 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/storage/ObservableResourceStore.d.ts +3 -0
- package/dist/storage/ObservableResourceStore.js +17 -1
- package/dist/storage/ObservableResourceStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeviceNotificationTicketHandler = exports.DeviceNotificationTicketStore = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
class DeviceNotificationTicketStore {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.records = new Map();
|
|
8
|
+
}
|
|
9
|
+
mint(input) {
|
|
10
|
+
const ticket = (0, node_crypto_1.randomBytes)(32).toString('base64url');
|
|
11
|
+
this.records.set(this.digest(ticket), {
|
|
12
|
+
digest: this.digest(ticket),
|
|
13
|
+
identity: input.identity,
|
|
14
|
+
deviceSessionId: input.deviceSessionId,
|
|
15
|
+
origin: new URL(input.origin).origin,
|
|
16
|
+
expiresAt: Date.now() + input.ttlMs,
|
|
17
|
+
});
|
|
18
|
+
return ticket;
|
|
19
|
+
}
|
|
20
|
+
consume(ticket) {
|
|
21
|
+
const digest = this.digest(ticket);
|
|
22
|
+
const record = this.records.get(digest);
|
|
23
|
+
this.records.delete(digest);
|
|
24
|
+
if (!record || record.expiresAt < Date.now()) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return record;
|
|
28
|
+
}
|
|
29
|
+
size() {
|
|
30
|
+
return this.records.size;
|
|
31
|
+
}
|
|
32
|
+
clear() {
|
|
33
|
+
this.records.clear();
|
|
34
|
+
}
|
|
35
|
+
digest(ticket) {
|
|
36
|
+
return (0, node_crypto_1.createHash)('sha256').update(ticket).digest('hex');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.DeviceNotificationTicketStore = DeviceNotificationTicketStore;
|
|
40
|
+
class DeviceNotificationTicketHandler {
|
|
41
|
+
constructor(options) {
|
|
42
|
+
this.webSocketEndpoint = options.webSocketEndpoint;
|
|
43
|
+
this.origin = new URL(options.origin).origin;
|
|
44
|
+
this.ticketTtlMs = options.ticketTtlMs ?? 60_000;
|
|
45
|
+
this.ticketStore = options.ticketStore ?? new DeviceNotificationTicketStore();
|
|
46
|
+
}
|
|
47
|
+
async handle(request, response) {
|
|
48
|
+
const identity = this.extractIdentity(request);
|
|
49
|
+
if (!identity) {
|
|
50
|
+
this.sendJson(response, 401, { error: 'Unauthorized' });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const body = await this.readJson(request);
|
|
54
|
+
const deviceSessionId = typeof body.deviceSessionId === 'string'
|
|
55
|
+
? body.deviceSessionId
|
|
56
|
+
: typeof body.sessionId === 'string'
|
|
57
|
+
? body.sessionId
|
|
58
|
+
: undefined;
|
|
59
|
+
const origin = typeof body.origin === 'string' ? body.origin : undefined;
|
|
60
|
+
if (!deviceSessionId || !origin) {
|
|
61
|
+
this.sendJson(response, 400, { error: 'deviceSessionId and origin are required' });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
if (new URL(origin).origin !== this.origin) {
|
|
66
|
+
this.sendJson(response, 400, { error: 'origin must match the notification service origin' });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
this.sendJson(response, 400, { error: 'origin must be a valid URL' });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const ticket = this.ticketStore.mint({
|
|
75
|
+
identity,
|
|
76
|
+
deviceSessionId,
|
|
77
|
+
origin: this.origin,
|
|
78
|
+
ttlMs: this.ticketTtlMs,
|
|
79
|
+
});
|
|
80
|
+
this.sendJson(response, 201, {
|
|
81
|
+
protocol: 'xpod.notifications.v1',
|
|
82
|
+
ticket,
|
|
83
|
+
webSocketEndpoint: this.webSocketEndpoint,
|
|
84
|
+
expiresInMs: this.ticketTtlMs,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
extractIdentity(request) {
|
|
88
|
+
const auth = request.auth;
|
|
89
|
+
const webId = auth && 'webId' in auth && typeof auth.webId === 'string' ? auth.webId : undefined;
|
|
90
|
+
if (!webId) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
webId,
|
|
95
|
+
localPart: auth && 'localPart' in auth && typeof auth.localPart === 'string'
|
|
96
|
+
? auth.localPart
|
|
97
|
+
: deriveLocalPart(webId),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async readJson(request) {
|
|
101
|
+
const chunks = [];
|
|
102
|
+
await new Promise((resolve, reject) => {
|
|
103
|
+
request.on('data', (chunk) => chunks.push(chunk));
|
|
104
|
+
request.on('end', () => resolve());
|
|
105
|
+
request.on('error', reject);
|
|
106
|
+
});
|
|
107
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
108
|
+
return body.length > 0 ? JSON.parse(body) : {};
|
|
109
|
+
}
|
|
110
|
+
sendJson(response, status, body) {
|
|
111
|
+
const encoded = JSON.stringify(body);
|
|
112
|
+
response.writeHead(status, {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
'Content-Length': Buffer.byteLength(encoded),
|
|
115
|
+
});
|
|
116
|
+
response.end(encoded);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.DeviceNotificationTicketHandler = DeviceNotificationTicketHandler;
|
|
120
|
+
function deriveLocalPart(webId) {
|
|
121
|
+
try {
|
|
122
|
+
const url = new URL(webId);
|
|
123
|
+
const parts = url.pathname.split('/').filter(Boolean);
|
|
124
|
+
return parts[0] ?? 'unknown';
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return 'unknown';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=DeviceNotificationTicketHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeviceNotificationTicketHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/DeviceNotificationTicketHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { AuthContext } from '../auth/AuthContext';\n\nexport interface DeviceNotificationIdentity {\n webId: string;\n localPart: string;\n}\n\nexport interface DeviceNotificationTicketRecord {\n digest: string;\n identity: DeviceNotificationIdentity;\n deviceSessionId: string;\n origin: string;\n expiresAt: number;\n}\n\nexport class DeviceNotificationTicketStore {\n private readonly records = new Map<string, DeviceNotificationTicketRecord>();\n\n public mint(input: {\n identity: DeviceNotificationIdentity;\n deviceSessionId: string;\n origin: string;\n ttlMs: number;\n }): string {\n const ticket = randomBytes(32).toString('base64url');\n this.records.set(this.digest(ticket), {\n digest: this.digest(ticket),\n identity: input.identity,\n deviceSessionId: input.deviceSessionId,\n origin: new URL(input.origin).origin,\n expiresAt: Date.now() + input.ttlMs,\n });\n return ticket;\n }\n\n public consume(ticket: string): DeviceNotificationTicketRecord | undefined {\n const digest = this.digest(ticket);\n const record = this.records.get(digest);\n this.records.delete(digest);\n if (!record || record.expiresAt < Date.now()) {\n return undefined;\n }\n return record;\n }\n\n public size(): number {\n return this.records.size;\n }\n\n public clear(): void {\n this.records.clear();\n }\n\n private digest(ticket: string): string {\n return createHash('sha256').update(ticket).digest('hex');\n }\n}\n\nexport interface DeviceNotificationTicketHandlerOptions {\n webSocketEndpoint: string;\n origin: string;\n ticketTtlMs?: number;\n ticketStore?: DeviceNotificationTicketStore;\n}\n\ntype TicketRequest = IncomingMessage & {\n auth?: AuthContext | { webId?: string; localPart?: string };\n};\n\nexport class DeviceNotificationTicketHandler {\n public readonly ticketStore: DeviceNotificationTicketStore;\n private readonly webSocketEndpoint: string;\n private readonly origin: string;\n private readonly ticketTtlMs: number;\n\n public constructor(options: DeviceNotificationTicketHandlerOptions) {\n this.webSocketEndpoint = options.webSocketEndpoint;\n this.origin = new URL(options.origin).origin;\n this.ticketTtlMs = options.ticketTtlMs ?? 60_000;\n this.ticketStore = options.ticketStore ?? new DeviceNotificationTicketStore();\n }\n\n public async handle(request: TicketRequest, response: ServerResponse): Promise<void> {\n const identity = this.extractIdentity(request);\n if (!identity) {\n this.sendJson(response, 401, { error: 'Unauthorized' });\n return;\n }\n const body = await this.readJson(request);\n const deviceSessionId = typeof body.deviceSessionId === 'string'\n ? body.deviceSessionId\n : typeof body.sessionId === 'string'\n ? body.sessionId\n : undefined;\n const origin = typeof body.origin === 'string' ? body.origin : undefined;\n if (!deviceSessionId || !origin) {\n this.sendJson(response, 400, { error: 'deviceSessionId and origin are required' });\n return;\n }\n try {\n if (new URL(origin).origin !== this.origin) {\n this.sendJson(response, 400, { error: 'origin must match the notification service origin' });\n return;\n }\n } catch {\n this.sendJson(response, 400, { error: 'origin must be a valid URL' });\n return;\n }\n const ticket = this.ticketStore.mint({\n identity,\n deviceSessionId,\n origin: this.origin,\n ttlMs: this.ticketTtlMs,\n });\n this.sendJson(response, 201, {\n protocol: 'xpod.notifications.v1',\n ticket,\n webSocketEndpoint: this.webSocketEndpoint,\n expiresInMs: this.ticketTtlMs,\n });\n }\n\n private extractIdentity(request: TicketRequest): DeviceNotificationIdentity | undefined {\n const auth = request.auth;\n const webId = auth && 'webId' in auth && typeof auth.webId === 'string' ? auth.webId : undefined;\n if (!webId) {\n return undefined;\n }\n return {\n webId,\n localPart: auth && 'localPart' in auth && typeof auth.localPart === 'string'\n ? auth.localPart\n : deriveLocalPart(webId),\n };\n }\n\n private async readJson(request: IncomingMessage): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n await new Promise<void>((resolve, reject) => {\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => resolve());\n request.on('error', reject);\n });\n const body = Buffer.concat(chunks).toString('utf8');\n return body.length > 0 ? JSON.parse(body) : {};\n }\n\n private sendJson(response: ServerResponse, status: number, body: unknown): void {\n const encoded = JSON.stringify(body);\n response.writeHead(status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(encoded),\n });\n response.end(encoded);\n }\n}\n\nfunction deriveLocalPart(webId: string): string {\n try {\n const url = new URL(webId);\n const parts = url.pathname.split('/').filter(Boolean);\n return parts[0] ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n"]}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": [
|
|
3
|
+
"https://linkedsoftwaredependencies.org/bundles/npm/@undefineds.co/xpod/^0.0.0/components/context.jsonld"
|
|
4
|
+
],
|
|
5
|
+
"@id": "npmd:@undefineds.co/xpod",
|
|
6
|
+
"components": [
|
|
7
|
+
{
|
|
8
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler",
|
|
9
|
+
"@type": "Class",
|
|
10
|
+
"requireElement": "DeviceNotificationTicketHandler",
|
|
11
|
+
"parameters": [
|
|
12
|
+
{
|
|
13
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_webSocketEndpoint",
|
|
14
|
+
"range": "xsd:string"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_origin",
|
|
18
|
+
"range": "xsd:string"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_ticketTtlMs",
|
|
22
|
+
"range": {
|
|
23
|
+
"@type": "ParameterRangeUnion",
|
|
24
|
+
"parameterRangeElements": [
|
|
25
|
+
"xsd:number",
|
|
26
|
+
{
|
|
27
|
+
"@type": "ParameterRangeUndefined"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_ticketStore",
|
|
34
|
+
"range": {
|
|
35
|
+
"@type": "ParameterRangeUnion",
|
|
36
|
+
"parameterRangeElements": [
|
|
37
|
+
"undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketStore",
|
|
38
|
+
{
|
|
39
|
+
"@type": "ParameterRangeUndefined"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"memberFields": [
|
|
46
|
+
{
|
|
47
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler__member_ticketStore",
|
|
48
|
+
"memberFieldName": "ticketStore",
|
|
49
|
+
"range": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketStore"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler__member_webSocketEndpoint",
|
|
53
|
+
"memberFieldName": "webSocketEndpoint"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler__member_origin",
|
|
57
|
+
"memberFieldName": "origin"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler__member_ticketTtlMs",
|
|
61
|
+
"memberFieldName": "ticketTtlMs"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler__member_constructor",
|
|
65
|
+
"memberFieldName": "constructor"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler__member_handle",
|
|
69
|
+
"memberFieldName": "handle"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler__member_extractIdentity",
|
|
73
|
+
"memberFieldName": "extractIdentity"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler__member_readJson",
|
|
77
|
+
"memberFieldName": "readJson"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler__member_sendJson",
|
|
81
|
+
"memberFieldName": "sendJson"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"constructorArguments": [
|
|
85
|
+
{
|
|
86
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options__constructorArgument",
|
|
87
|
+
"fields": [
|
|
88
|
+
{
|
|
89
|
+
"keyRaw": "webSocketEndpoint",
|
|
90
|
+
"value": {
|
|
91
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_webSocketEndpoint"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"keyRaw": "origin",
|
|
96
|
+
"value": {
|
|
97
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_origin"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"keyRaw": "ticketTtlMs",
|
|
102
|
+
"value": {
|
|
103
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_ticketTtlMs"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"keyRaw": "ticketStore",
|
|
108
|
+
"value": {
|
|
109
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_ticketStore"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
@@ -156,7 +156,7 @@ export declare class InngestRunExecutionBackend implements RunExecutionBackend {
|
|
|
156
156
|
readonly event: "xpod/run.continue_requested";
|
|
157
157
|
}], Omit<Omit<import("inngest").BaseContext<Inngest.Any>, never> & Record<never, never> & {
|
|
158
158
|
logger: import("inngest").Logger;
|
|
159
|
-
}, "group" | "requestId" | "
|
|
159
|
+
}, "group" | "requestId" | "event" | "attempt" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest").FailureEventArgs<EventPayload<any>> & {
|
|
160
160
|
step: {
|
|
161
161
|
sendEvent: (idOrOptions: import("inngest").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<Inngest<import("inngest").ClientOptions>>>>;
|
|
162
162
|
waitForSignal: <TData>(idOrOptions: import("inngest").StepOptionsOrId, opts: {
|
|
@@ -337,7 +337,7 @@ export declare class InngestRunExecutionBackend implements RunExecutionBackend {
|
|
|
337
337
|
readonly event: "xpod/run.continue_requested";
|
|
338
338
|
}], Omit<Omit<import("inngest").BaseContext<Inngest.Any>, never> & Record<never, never> & {
|
|
339
339
|
logger: import("inngest").Logger;
|
|
340
|
-
}, "group" | "requestId" | "
|
|
340
|
+
}, "group" | "requestId" | "event" | "attempt" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest").FailureEventArgs<EventPayload<any>> & {
|
|
341
341
|
step: {
|
|
342
342
|
sendEvent: (idOrOptions: import("inngest").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<Inngest<import("inngest").ClientOptions>>>>;
|
|
343
343
|
waitForSignal: <TData>(idOrOptions: import("inngest").StepOptionsOrId, opts: {
|
|
@@ -133,7 +133,7 @@ export declare class InngestTaskScheduler<TContext = StoreContext> {
|
|
|
133
133
|
readonly event: "xpod/task.materialize_due";
|
|
134
134
|
}], Omit<Omit<import("inngest/types").BaseContext<import("inngest").Inngest.Any>, never> & Record<never, never> & {
|
|
135
135
|
logger: import("inngest").Logger;
|
|
136
|
-
}, "group" | "requestId" | "
|
|
136
|
+
}, "group" | "requestId" | "event" | "attempt" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
|
|
137
137
|
step: {
|
|
138
138
|
sendEvent: (idOrOptions: import("inngest/types").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<import("inngest").Inngest<import("inngest/types").ClientOptions>>>>;
|
|
139
139
|
waitForSignal: <TData>(idOrOptions: import("inngest/types").StepOptionsOrId, opts: {
|
|
@@ -311,7 +311,7 @@ export declare class InngestTaskScheduler<TContext = StoreContext> {
|
|
|
311
311
|
readonly event: "xpod/task.materialize_due";
|
|
312
312
|
}], Omit<Omit<import("inngest/types").BaseContext<import("inngest").Inngest.Any>, never> & Record<never, never> & {
|
|
313
313
|
logger: import("inngest").Logger;
|
|
314
|
-
}, "group" | "requestId" | "
|
|
314
|
+
}, "group" | "requestId" | "event" | "attempt" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
|
|
315
315
|
step: {
|
|
316
316
|
sendEvent: (idOrOptions: import("inngest/types").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<import("inngest").Inngest<import("inngest/types").ClientOptions>>>>;
|
|
317
317
|
waitForSignal: <TData>(idOrOptions: import("inngest/types").StepOptionsOrId, opts: {
|
|
@@ -494,7 +494,7 @@ export declare class InngestTaskScheduler<TContext = StoreContext> {
|
|
|
494
494
|
readonly event: "xpod/task.event";
|
|
495
495
|
}], Omit<Omit<import("inngest/types").BaseContext<import("inngest").Inngest.Any>, never> & Record<never, never> & {
|
|
496
496
|
logger: import("inngest").Logger;
|
|
497
|
-
}, "group" | "requestId" | "
|
|
497
|
+
}, "group" | "requestId" | "event" | "attempt" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
|
|
498
498
|
step: {
|
|
499
499
|
sendEvent: (idOrOptions: import("inngest/types").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<import("inngest").Inngest<import("inngest/types").ClientOptions>>>>;
|
|
500
500
|
waitForSignal: <TData>(idOrOptions: import("inngest/types").StepOptionsOrId, opts: {
|
|
@@ -670,7 +670,7 @@ export declare class InngestTaskScheduler<TContext = StoreContext> {
|
|
|
670
670
|
readonly event: "xpod/task.event";
|
|
671
671
|
}], Omit<Omit<import("inngest/types").BaseContext<import("inngest").Inngest.Any>, never> & Record<never, never> & {
|
|
672
672
|
logger: import("inngest").Logger;
|
|
673
|
-
}, "group" | "requestId" | "
|
|
673
|
+
}, "group" | "requestId" | "event" | "attempt" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
|
|
674
674
|
step: {
|
|
675
675
|
sendEvent: (idOrOptions: import("inngest/types").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<import("inngest").Inngest<import("inngest/types").ClientOptions>>>>;
|
|
676
676
|
waitForSignal: <TData>(idOrOptions: import("inngest/types").StepOptionsOrId, opts: {
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"undefineds:dist/http/TracingHandler.jsonld",
|
|
45
45
|
"undefineds:dist/http/admin/EdgeNodeCertificateHttpHandler.jsonld",
|
|
46
46
|
"undefineds:dist/http/terminal/TerminalHttpHandler.jsonld",
|
|
47
|
+
"undefineds:dist/notifications/DeviceNotificationHub.jsonld",
|
|
48
|
+
"undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld",
|
|
49
|
+
"undefineds:dist/http/DeviceNotificationWebSocketServer.jsonld",
|
|
50
|
+
"undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld",
|
|
47
51
|
"undefineds:dist/pods/ReservedSuffixIdentifierGenerator.jsonld",
|
|
48
52
|
"undefineds:dist/identity/drizzle/DrizzleIndexedStorage.jsonld",
|
|
49
53
|
"undefineds:dist/identity/ValidatingIdentityProviderHttpHandler.jsonld",
|
|
@@ -128,6 +132,7 @@
|
|
|
128
132
|
"undefineds:dist/edge/reachability/TcpP2PSignalingSession.jsonld",
|
|
129
133
|
"undefineds:dist/edge/reachability/ManagedClientFetch.jsonld",
|
|
130
134
|
"undefineds:dist/edge/reachability/ManagedClientP2PSmoke.jsonld",
|
|
131
|
-
"undefineds:dist/edge/reachability/P2PRealnetAcceptance.jsonld"
|
|
135
|
+
"undefineds:dist/edge/reachability/P2PRealnetAcceptance.jsonld",
|
|
136
|
+
"undefineds:dist/notifications/device-notification-protocol.jsonld"
|
|
132
137
|
]
|
|
133
138
|
}
|
|
@@ -1393,6 +1393,123 @@
|
|
|
1393
1393
|
}
|
|
1394
1394
|
}
|
|
1395
1395
|
},
|
|
1396
|
+
"DeviceNotificationHub": {
|
|
1397
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub",
|
|
1398
|
+
"@prefix": true,
|
|
1399
|
+
"@context": {
|
|
1400
|
+
"options": {
|
|
1401
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub_options"
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
},
|
|
1405
|
+
"DeviceNotificationHubOptions": {
|
|
1406
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHubOptions",
|
|
1407
|
+
"@prefix": true,
|
|
1408
|
+
"@context": {}
|
|
1409
|
+
},
|
|
1410
|
+
"OpenDeviceNotificationConnectionInput": {
|
|
1411
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#OpenDeviceNotificationConnectionInput",
|
|
1412
|
+
"@prefix": true,
|
|
1413
|
+
"@context": {}
|
|
1414
|
+
},
|
|
1415
|
+
"DeviceNotificationConnectionHandle": {
|
|
1416
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationConnectionHandle",
|
|
1417
|
+
"@prefix": true,
|
|
1418
|
+
"@context": {}
|
|
1419
|
+
},
|
|
1420
|
+
"DeviceNotificationPublishInput": {
|
|
1421
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationPublishInput",
|
|
1422
|
+
"@prefix": true,
|
|
1423
|
+
"@context": {}
|
|
1424
|
+
},
|
|
1425
|
+
"DeviceNotificationResourceListener": {
|
|
1426
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener",
|
|
1427
|
+
"@prefix": true,
|
|
1428
|
+
"@context": {
|
|
1429
|
+
"options_origin": {
|
|
1430
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener_options_origin"
|
|
1431
|
+
},
|
|
1432
|
+
"options_hub": {
|
|
1433
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener_options_hub"
|
|
1434
|
+
},
|
|
1435
|
+
"origin": {
|
|
1436
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener_options_origin"
|
|
1437
|
+
},
|
|
1438
|
+
"hub": {
|
|
1439
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener_options_hub"
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
"DeviceNotificationResourcePublisher": {
|
|
1444
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourcePublisher",
|
|
1445
|
+
"@prefix": true,
|
|
1446
|
+
"@context": {}
|
|
1447
|
+
},
|
|
1448
|
+
"DeviceNotificationResourceListenerOptions": {
|
|
1449
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListenerOptions",
|
|
1450
|
+
"@prefix": true,
|
|
1451
|
+
"@context": {}
|
|
1452
|
+
},
|
|
1453
|
+
"DeviceNotificationWebSocketServer": {
|
|
1454
|
+
"@id": "undefineds:dist/http/DeviceNotificationWebSocketServer.jsonld#DeviceNotificationWebSocketServer",
|
|
1455
|
+
"@prefix": true,
|
|
1456
|
+
"@context": {
|
|
1457
|
+
"options_hub": {
|
|
1458
|
+
"@id": "undefineds:dist/http/DeviceNotificationWebSocketServer.jsonld#DeviceNotificationWebSocketServer_options_hub"
|
|
1459
|
+
},
|
|
1460
|
+
"options_ticketStore": {
|
|
1461
|
+
"@id": "undefineds:dist/http/DeviceNotificationWebSocketServer.jsonld#DeviceNotificationWebSocketServer_options_ticketStore"
|
|
1462
|
+
},
|
|
1463
|
+
"options_path": {
|
|
1464
|
+
"@id": "undefineds:dist/http/DeviceNotificationWebSocketServer.jsonld#DeviceNotificationWebSocketServer_options_path"
|
|
1465
|
+
},
|
|
1466
|
+
"options_heartbeatIntervalMs": {
|
|
1467
|
+
"@id": "undefineds:dist/http/DeviceNotificationWebSocketServer.jsonld#DeviceNotificationWebSocketServer_options_heartbeatIntervalMs"
|
|
1468
|
+
},
|
|
1469
|
+
"hub": {
|
|
1470
|
+
"@id": "undefineds:dist/http/DeviceNotificationWebSocketServer.jsonld#DeviceNotificationWebSocketServer_options_hub"
|
|
1471
|
+
},
|
|
1472
|
+
"ticketStore": {
|
|
1473
|
+
"@id": "undefineds:dist/http/DeviceNotificationWebSocketServer.jsonld#DeviceNotificationWebSocketServer_options_ticketStore"
|
|
1474
|
+
},
|
|
1475
|
+
"path": {
|
|
1476
|
+
"@id": "undefineds:dist/http/DeviceNotificationWebSocketServer.jsonld#DeviceNotificationWebSocketServer_options_path"
|
|
1477
|
+
},
|
|
1478
|
+
"heartbeatIntervalMs": {
|
|
1479
|
+
"@id": "undefineds:dist/http/DeviceNotificationWebSocketServer.jsonld#DeviceNotificationWebSocketServer_options_heartbeatIntervalMs"
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
},
|
|
1483
|
+
"DeviceNotificationTicketHandler": {
|
|
1484
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler",
|
|
1485
|
+
"@prefix": true,
|
|
1486
|
+
"@context": {
|
|
1487
|
+
"options_webSocketEndpoint": {
|
|
1488
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_webSocketEndpoint"
|
|
1489
|
+
},
|
|
1490
|
+
"options_origin": {
|
|
1491
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_origin"
|
|
1492
|
+
},
|
|
1493
|
+
"options_ticketTtlMs": {
|
|
1494
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_ticketTtlMs"
|
|
1495
|
+
},
|
|
1496
|
+
"options_ticketStore": {
|
|
1497
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_ticketStore"
|
|
1498
|
+
},
|
|
1499
|
+
"webSocketEndpoint": {
|
|
1500
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_webSocketEndpoint"
|
|
1501
|
+
},
|
|
1502
|
+
"origin": {
|
|
1503
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_origin"
|
|
1504
|
+
},
|
|
1505
|
+
"ticketTtlMs": {
|
|
1506
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_ticketTtlMs"
|
|
1507
|
+
},
|
|
1508
|
+
"ticketStore": {
|
|
1509
|
+
"@id": "undefineds:dist/api/handlers/DeviceNotificationTicketHandler.jsonld#DeviceNotificationTicketHandler_options_ticketStore"
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
},
|
|
1396
1513
|
"ReservedSuffixIdentifierGenerator": {
|
|
1397
1514
|
"@id": "undefineds:dist/pods/ReservedSuffixIdentifierGenerator.jsonld#ReservedSuffixIdentifierGenerator",
|
|
1398
1515
|
"@prefix": true,
|
|
@@ -3813,6 +3930,11 @@
|
|
|
3813
3930
|
"@id": "undefineds:dist/edge/reachability/P2PRealnetAcceptance.jsonld#P2PRealnetAcceptanceVerification",
|
|
3814
3931
|
"@prefix": true,
|
|
3815
3932
|
"@context": {}
|
|
3933
|
+
},
|
|
3934
|
+
"ParseClientFrameOptions": {
|
|
3935
|
+
"@id": "undefineds:dist/notifications/device-notification-protocol.jsonld#ParseClientFrameOptions",
|
|
3936
|
+
"@prefix": true,
|
|
3937
|
+
"@context": {}
|
|
3816
3938
|
}
|
|
3817
3939
|
}
|
|
3818
3940
|
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IncomingMessage, Server } from 'node:http';
|
|
2
|
+
import type { Duplex } from 'node:stream';
|
|
3
|
+
import type { DeviceNotificationTicketStore } from '../api/handlers/DeviceNotificationTicketHandler';
|
|
4
|
+
import { DeviceNotificationHub } from '../notifications/DeviceNotificationHub';
|
|
5
|
+
export interface DeviceNotificationWebSocketServerOptions {
|
|
6
|
+
hub: DeviceNotificationHub;
|
|
7
|
+
ticketStore: DeviceNotificationTicketStore;
|
|
8
|
+
path?: string;
|
|
9
|
+
heartbeatIntervalMs?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class DeviceNotificationWebSocketServer {
|
|
12
|
+
private readonly hub;
|
|
13
|
+
private readonly ticketStore;
|
|
14
|
+
private readonly path;
|
|
15
|
+
private readonly heartbeatIntervalMs;
|
|
16
|
+
private readonly wss;
|
|
17
|
+
private readonly sockets;
|
|
18
|
+
private readonly socketConnections;
|
|
19
|
+
private readonly alive;
|
|
20
|
+
private heartbeatTimer?;
|
|
21
|
+
constructor(options: DeviceNotificationWebSocketServerOptions);
|
|
22
|
+
attach(server: Server): void;
|
|
23
|
+
handleUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer): void;
|
|
24
|
+
stop(): void;
|
|
25
|
+
private handleFrame;
|
|
26
|
+
private reject;
|
|
27
|
+
private startHeartbeat;
|
|
28
|
+
private cleanupSocket;
|
|
29
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeviceNotificationWebSocketServer = void 0;
|
|
4
|
+
const ws_1 = require("ws");
|
|
5
|
+
const device_notification_protocol_1 = require("../notifications/device-notification-protocol");
|
|
6
|
+
class DeviceNotificationWebSocketServer {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.wss = new ws_1.WebSocketServer({ noServer: true });
|
|
9
|
+
this.sockets = new Set();
|
|
10
|
+
this.socketConnections = new Map();
|
|
11
|
+
this.alive = new Map();
|
|
12
|
+
this.hub = options.hub;
|
|
13
|
+
this.ticketStore = options.ticketStore;
|
|
14
|
+
this.path = options.path ?? '/v1/notifications/ws';
|
|
15
|
+
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? 30_000;
|
|
16
|
+
this.startHeartbeat();
|
|
17
|
+
}
|
|
18
|
+
attach(server) {
|
|
19
|
+
server.on('upgrade', (request, socket, head) => {
|
|
20
|
+
this.handleUpgrade(request, socket, head);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
handleUpgrade(request, socket, head) {
|
|
24
|
+
const url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);
|
|
25
|
+
if (url.pathname !== this.path) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const { protocolAccepted, ticket } = (0, device_notification_protocol_1.parseDeviceNotificationSubprotocols)(request.headers['sec-websocket-protocol']);
|
|
29
|
+
if (!protocolAccepted || !ticket) {
|
|
30
|
+
this.reject(socket, 401, 'Missing notification subprotocol');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const record = this.ticketStore.consume(ticket);
|
|
34
|
+
if (!record) {
|
|
35
|
+
this.reject(socket, 401, 'Invalid notification ticket');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.wss.handleUpgrade(request, socket, head, (ws) => {
|
|
39
|
+
this.sockets.add(ws);
|
|
40
|
+
const connection = this.hub.openConnection({
|
|
41
|
+
identity: record.identity,
|
|
42
|
+
deviceSessionId: record.deviceSessionId,
|
|
43
|
+
send: (frame) => {
|
|
44
|
+
if (ws.readyState !== ws_1.WebSocket.OPEN) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
ws.send((0, device_notification_protocol_1.serializeServerFrame)(frame));
|
|
48
|
+
return true;
|
|
49
|
+
},
|
|
50
|
+
close: (code, reason) => {
|
|
51
|
+
ws.close(code, reason);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
this.socketConnections.set(ws, connection.connectionId);
|
|
55
|
+
this.alive.set(ws, true);
|
|
56
|
+
ws.on('pong', () => {
|
|
57
|
+
this.alive.set(ws, true);
|
|
58
|
+
});
|
|
59
|
+
ws.on('message', (data) => {
|
|
60
|
+
void (async () => {
|
|
61
|
+
try {
|
|
62
|
+
const frame = (0, device_notification_protocol_1.parseClientFrame)(JSON.parse(data.toString()), {
|
|
63
|
+
origin: record.origin,
|
|
64
|
+
});
|
|
65
|
+
await this.handleFrame(connection.connectionId, frame);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
ws.send((0, device_notification_protocol_1.serializeServerFrame)((0, device_notification_protocol_1.createProtocolErrorFrame)('bad-frame', error.message)));
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
});
|
|
72
|
+
ws.on('close', () => {
|
|
73
|
+
this.cleanupSocket(ws);
|
|
74
|
+
});
|
|
75
|
+
ws.on('error', () => {
|
|
76
|
+
this.cleanupSocket(ws);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
stop() {
|
|
81
|
+
for (const socket of this.sockets) {
|
|
82
|
+
socket.close();
|
|
83
|
+
}
|
|
84
|
+
this.sockets.clear();
|
|
85
|
+
this.socketConnections.clear();
|
|
86
|
+
this.alive.clear();
|
|
87
|
+
if (this.heartbeatTimer) {
|
|
88
|
+
clearInterval(this.heartbeatTimer);
|
|
89
|
+
this.heartbeatTimer = undefined;
|
|
90
|
+
}
|
|
91
|
+
this.wss.close();
|
|
92
|
+
}
|
|
93
|
+
async handleFrame(connectionId, frame) {
|
|
94
|
+
switch (frame.type) {
|
|
95
|
+
case 'hello':
|
|
96
|
+
this.hub.hello(connectionId, frame.resumeFrom);
|
|
97
|
+
break;
|
|
98
|
+
case 'register':
|
|
99
|
+
await this.hub.registerTopics(connectionId, frame.requestId, frame.topics);
|
|
100
|
+
break;
|
|
101
|
+
case 'unregister':
|
|
102
|
+
await this.hub.unregisterTopics(connectionId, frame.requestId, frame.topics);
|
|
103
|
+
break;
|
|
104
|
+
case 'ack':
|
|
105
|
+
this.hub.ack(connectionId, frame.sequence);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
reject(socket, status, message) {
|
|
110
|
+
socket.write(`HTTP/1.1 ${status} ${message}\r\nConnection: close\r\n\r\n`);
|
|
111
|
+
socket.end();
|
|
112
|
+
}
|
|
113
|
+
startHeartbeat() {
|
|
114
|
+
this.heartbeatTimer = setInterval(() => {
|
|
115
|
+
for (const socket of this.sockets) {
|
|
116
|
+
if (this.alive.get(socket) === false) {
|
|
117
|
+
this.cleanupSocket(socket);
|
|
118
|
+
socket.close();
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
this.alive.set(socket, false);
|
|
122
|
+
socket.ping();
|
|
123
|
+
}
|
|
124
|
+
}, this.heartbeatIntervalMs);
|
|
125
|
+
this.heartbeatTimer.unref?.();
|
|
126
|
+
}
|
|
127
|
+
cleanupSocket(socket) {
|
|
128
|
+
this.sockets.delete(socket);
|
|
129
|
+
this.alive.delete(socket);
|
|
130
|
+
const connectionId = this.socketConnections.get(socket);
|
|
131
|
+
this.socketConnections.delete(socket);
|
|
132
|
+
if (connectionId) {
|
|
133
|
+
this.hub.closeConnection(connectionId);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.DeviceNotificationWebSocketServer = DeviceNotificationWebSocketServer;
|
|
138
|
+
//# sourceMappingURL=DeviceNotificationWebSocketServer.js.map
|