@undefineds.co/xpod 0.3.67 → 0.3.69
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/common.js +1 -1
- package/dist/api/container/common.js.map +1 -1
- package/dist/api/container/routes.js +13 -0
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/container/types.d.ts +1 -1
- package/dist/api/container/types.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,258 @@
|
|
|
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/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub",
|
|
9
|
+
"@type": "Class",
|
|
10
|
+
"requireElement": "DeviceNotificationHub",
|
|
11
|
+
"parameters": [
|
|
12
|
+
{
|
|
13
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub_options",
|
|
14
|
+
"range": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHubOptions"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"memberFields": [
|
|
18
|
+
{
|
|
19
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_origin",
|
|
20
|
+
"memberFieldName": "origin"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_maxTopicsPerConnection",
|
|
24
|
+
"memberFieldName": "maxTopicsPerConnection"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_maxQueueMessages",
|
|
28
|
+
"memberFieldName": "maxQueueMessages"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_maxReplayEvents",
|
|
32
|
+
"memberFieldName": "maxReplayEvents"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_authorizeTopic",
|
|
36
|
+
"memberFieldName": "authorizeTopic"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_connections",
|
|
40
|
+
"memberFieldName": "connections"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_connectionsByDevice",
|
|
44
|
+
"memberFieldName": "connectionsByDevice"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_topicMembers",
|
|
48
|
+
"memberFieldName": "topicMembers"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_replay",
|
|
52
|
+
"memberFieldName": "replay"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_sequence",
|
|
56
|
+
"memberFieldName": "sequence"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_constructor",
|
|
60
|
+
"memberFieldName": "constructor"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_openConnection",
|
|
64
|
+
"memberFieldName": "openConnection"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_hello",
|
|
68
|
+
"memberFieldName": "hello"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_registerTopics",
|
|
72
|
+
"memberFieldName": "registerTopics"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_unregisterTopics",
|
|
76
|
+
"memberFieldName": "unregisterTopics"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_ack",
|
|
80
|
+
"memberFieldName": "ack"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_publish",
|
|
84
|
+
"memberFieldName": "publish"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_resume",
|
|
88
|
+
"memberFieldName": "resume"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_closeConnection",
|
|
92
|
+
"memberFieldName": "closeConnection"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_getSeenRequestIds",
|
|
96
|
+
"memberFieldName": "getSeenRequestIds"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_getTopicMemberCount",
|
|
100
|
+
"memberFieldName": "getTopicMemberCount"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_getConnectionSnapshot",
|
|
104
|
+
"memberFieldName": "getConnectionSnapshot"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_deliver",
|
|
108
|
+
"memberFieldName": "deliver"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_deliverPendingResume",
|
|
112
|
+
"memberFieldName": "deliverPendingResume"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_enqueue",
|
|
116
|
+
"memberFieldName": "enqueue"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_coalesceEvent",
|
|
120
|
+
"memberFieldName": "coalesceEvent"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_markOverflow",
|
|
124
|
+
"memberFieldName": "markOverflow"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_pushReplay",
|
|
128
|
+
"memberFieldName": "pushReplay"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_matchingMemberIds",
|
|
132
|
+
"memberFieldName": "matchingMemberIds"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_connectionMatches",
|
|
136
|
+
"memberFieldName": "connectionMatches"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_topicMatches",
|
|
140
|
+
"memberFieldName": "topicMatches"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_assertNewRequestId",
|
|
144
|
+
"memberFieldName": "assertNewRequestId"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_normalizeTopic",
|
|
148
|
+
"memberFieldName": "normalizeTopic"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_normalizeResource",
|
|
152
|
+
"memberFieldName": "normalizeResource"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_requireConnection",
|
|
156
|
+
"memberFieldName": "requireConnection"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub__member_deviceConnectionKey",
|
|
160
|
+
"memberFieldName": "deviceConnectionKey"
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
"constructorArguments": [
|
|
164
|
+
{
|
|
165
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHub_options"
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHubOptions",
|
|
171
|
+
"@type": "AbstractClass",
|
|
172
|
+
"requireElement": "DeviceNotificationHubOptions",
|
|
173
|
+
"parameters": [],
|
|
174
|
+
"memberFields": [
|
|
175
|
+
{
|
|
176
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHubOptions__member_origin",
|
|
177
|
+
"memberFieldName": "origin"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHubOptions__member_maxTopicsPerConnection",
|
|
181
|
+
"memberFieldName": "maxTopicsPerConnection"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHubOptions__member_maxQueueMessages",
|
|
185
|
+
"memberFieldName": "maxQueueMessages"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHubOptions__member_maxReplayEvents",
|
|
189
|
+
"memberFieldName": "maxReplayEvents"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationHubOptions__member_authorizeTopic",
|
|
193
|
+
"memberFieldName": "authorizeTopic"
|
|
194
|
+
}
|
|
195
|
+
],
|
|
196
|
+
"constructorArguments": []
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#OpenDeviceNotificationConnectionInput",
|
|
200
|
+
"@type": "AbstractClass",
|
|
201
|
+
"requireElement": "OpenDeviceNotificationConnectionInput",
|
|
202
|
+
"parameters": [],
|
|
203
|
+
"memberFields": [
|
|
204
|
+
{
|
|
205
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#OpenDeviceNotificationConnectionInput__member_identity",
|
|
206
|
+
"memberFieldName": "identity"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#OpenDeviceNotificationConnectionInput__member_deviceSessionId",
|
|
210
|
+
"memberFieldName": "deviceSessionId"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#OpenDeviceNotificationConnectionInput__member_send",
|
|
214
|
+
"memberFieldName": "send"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#OpenDeviceNotificationConnectionInput__member_close",
|
|
218
|
+
"memberFieldName": "close"
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
"constructorArguments": []
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationConnectionHandle",
|
|
225
|
+
"@type": "AbstractClass",
|
|
226
|
+
"requireElement": "DeviceNotificationConnectionHandle",
|
|
227
|
+
"parameters": [],
|
|
228
|
+
"memberFields": [
|
|
229
|
+
{
|
|
230
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationConnectionHandle__member_connectionId",
|
|
231
|
+
"memberFieldName": "connectionId"
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
"constructorArguments": []
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationPublishInput",
|
|
238
|
+
"@type": "AbstractClass",
|
|
239
|
+
"requireElement": "DeviceNotificationPublishInput",
|
|
240
|
+
"parameters": [],
|
|
241
|
+
"memberFields": [
|
|
242
|
+
{
|
|
243
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationPublishInput__member_topic",
|
|
244
|
+
"memberFieldName": "topic"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationPublishInput__member_object",
|
|
248
|
+
"memberFieldName": "object"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationHub.jsonld#DeviceNotificationPublishInput__member_operation",
|
|
252
|
+
"memberFieldName": "operation"
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
"constructorArguments": []
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ResourceChangeEvent } from '../storage/ObservableResourceStore';
|
|
2
|
+
import type { DeviceNotificationOperation } from './device-notification-protocol';
|
|
3
|
+
export interface DeviceNotificationResourcePublisher {
|
|
4
|
+
publish(event: {
|
|
5
|
+
topic: string;
|
|
6
|
+
object?: string;
|
|
7
|
+
operation: DeviceNotificationOperation;
|
|
8
|
+
}): void;
|
|
9
|
+
}
|
|
10
|
+
export interface DeviceNotificationResourceListenerOptions {
|
|
11
|
+
origin: string;
|
|
12
|
+
hub: DeviceNotificationResourcePublisher;
|
|
13
|
+
}
|
|
14
|
+
export declare class DeviceNotificationResourceListener {
|
|
15
|
+
private readonly origin;
|
|
16
|
+
private readonly hub;
|
|
17
|
+
constructor(options: DeviceNotificationResourceListenerOptions);
|
|
18
|
+
onResourceChanged(event: ResourceChangeEvent): Promise<void>;
|
|
19
|
+
private toResourceUrl;
|
|
20
|
+
private toParentTopic;
|
|
21
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeviceNotificationResourceListener = void 0;
|
|
4
|
+
class DeviceNotificationResourceListener {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.origin = new URL(options.origin);
|
|
7
|
+
this.hub = options.hub;
|
|
8
|
+
}
|
|
9
|
+
async onResourceChanged(event) {
|
|
10
|
+
const object = this.toResourceUrl(event.path);
|
|
11
|
+
this.hub.publish({
|
|
12
|
+
topic: this.toParentTopic(object),
|
|
13
|
+
object,
|
|
14
|
+
operation: event.action,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
toResourceUrl(path) {
|
|
18
|
+
try {
|
|
19
|
+
return new URL(path).toString();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return new URL(path, this.origin).toString();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
toParentTopic(object) {
|
|
26
|
+
const url = new URL(object);
|
|
27
|
+
if (url.pathname.endsWith('/')) {
|
|
28
|
+
return url.toString();
|
|
29
|
+
}
|
|
30
|
+
const index = url.pathname.lastIndexOf('/');
|
|
31
|
+
url.pathname = index >= 0 ? url.pathname.slice(0, index + 1) : '/';
|
|
32
|
+
url.search = '';
|
|
33
|
+
url.hash = '';
|
|
34
|
+
return url.toString();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.DeviceNotificationResourceListener = DeviceNotificationResourceListener;
|
|
38
|
+
//# sourceMappingURL=DeviceNotificationResourceListener.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeviceNotificationResourceListener.js","sourceRoot":"","sources":["../../src/notifications/DeviceNotificationResourceListener.ts"],"names":[],"mappings":";;;AAYA,MAAa,kCAAkC;IAI7C,YAAmB,OAAkD;QACnE,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACzB,CAAC;IAEM,KAAK,CAAC,iBAAiB,CAAC,KAA0B;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YACf,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;YACjC,MAAM;YACN,SAAS,EAAE,KAAK,CAAC,MAAM;SACxB,CAAC,CAAC;IACL,CAAC;IAEO,aAAa,CAAC,IAAY;QAChC,IAAI,CAAC;YACH,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC/C,CAAC;IACH,CAAC;IAEO,aAAa,CAAC,MAAc;QAClC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;QACxB,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC5C,GAAG,CAAC,QAAQ,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACnE,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC;QAChB,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;CACF;AArCD,gFAqCC","sourcesContent":["import type { ResourceChangeEvent } from '../storage/ObservableResourceStore';\nimport type { DeviceNotificationOperation } from './device-notification-protocol';\n\nexport interface DeviceNotificationResourcePublisher {\n publish(event: { topic: string; object?: string; operation: DeviceNotificationOperation }): void;\n}\n\nexport interface DeviceNotificationResourceListenerOptions {\n origin: string;\n hub: DeviceNotificationResourcePublisher;\n}\n\nexport class DeviceNotificationResourceListener {\n private readonly origin: URL;\n private readonly hub: DeviceNotificationResourcePublisher;\n\n public constructor(options: DeviceNotificationResourceListenerOptions) {\n this.origin = new URL(options.origin);\n this.hub = options.hub;\n }\n\n public async onResourceChanged(event: ResourceChangeEvent): Promise<void> {\n const object = this.toResourceUrl(event.path);\n this.hub.publish({\n topic: this.toParentTopic(object),\n object,\n operation: event.action,\n });\n }\n\n private toResourceUrl(path: string): string {\n try {\n return new URL(path).toString();\n } catch {\n return new URL(path, this.origin).toString();\n }\n }\n\n private toParentTopic(object: string): string {\n const url = new URL(object);\n if (url.pathname.endsWith('/')) {\n return url.toString();\n }\n const index = url.pathname.lastIndexOf('/');\n url.pathname = index >= 0 ? url.pathname.slice(0, index + 1) : '/';\n url.search = '';\n url.hash = '';\n return url.toString();\n }\n}\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
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/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener",
|
|
9
|
+
"@type": "Class",
|
|
10
|
+
"requireElement": "DeviceNotificationResourceListener",
|
|
11
|
+
"parameters": [
|
|
12
|
+
{
|
|
13
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener_options_origin",
|
|
14
|
+
"range": "xsd:string"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener_options_hub",
|
|
18
|
+
"range": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourcePublisher"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"memberFields": [
|
|
22
|
+
{
|
|
23
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener__member_origin",
|
|
24
|
+
"memberFieldName": "origin"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener__member_hub",
|
|
28
|
+
"memberFieldName": "hub"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener__member_constructor",
|
|
32
|
+
"memberFieldName": "constructor"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener__member_onResourceChanged",
|
|
36
|
+
"memberFieldName": "onResourceChanged"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener__member_toResourceUrl",
|
|
40
|
+
"memberFieldName": "toResourceUrl"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener__member_toParentTopic",
|
|
44
|
+
"memberFieldName": "toParentTopic"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"constructorArguments": [
|
|
48
|
+
{
|
|
49
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener_options__constructorArgument",
|
|
50
|
+
"fields": [
|
|
51
|
+
{
|
|
52
|
+
"keyRaw": "origin",
|
|
53
|
+
"value": {
|
|
54
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener_options_origin"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"keyRaw": "hub",
|
|
59
|
+
"value": {
|
|
60
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListener_options_hub"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourcePublisher",
|
|
69
|
+
"@type": "AbstractClass",
|
|
70
|
+
"requireElement": "DeviceNotificationResourcePublisher",
|
|
71
|
+
"parameters": [],
|
|
72
|
+
"memberFields": [
|
|
73
|
+
{
|
|
74
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourcePublisher__member_publish",
|
|
75
|
+
"memberFieldName": "publish"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"constructorArguments": []
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListenerOptions",
|
|
82
|
+
"@type": "AbstractClass",
|
|
83
|
+
"requireElement": "DeviceNotificationResourceListenerOptions",
|
|
84
|
+
"parameters": [],
|
|
85
|
+
"memberFields": [
|
|
86
|
+
{
|
|
87
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListenerOptions__member_origin",
|
|
88
|
+
"memberFieldName": "origin"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"@id": "undefineds:dist/notifications/DeviceNotificationResourceListener.jsonld#DeviceNotificationResourceListenerOptions__member_hub",
|
|
92
|
+
"memberFieldName": "hub"
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"constructorArguments": []
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export declare const XPOD_NOTIFICATIONS_PROTOCOL = "xpod.notifications.v1";
|
|
2
|
+
export type DeviceNotificationOperation = 'create' | 'update' | 'delete' | 'invalidate';
|
|
3
|
+
export type DeviceNotificationClientFrame = {
|
|
4
|
+
type: 'hello';
|
|
5
|
+
protocol: typeof XPOD_NOTIFICATIONS_PROTOCOL;
|
|
6
|
+
resumeFrom?: number;
|
|
7
|
+
} | {
|
|
8
|
+
type: 'register';
|
|
9
|
+
requestId: string;
|
|
10
|
+
topics: string[];
|
|
11
|
+
} | {
|
|
12
|
+
type: 'unregister';
|
|
13
|
+
requestId: string;
|
|
14
|
+
topics: string[];
|
|
15
|
+
} | {
|
|
16
|
+
type: 'ack';
|
|
17
|
+
sequence: number;
|
|
18
|
+
};
|
|
19
|
+
export type DeviceNotificationServerFrame = {
|
|
20
|
+
type: 'ready';
|
|
21
|
+
connectionId: string;
|
|
22
|
+
sequence: number;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'registered' | 'unregistered';
|
|
25
|
+
requestId: string;
|
|
26
|
+
topics: string[];
|
|
27
|
+
} | {
|
|
28
|
+
type: 'event';
|
|
29
|
+
sequence: number;
|
|
30
|
+
eventId: string;
|
|
31
|
+
topic: string;
|
|
32
|
+
object?: string;
|
|
33
|
+
operation: DeviceNotificationOperation;
|
|
34
|
+
emittedAt: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'resync-required';
|
|
37
|
+
topics: string[];
|
|
38
|
+
reason: 'gap' | 'overflow' | 'expired';
|
|
39
|
+
} | {
|
|
40
|
+
type: 'error';
|
|
41
|
+
requestId?: string;
|
|
42
|
+
code: string;
|
|
43
|
+
message: string;
|
|
44
|
+
};
|
|
45
|
+
export interface ParseClientFrameOptions {
|
|
46
|
+
origin?: string;
|
|
47
|
+
seenRequestIds?: Set<string>;
|
|
48
|
+
maxTopicsPerBatch?: number;
|
|
49
|
+
}
|
|
50
|
+
export declare function parseClientFrame(input: unknown, options?: ParseClientFrameOptions): DeviceNotificationClientFrame;
|
|
51
|
+
export declare function serializeServerFrame(frame: DeviceNotificationServerFrame): string;
|
|
52
|
+
export declare function createProtocolErrorFrame(code: string, message: string, requestId?: string): DeviceNotificationServerFrame;
|
|
53
|
+
export declare function parseDeviceNotificationSubprotocols(header: string | string[] | undefined): {
|
|
54
|
+
protocolAccepted: boolean;
|
|
55
|
+
ticket?: string;
|
|
56
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.XPOD_NOTIFICATIONS_PROTOCOL = void 0;
|
|
4
|
+
exports.parseClientFrame = parseClientFrame;
|
|
5
|
+
exports.serializeServerFrame = serializeServerFrame;
|
|
6
|
+
exports.createProtocolErrorFrame = createProtocolErrorFrame;
|
|
7
|
+
exports.parseDeviceNotificationSubprotocols = parseDeviceNotificationSubprotocols;
|
|
8
|
+
exports.XPOD_NOTIFICATIONS_PROTOCOL = 'xpod.notifications.v1';
|
|
9
|
+
const CLIENT_FIELDS = {
|
|
10
|
+
hello: new Set(['type', 'protocol', 'resumeFrom']),
|
|
11
|
+
register: new Set(['type', 'requestId', 'topics']),
|
|
12
|
+
unregister: new Set(['type', 'requestId', 'topics']),
|
|
13
|
+
ack: new Set(['type', 'sequence']),
|
|
14
|
+
};
|
|
15
|
+
function parseClientFrame(input, options = {}) {
|
|
16
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
17
|
+
throw new Error('Invalid client frame');
|
|
18
|
+
}
|
|
19
|
+
const frame = input;
|
|
20
|
+
if (typeof frame.type !== 'string' || !(frame.type in CLIENT_FIELDS)) {
|
|
21
|
+
throw new Error(`Unknown frame type: ${String(frame.type)}`);
|
|
22
|
+
}
|
|
23
|
+
rejectUnknownFields(frame, CLIENT_FIELDS[frame.type]);
|
|
24
|
+
switch (frame.type) {
|
|
25
|
+
case 'hello':
|
|
26
|
+
return parseHello(frame);
|
|
27
|
+
case 'register':
|
|
28
|
+
case 'unregister':
|
|
29
|
+
return parseTopicFrame(frame, frame.type, options);
|
|
30
|
+
case 'ack':
|
|
31
|
+
return parseAck(frame);
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(`Unknown frame type: ${String(frame.type)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function serializeServerFrame(frame) {
|
|
37
|
+
return JSON.stringify(frame);
|
|
38
|
+
}
|
|
39
|
+
function createProtocolErrorFrame(code, message, requestId) {
|
|
40
|
+
return {
|
|
41
|
+
type: 'error',
|
|
42
|
+
...(requestId ? { requestId } : {}),
|
|
43
|
+
code,
|
|
44
|
+
message: messageContainsSecret(message) ? '[redacted]' : message,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function parseDeviceNotificationSubprotocols(header) {
|
|
48
|
+
const raw = Array.isArray(header) ? header.join(',') : header ?? '';
|
|
49
|
+
const entries = raw.split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
50
|
+
return {
|
|
51
|
+
protocolAccepted: entries.includes(exports.XPOD_NOTIFICATIONS_PROTOCOL),
|
|
52
|
+
ticket: entries.find((entry) => entry !== exports.XPOD_NOTIFICATIONS_PROTOCOL),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function parseHello(frame) {
|
|
56
|
+
if (frame.protocol !== exports.XPOD_NOTIFICATIONS_PROTOCOL) {
|
|
57
|
+
throw new Error('Unsupported notification protocol');
|
|
58
|
+
}
|
|
59
|
+
if (frame.resumeFrom !== undefined && !isNonNegativeInteger(frame.resumeFrom)) {
|
|
60
|
+
throw new Error('resumeFrom must be a non-negative integer');
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
type: 'hello',
|
|
64
|
+
protocol: exports.XPOD_NOTIFICATIONS_PROTOCOL,
|
|
65
|
+
...(frame.resumeFrom === undefined ? {} : { resumeFrom: frame.resumeFrom }),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function parseTopicFrame(frame, type, options) {
|
|
69
|
+
if (typeof frame.requestId !== 'string' || frame.requestId.length === 0) {
|
|
70
|
+
throw new Error('requestId is required');
|
|
71
|
+
}
|
|
72
|
+
if (options.seenRequestIds?.has(frame.requestId)) {
|
|
73
|
+
throw new Error(`Duplicate requestId: ${frame.requestId}`);
|
|
74
|
+
}
|
|
75
|
+
const topics = parseTopics(frame.topics, options);
|
|
76
|
+
options.seenRequestIds?.add(frame.requestId);
|
|
77
|
+
return { type, requestId: frame.requestId, topics };
|
|
78
|
+
}
|
|
79
|
+
function parseAck(frame) {
|
|
80
|
+
if (!isNonNegativeInteger(frame.sequence)) {
|
|
81
|
+
throw new Error('sequence must be a non-negative integer');
|
|
82
|
+
}
|
|
83
|
+
return { type: 'ack', sequence: frame.sequence };
|
|
84
|
+
}
|
|
85
|
+
function parseTopics(input, options) {
|
|
86
|
+
if (!Array.isArray(input) || input.length === 0) {
|
|
87
|
+
throw new Error('topics must be a non-empty array');
|
|
88
|
+
}
|
|
89
|
+
const maxTopics = options.maxTopicsPerBatch ?? 100;
|
|
90
|
+
if (input.length > maxTopics) {
|
|
91
|
+
throw new Error(`Too many topics: ${input.length}`);
|
|
92
|
+
}
|
|
93
|
+
return input.map((topic) => parseTopic(topic, options.origin));
|
|
94
|
+
}
|
|
95
|
+
function parseTopic(input, origin) {
|
|
96
|
+
if (typeof input !== 'string' || input.trim().length === 0) {
|
|
97
|
+
throw new Error('topic must be a URL string');
|
|
98
|
+
}
|
|
99
|
+
let url;
|
|
100
|
+
try {
|
|
101
|
+
url = new URL(input);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
throw new Error('topic must be an absolute URL');
|
|
105
|
+
}
|
|
106
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
107
|
+
throw new Error('topic must be an HTTP URL');
|
|
108
|
+
}
|
|
109
|
+
if (origin && url.origin !== new URL(origin).origin) {
|
|
110
|
+
throw new Error('topic must be a same-origin Pod resource URL');
|
|
111
|
+
}
|
|
112
|
+
return url.toString();
|
|
113
|
+
}
|
|
114
|
+
function rejectUnknownFields(frame, allowed) {
|
|
115
|
+
for (const key of Object.keys(frame)) {
|
|
116
|
+
if (!allowed.has(key)) {
|
|
117
|
+
throw new Error(`Unknown field: ${key}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function isNonNegativeInteger(value) {
|
|
122
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 0;
|
|
123
|
+
}
|
|
124
|
+
function messageContainsSecret(message) {
|
|
125
|
+
return /token|ticket|secret|bearer/i.test(message);
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=device-notification-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-notification-protocol.js","sourceRoot":"","sources":["../../src/notifications/device-notification-protocol.ts"],"names":[],"mappings":"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","sourcesContent":["export const XPOD_NOTIFICATIONS_PROTOCOL = 'xpod.notifications.v1';\n\nexport type DeviceNotificationOperation = 'create' | 'update' | 'delete' | 'invalidate';\n\nexport type DeviceNotificationClientFrame =\n | { type: 'hello'; protocol: typeof XPOD_NOTIFICATIONS_PROTOCOL; resumeFrom?: number }\n | { type: 'register'; requestId: string; topics: string[] }\n | { type: 'unregister'; requestId: string; topics: string[] }\n | { type: 'ack'; sequence: number };\n\nexport type DeviceNotificationServerFrame =\n | { type: 'ready'; connectionId: string; sequence: number }\n | { type: 'registered' | 'unregistered'; requestId: string; topics: string[] }\n | {\n type: 'event';\n sequence: number;\n eventId: string;\n topic: string;\n object?: string;\n operation: DeviceNotificationOperation;\n emittedAt: string;\n }\n | { type: 'resync-required'; topics: string[]; reason: 'gap' | 'overflow' | 'expired' }\n | { type: 'error'; requestId?: string; code: string; message: string };\n\nexport interface ParseClientFrameOptions {\n origin?: string;\n seenRequestIds?: Set<string>;\n maxTopicsPerBatch?: number;\n}\n\nconst CLIENT_FIELDS: Record<string, Set<string>> = {\n hello: new Set(['type', 'protocol', 'resumeFrom']),\n register: new Set(['type', 'requestId', 'topics']),\n unregister: new Set(['type', 'requestId', 'topics']),\n ack: new Set(['type', 'sequence']),\n};\n\nexport function parseClientFrame(input: unknown, options: ParseClientFrameOptions = {}): DeviceNotificationClientFrame {\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n throw new Error('Invalid client frame');\n }\n const frame = input as Record<string, unknown>;\n if (typeof frame.type !== 'string' || !(frame.type in CLIENT_FIELDS)) {\n throw new Error(`Unknown frame type: ${String(frame.type)}`);\n }\n rejectUnknownFields(frame, CLIENT_FIELDS[frame.type]);\n\n switch (frame.type) {\n case 'hello':\n return parseHello(frame);\n case 'register':\n case 'unregister':\n return parseTopicFrame(frame, frame.type, options);\n case 'ack':\n return parseAck(frame);\n default:\n throw new Error(`Unknown frame type: ${String(frame.type)}`);\n }\n}\n\nexport function serializeServerFrame(frame: DeviceNotificationServerFrame): string {\n return JSON.stringify(frame);\n}\n\nexport function createProtocolErrorFrame(\n code: string,\n message: string,\n requestId?: string,\n): DeviceNotificationServerFrame {\n return {\n type: 'error',\n ...(requestId ? { requestId } : {}),\n code,\n message: messageContainsSecret(message) ? '[redacted]' : message,\n };\n}\n\nexport function parseDeviceNotificationSubprotocols(header: string | string[] | undefined): {\n protocolAccepted: boolean;\n ticket?: string;\n} {\n const raw = Array.isArray(header) ? header.join(',') : header ?? '';\n const entries = raw.split(',').map((entry) => entry.trim()).filter(Boolean);\n return {\n protocolAccepted: entries.includes(XPOD_NOTIFICATIONS_PROTOCOL),\n ticket: entries.find((entry) => entry !== XPOD_NOTIFICATIONS_PROTOCOL),\n };\n}\n\nfunction parseHello(frame: Record<string, unknown>): DeviceNotificationClientFrame {\n if (frame.protocol !== XPOD_NOTIFICATIONS_PROTOCOL) {\n throw new Error('Unsupported notification protocol');\n }\n if (frame.resumeFrom !== undefined && !isNonNegativeInteger(frame.resumeFrom)) {\n throw new Error('resumeFrom must be a non-negative integer');\n }\n return {\n type: 'hello',\n protocol: XPOD_NOTIFICATIONS_PROTOCOL,\n ...(frame.resumeFrom === undefined ? {} : { resumeFrom: frame.resumeFrom as number }),\n };\n}\n\nfunction parseTopicFrame(\n frame: Record<string, unknown>,\n type: 'register' | 'unregister',\n options: ParseClientFrameOptions,\n): DeviceNotificationClientFrame {\n if (typeof frame.requestId !== 'string' || frame.requestId.length === 0) {\n throw new Error('requestId is required');\n }\n if (options.seenRequestIds?.has(frame.requestId)) {\n throw new Error(`Duplicate requestId: ${frame.requestId}`);\n }\n const topics = parseTopics(frame.topics, options);\n options.seenRequestIds?.add(frame.requestId);\n return { type, requestId: frame.requestId, topics };\n}\n\nfunction parseAck(frame: Record<string, unknown>): DeviceNotificationClientFrame {\n if (!isNonNegativeInteger(frame.sequence)) {\n throw new Error('sequence must be a non-negative integer');\n }\n return { type: 'ack', sequence: frame.sequence as number };\n}\n\nfunction parseTopics(input: unknown, options: ParseClientFrameOptions): string[] {\n if (!Array.isArray(input) || input.length === 0) {\n throw new Error('topics must be a non-empty array');\n }\n const maxTopics = options.maxTopicsPerBatch ?? 100;\n if (input.length > maxTopics) {\n throw new Error(`Too many topics: ${input.length}`);\n }\n return input.map((topic) => parseTopic(topic, options.origin));\n}\n\nfunction parseTopic(input: unknown, origin?: string): string {\n if (typeof input !== 'string' || input.trim().length === 0) {\n throw new Error('topic must be a URL string');\n }\n let url: URL;\n try {\n url = new URL(input);\n } catch {\n throw new Error('topic must be an absolute URL');\n }\n if (!['http:', 'https:'].includes(url.protocol)) {\n throw new Error('topic must be an HTTP URL');\n }\n if (origin && url.origin !== new URL(origin).origin) {\n throw new Error('topic must be a same-origin Pod resource URL');\n }\n return url.toString();\n}\n\nfunction rejectUnknownFields(frame: Record<string, unknown>, allowed: Set<string>): void {\n for (const key of Object.keys(frame)) {\n if (!allowed.has(key)) {\n throw new Error(`Unknown field: ${key}`);\n }\n }\n}\n\nfunction isNonNegativeInteger(value: unknown): boolean {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0;\n}\n\nfunction messageContainsSecret(message: string): boolean {\n return /token|ticket|secret|bearer/i.test(message);\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
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/notifications/device-notification-protocol.jsonld#ParseClientFrameOptions",
|
|
9
|
+
"@type": "AbstractClass",
|
|
10
|
+
"requireElement": "ParseClientFrameOptions",
|
|
11
|
+
"parameters": [],
|
|
12
|
+
"memberFields": [
|
|
13
|
+
{
|
|
14
|
+
"@id": "undefineds:dist/notifications/device-notification-protocol.jsonld#ParseClientFrameOptions__member_origin",
|
|
15
|
+
"memberFieldName": "origin"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"@id": "undefineds:dist/notifications/device-notification-protocol.jsonld#ParseClientFrameOptions__member_seenRequestIds",
|
|
19
|
+
"memberFieldName": "seenRequestIds"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"@id": "undefineds:dist/notifications/device-notification-protocol.jsonld#ParseClientFrameOptions__member_maxTopicsPerBatch",
|
|
23
|
+
"memberFieldName": "maxTopicsPerBatch"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"constructorArguments": []
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|