@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.
Files changed (43) hide show
  1. package/config/components-ignore.json +1 -0
  2. package/dist/api/ApiServer.d.ts +9 -1
  3. package/dist/api/ApiServer.js +22 -1
  4. package/dist/api/ApiServer.js.map +1 -1
  5. package/dist/api/container/routes.js +13 -0
  6. package/dist/api/container/routes.js.map +1 -1
  7. package/dist/api/handlers/DeviceNotificationRuntime.d.ts +26 -0
  8. package/dist/api/handlers/DeviceNotificationRuntime.js +77 -0
  9. package/dist/api/handlers/DeviceNotificationRuntime.js.map +1 -0
  10. package/dist/api/handlers/DeviceNotificationTicketHandler.d.ts +50 -0
  11. package/dist/api/handlers/DeviceNotificationTicketHandler.js +130 -0
  12. package/dist/api/handlers/DeviceNotificationTicketHandler.js.map +1 -0
  13. package/dist/api/handlers/DeviceNotificationTicketHandler.jsonld +117 -0
  14. package/dist/api/runs/InngestRunExecutionBackend.d.ts +2 -2
  15. package/dist/api/tasks/InngestTaskScheduler.d.ts +4 -4
  16. package/dist/components/components.jsonld +6 -1
  17. package/dist/components/context.jsonld +122 -0
  18. package/dist/http/DeviceNotificationWebSocketServer.d.ts +29 -0
  19. package/dist/http/DeviceNotificationWebSocketServer.js +138 -0
  20. package/dist/http/DeviceNotificationWebSocketServer.js.map +1 -0
  21. package/dist/http/DeviceNotificationWebSocketServer.jsonld +148 -0
  22. package/dist/index.d.ts +7 -1
  23. package/dist/index.js +13 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/notifications/DeviceNotificationHub.d.ts +74 -0
  26. package/dist/notifications/DeviceNotificationHub.js +313 -0
  27. package/dist/notifications/DeviceNotificationHub.js.map +1 -0
  28. package/dist/notifications/DeviceNotificationHub.jsonld +258 -0
  29. package/dist/notifications/DeviceNotificationResourceListener.d.ts +21 -0
  30. package/dist/notifications/DeviceNotificationResourceListener.js +38 -0
  31. package/dist/notifications/DeviceNotificationResourceListener.js.map +1 -0
  32. package/dist/notifications/DeviceNotificationResourceListener.jsonld +98 -0
  33. package/dist/notifications/device-notification-protocol.d.ts +56 -0
  34. package/dist/notifications/device-notification-protocol.js +127 -0
  35. package/dist/notifications/device-notification-protocol.js.map +1 -0
  36. package/dist/notifications/device-notification-protocol.jsonld +29 -0
  37. package/dist/notifications/index.d.ts +3 -0
  38. package/dist/notifications/index.js +20 -0
  39. package/dist/notifications/index.js.map +1 -0
  40. package/dist/storage/ObservableResourceStore.d.ts +3 -0
  41. package/dist/storage/ObservableResourceStore.js +17 -1
  42. package/dist/storage/ObservableResourceStore.js.map +1 -1
  43. 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" | "attempt" | "event" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest").FailureEventArgs<EventPayload<any>> & {
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" | "attempt" | "event" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest").FailureEventArgs<EventPayload<any>> & {
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" | "attempt" | "event" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
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" | "attempt" | "event" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
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" | "attempt" | "event" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
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" | "attempt" | "event" | "events" | "runId" | "step" | "defer" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
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