@elliemae/pui-websocket-so 2.0.0

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 (36) hide show
  1. package/dist/cjs/index.html +758 -0
  2. package/dist/cjs/index.js +24 -0
  3. package/dist/cjs/messageRouter.js +111 -0
  4. package/dist/cjs/package.json +7 -0
  5. package/dist/cjs/subscriptionManager.js +224 -0
  6. package/dist/cjs/types.js +16 -0
  7. package/dist/cjs/websocketSO.js +338 -0
  8. package/dist/esm/index.html +758 -0
  9. package/dist/esm/index.js +4 -0
  10. package/dist/esm/messageRouter.js +91 -0
  11. package/dist/esm/package.json +7 -0
  12. package/dist/esm/subscriptionManager.js +204 -0
  13. package/dist/esm/types.js +0 -0
  14. package/dist/esm/websocketSO.js +318 -0
  15. package/dist/public/guest.html +523 -0
  16. package/dist/public/index.html +1 -0
  17. package/dist/public/js/emuiWebsocketSo.cc1f5b5e1d095fc3a34e.js +3 -0
  18. package/dist/public/js/emuiWebsocketSo.cc1f5b5e1d095fc3a34e.js.br +0 -0
  19. package/dist/public/js/emuiWebsocketSo.cc1f5b5e1d095fc3a34e.js.gz +0 -0
  20. package/dist/public/js/emuiWebsocketSo.cc1f5b5e1d095fc3a34e.js.map +1 -0
  21. package/dist/types/lib/index.d.ts +2 -0
  22. package/dist/types/lib/messageRouter.d.ts +30 -0
  23. package/dist/types/lib/subscriptionManager.d.ts +101 -0
  24. package/dist/types/lib/tests/messageRouter.test.d.ts +1 -0
  25. package/dist/types/lib/tests/subscriptionManager.test.d.ts +1 -0
  26. package/dist/types/lib/tests/websocketSO.test.d.ts +1 -0
  27. package/dist/types/lib/types.d.ts +118 -0
  28. package/dist/types/lib/websocketSO.d.ts +56 -0
  29. package/dist/types/tsconfig.tsbuildinfo +1 -0
  30. package/dist/umd/guest.html +523 -0
  31. package/dist/umd/index.html +1 -0
  32. package/dist/umd/index.js +3 -0
  33. package/dist/umd/index.js.br +0 -0
  34. package/dist/umd/index.js.gz +0 -0
  35. package/dist/umd/index.js.map +1 -0
  36. package/package.json +69 -0
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var index_exports = {};
20
+ __export(index_exports, {
21
+ WebSocketSO: () => import_websocketSO.WebSocketSO
22
+ });
23
+ module.exports = __toCommonJS(index_exports);
24
+ var import_websocketSO = require("./websocketSO.js");
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var messageRouter_exports = {};
20
+ __export(messageRouter_exports, {
21
+ MessageRouter: () => MessageRouter
22
+ });
23
+ module.exports = __toCommonJS(messageRouter_exports);
24
+ class MessageRouter {
25
+ #subscriptionManager;
26
+ #onEvent;
27
+ #logger;
28
+ /**
29
+ * @param {SubscriptionManager} subscriptionManager - manages pending requests and active subscriptions
30
+ * @param {EventCallback} onEvent - callback invoked when a server event matches an active subscription
31
+ * @param {Logger} logger - logger instance for debug/error output
32
+ */
33
+ constructor(subscriptionManager, onEvent, logger) {
34
+ this.#subscriptionManager = subscriptionManager;
35
+ this.#onEvent = onEvent;
36
+ this.#logger = logger;
37
+ }
38
+ /**
39
+ * Entry point for all messages received from the WebSocket server.
40
+ * Dispatches to the correct handler based on message type.
41
+ * @param {ServerMessage} message - incoming server message
42
+ */
43
+ handleMessage = (message) => {
44
+ switch (message.type) {
45
+ case "subscribe_ack":
46
+ this.#handleSubscribeAck(message);
47
+ break;
48
+ case "unsubscribe_ack":
49
+ this.#handleUnsubscribeAck(message);
50
+ break;
51
+ case "error":
52
+ this.#handleError(message);
53
+ break;
54
+ case "event":
55
+ this.#handleEvent(message);
56
+ break;
57
+ default:
58
+ this.#logger.debug(
59
+ `Unknown message type received: ${message.type}`
60
+ );
61
+ }
62
+ };
63
+ #handleSubscribeAck(message) {
64
+ const { correlationId, subscriptionId } = message;
65
+ const resolved = this.#subscriptionManager.resolvePendingRequest(
66
+ correlationId,
67
+ { subscriptionId }
68
+ );
69
+ if (!resolved) {
70
+ this.#logger.debug(
71
+ `Received subscribe_ack for unknown correlationId: ${correlationId}`
72
+ );
73
+ }
74
+ }
75
+ #handleUnsubscribeAck(message) {
76
+ const { correlationId } = message;
77
+ const resolved = this.#subscriptionManager.resolvePendingRequest(
78
+ correlationId,
79
+ void 0
80
+ );
81
+ if (!resolved) {
82
+ this.#logger.debug(
83
+ `Received unsubscribe_ack for unknown correlationId: ${correlationId}`
84
+ );
85
+ }
86
+ }
87
+ #handleError(message) {
88
+ const { correlationId, errors } = message;
89
+ const errorMessages = errors.map((e) => `${e.code}: ${e.message}`).join("; ");
90
+ const rejected = this.#subscriptionManager.rejectPendingRequest(
91
+ correlationId,
92
+ new Error(errorMessages)
93
+ );
94
+ if (!rejected) {
95
+ this.#logger.debug(
96
+ `Received error for unknown correlationId: ${correlationId}. Errors: ${errorMessages}`
97
+ );
98
+ }
99
+ }
100
+ #handleEvent(message) {
101
+ const { subscriptionId } = message;
102
+ const subscription = this.#subscriptionManager.getSubscription(subscriptionId);
103
+ if (!subscription) {
104
+ this.#logger.debug(
105
+ `Received event for unknown subscriptionId: ${subscriptionId}`
106
+ );
107
+ return;
108
+ }
109
+ this.#onEvent(message, subscription);
110
+ }
111
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "type": "commonjs",
3
+ "sideEffects": false,
4
+ "publishConfig": {
5
+ "access": "public"
6
+ }
7
+ }
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var subscriptionManager_exports = {};
20
+ __export(subscriptionManager_exports, {
21
+ SubscriptionManager: () => SubscriptionManager
22
+ });
23
+ module.exports = __toCommonJS(subscriptionManager_exports);
24
+ const DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
25
+ class SubscriptionManager {
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ #pendingRequests = /* @__PURE__ */ new Map();
28
+ #subscriptions = /* @__PURE__ */ new Map();
29
+ /** Reverse index: composite lookup key → subscriptionId for O(1) duplicate detection. */
30
+ #keyIndex = /* @__PURE__ */ new Map();
31
+ #requestTimeoutMs;
32
+ /**
33
+ * @param {number} [requestTimeoutMs=30000] - timeout in ms before a pending request is auto-rejected
34
+ */
35
+ constructor(requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
36
+ this.#requestTimeoutMs = requestTimeoutMs;
37
+ }
38
+ /**
39
+ * Register a pending request that will be resolved/rejected when the
40
+ * server responds with a message matching the given correlationId.
41
+ * @param {string} correlationId - unique id correlating request to response
42
+ * @param {Function} resolve - callback invoked on success
43
+ * @param {Function} reject - callback invoked on failure or timeout
44
+ */
45
+ addPendingRequest(correlationId, resolve, reject) {
46
+ const existing = this.#pendingRequests.get(correlationId);
47
+ if (existing) {
48
+ clearTimeout(existing.timeoutId);
49
+ existing.reject(
50
+ new Error(`Pending request replaced (correlationId: ${correlationId})`)
51
+ );
52
+ }
53
+ const timeoutId = setTimeout(() => {
54
+ this.#pendingRequests.delete(correlationId);
55
+ reject(
56
+ new Error(
57
+ `Request timed out after ${this.#requestTimeoutMs}ms (correlationId: ${correlationId})`
58
+ )
59
+ );
60
+ }, this.#requestTimeoutMs);
61
+ this.#pendingRequests.set(correlationId, { resolve, reject, timeoutId });
62
+ }
63
+ /**
64
+ * Resolve a pending request with a successful result.
65
+ * @param {string} correlationId - unique id of the pending request
66
+ * @param {*} value - the value to resolve with
67
+ * @returns {boolean} true if the request was found and resolved
68
+ */
69
+ resolvePendingRequest(correlationId, value) {
70
+ const pending = this.#pendingRequests.get(correlationId);
71
+ if (!pending) return false;
72
+ clearTimeout(pending.timeoutId);
73
+ this.#pendingRequests.delete(correlationId);
74
+ pending.resolve(value);
75
+ return true;
76
+ }
77
+ /**
78
+ * Reject a pending request with an error.
79
+ * @param {string} correlationId - unique id of the pending request
80
+ * @param {Error} error - the error to reject with
81
+ * @returns {boolean} true if the request was found and rejected
82
+ */
83
+ rejectPendingRequest(correlationId, error) {
84
+ const pending = this.#pendingRequests.get(correlationId);
85
+ if (!pending) return false;
86
+ clearTimeout(pending.timeoutId);
87
+ this.#pendingRequests.delete(correlationId);
88
+ pending.reject(error);
89
+ return true;
90
+ }
91
+ /**
92
+ * Find an existing subscription matching the given resource criteria and guest.
93
+ * Uses a secondary key index for O(1) lookup.
94
+ * @param {Omit<Subscription, 'subscriptionId' | 'refCount'>} criteria - fields to match
95
+ * @returns {Subscription | undefined} matching subscription or undefined
96
+ */
97
+ findExistingSubscription(criteria) {
98
+ const key = SubscriptionManager.#buildLookupKey(criteria);
99
+ const subscriptionId = this.#keyIndex.get(key);
100
+ if (!subscriptionId) return void 0;
101
+ return this.#subscriptions.get(subscriptionId);
102
+ }
103
+ /**
104
+ * Register a new active subscription. Initialises refCount to 1.
105
+ * @param {Omit<Subscription, 'refCount'>} subscription - subscription data (refCount is set internally)
106
+ */
107
+ addSubscription(subscription) {
108
+ const entry = {
109
+ ...subscription,
110
+ filter: subscription.filter ? JSON.parse(JSON.stringify(subscription.filter)) : void 0,
111
+ refCount: 1
112
+ };
113
+ this.#subscriptions.set(subscription.subscriptionId, entry);
114
+ this.#keyIndex.set(
115
+ SubscriptionManager.#buildLookupKey(entry),
116
+ subscription.subscriptionId
117
+ );
118
+ }
119
+ /**
120
+ * Increment the reference count for an existing subscription.
121
+ * @param {string} subscriptionId - subscription to increment
122
+ * @returns {number} the new refCount, or -1 if the subscription was not found
123
+ */
124
+ incrementRefCount(subscriptionId) {
125
+ const sub = this.#subscriptions.get(subscriptionId);
126
+ if (!sub) return -1;
127
+ sub.refCount += 1;
128
+ return sub.refCount;
129
+ }
130
+ /**
131
+ * Decrement the reference count and remove when it reaches 0.
132
+ * @param {string} subscriptionId - subscription to decrement
133
+ * @returns {number} the new refCount (0 means removed), or -1 if not found
134
+ */
135
+ decrementRefCount(subscriptionId) {
136
+ const sub = this.#subscriptions.get(subscriptionId);
137
+ if (!sub) return -1;
138
+ sub.refCount -= 1;
139
+ if (sub.refCount <= 0) {
140
+ this.#keyIndex.delete(SubscriptionManager.#buildLookupKey(sub));
141
+ this.#subscriptions.delete(subscriptionId);
142
+ return 0;
143
+ }
144
+ return sub.refCount;
145
+ }
146
+ /**
147
+ * Unconditionally remove a subscription regardless of refCount.
148
+ * Used during reconnection to replace old entries.
149
+ * @param {string} subscriptionId - subscription to remove
150
+ * @returns {boolean} true if the subscription existed and was removed
151
+ */
152
+ removeSubscription(subscriptionId) {
153
+ const sub = this.#subscriptions.get(subscriptionId);
154
+ if (sub) {
155
+ this.#keyIndex.delete(SubscriptionManager.#buildLookupKey(sub));
156
+ }
157
+ return this.#subscriptions.delete(subscriptionId);
158
+ }
159
+ /**
160
+ * Retrieve subscription metadata by id.
161
+ * @param {string} subscriptionId - subscription to look up
162
+ * @returns {Subscription | undefined} the subscription, or undefined if not found
163
+ */
164
+ getSubscription(subscriptionId) {
165
+ return this.#subscriptions.get(subscriptionId);
166
+ }
167
+ /**
168
+ * Check whether a subscription is currently active.
169
+ * @param {string} subscriptionId - subscription to check
170
+ * @returns {boolean} true if the subscription exists
171
+ */
172
+ hasSubscription(subscriptionId) {
173
+ return this.#subscriptions.has(subscriptionId);
174
+ }
175
+ /**
176
+ * Returns all active subscriptions.
177
+ * Used for re-subscribing after a reconnection.
178
+ * @returns {Subscription[]} array of active subscriptions
179
+ */
180
+ getAllSubscriptions() {
181
+ return Array.from(this.#subscriptions.values());
182
+ }
183
+ /**
184
+ * Clean up all pending requests and subscriptions.
185
+ * Rejects any outstanding pending requests.
186
+ */
187
+ dispose() {
188
+ this.#pendingRequests.forEach((pending) => {
189
+ clearTimeout(pending.timeoutId);
190
+ pending.reject(new Error("WebSocket scripting object disposed"));
191
+ });
192
+ this.#pendingRequests.clear();
193
+ this.#subscriptions.clear();
194
+ this.#keyIndex.clear();
195
+ }
196
+ /**
197
+ * Compute the canonical lookup key for a set of subscription criteria.
198
+ * Exposed so callers can track in-flight subscribes by the same key.
199
+ * @param {Omit<Subscription, 'subscriptionId' | 'refCount'>} criteria - subscription fields
200
+ * @returns {string} composite lookup key
201
+ */
202
+ static buildLookupKey(criteria) {
203
+ return SubscriptionManager.#buildLookupKey(criteria);
204
+ }
205
+ /**
206
+ * Builds a canonical composite key for O(1) subscription lookup.
207
+ * Uses NUL (\0) as separator to avoid collisions with real values.
208
+ * Filter keys are sorted for order-independent comparison.
209
+ * @param {Pick<Subscription, 'guestId' | 'resource' | 'resourceId' | 'filter'>} sub - subscription fields
210
+ * @returns {string} composite lookup key
211
+ */
212
+ static #buildLookupKey(sub) {
213
+ const { guestId, resource, resourceId, filter } = sub;
214
+ if (resourceId != null) {
215
+ return `${guestId}\0${resource}\0rid:${resourceId}`;
216
+ }
217
+ if (filter != null) {
218
+ const sortedKeys = Object.keys(filter).sort();
219
+ const canonical = sortedKeys.map((k) => `${k}=${filter[k].join(",")}`).join("&");
220
+ return `${guestId}\0${resource}\0f:${canonical}`;
221
+ }
222
+ return `${guestId}\0${resource}`;
223
+ }
224
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+ var types_exports = {};
16
+ module.exports = __toCommonJS(types_exports);