@triveria/wallet 0.0.0-dev-c46c1cf

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.
@@ -0,0 +1,299 @@
1
+ "use strict";
2
+ /**
3
+ * Hand-written client for the `walletNotifications` SSE endpoint.
4
+ *
5
+ * This file is NOT managed by the OpenAPI generator (see .openapi-generator/FILES) -
6
+ * it builds on top of the generated request builder in `api.ts` to provide an
7
+ * ergonomic Node.js client for the `GET /wallets/{wallet_id}/notifications`
8
+ * `text/event-stream` endpoint, including reconnect-with-replay via `Last-Event-ID`.
9
+ */
10
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
11
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
12
+ return new (P || (P = Promise))(function (resolve, reject) {
13
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
14
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
15
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
16
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
17
+ });
18
+ };
19
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
20
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
21
+ var m = o[Symbol.asyncIterator], i;
22
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
23
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
24
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
25
+ };
26
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
27
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
28
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
29
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
30
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
31
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
32
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
33
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
34
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
35
+ function fulfill(value) { resume("next", value); }
36
+ function reject(value) { resume("throw", value); }
37
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
38
+ };
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.WalletNotificationStream = void 0;
44
+ const events_1 = require("events");
45
+ const axios_1 = __importDefault(require("axios"));
46
+ const base_1 = require("./base");
47
+ const common_1 = require("./common");
48
+ const api_1 = require("./api");
49
+ /**
50
+ * Parses a raw byte/text stream into Server-Sent Events per the WHATWG spec
51
+ * (https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation).
52
+ */
53
+ function parseSseStream(source) {
54
+ return __asyncGenerator(this, arguments, function* parseSseStream_1() {
55
+ var _a, e_1, _b, _c;
56
+ let buffer = '';
57
+ let eventType;
58
+ let dataLines = [];
59
+ let id;
60
+ let sawData = false;
61
+ function takeEvent() {
62
+ const event = sawData
63
+ ? { kind: 'event', id, event: eventType, data: dataLines.join('\n') }
64
+ : undefined;
65
+ eventType = undefined;
66
+ dataLines = [];
67
+ sawData = false;
68
+ return event;
69
+ }
70
+ try {
71
+ for (var _d = true, source_1 = __asyncValues(source), source_1_1; source_1_1 = yield __await(source_1.next()), _a = source_1_1.done, !_a; _d = true) {
72
+ _c = source_1_1.value;
73
+ _d = false;
74
+ const chunk = _c;
75
+ buffer += typeof chunk === 'string' ? chunk : chunk.toString('utf8');
76
+ let newlineIndex;
77
+ while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
78
+ let line = buffer.slice(0, newlineIndex);
79
+ buffer = buffer.slice(newlineIndex + 1);
80
+ if (line.endsWith('\r')) {
81
+ line = line.slice(0, -1);
82
+ }
83
+ if (line === '') {
84
+ const event = takeEvent();
85
+ if (event)
86
+ yield yield __await(event);
87
+ continue;
88
+ }
89
+ if (line.startsWith(':')) {
90
+ continue;
91
+ }
92
+ const colonIndex = line.indexOf(':');
93
+ const field = colonIndex === -1 ? line : line.slice(0, colonIndex);
94
+ let value = colonIndex === -1 ? '' : line.slice(colonIndex + 1);
95
+ if (value.startsWith(' '))
96
+ value = value.slice(1);
97
+ switch (field) {
98
+ case 'event':
99
+ eventType = value;
100
+ break;
101
+ case 'data':
102
+ dataLines.push(value);
103
+ sawData = true;
104
+ break;
105
+ case 'id':
106
+ // Per spec, an id field containing a NUL character is ignored.
107
+ if (!value.includes('\u0000'))
108
+ id = value;
109
+ break;
110
+ case 'retry':
111
+ // Unlike `data`/`id`, `retry` is not part of a dispatched event - it
112
+ // just updates the reconnection delay for future reconnects.
113
+ if (/^\d+$/.test(value))
114
+ yield yield __await({ kind: 'retry', delayMs: parseInt(value, 10) });
115
+ break;
116
+ }
117
+ }
118
+ }
119
+ }
120
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
121
+ finally {
122
+ try {
123
+ if (!_d && !_a && (_b = source_1.return)) yield __await(_b.call(source_1));
124
+ }
125
+ finally { if (e_1) throw e_1.error; }
126
+ }
127
+ });
128
+ }
129
+ /**
130
+ * Live stream of wallet notifications over `GET /wallets/{wallet_id}/notifications`.
131
+ *
132
+ * Emits `open`, `notification`, `error` and `close` events, and is itself an
133
+ * async iterable of {@link WalletNotification}:
134
+ *
135
+ * ```ts
136
+ * const stream = new WalletNotificationStream(configuration, walletId);
137
+ * for await (const notification of stream) {
138
+ * console.log(notification);
139
+ * }
140
+ * ```
141
+ *
142
+ * Drops are reconnected automatically (replaying queued notifications via
143
+ * `Last-Event-ID`) unless `options.reconnect` is set to `false`. The current
144
+ * `Last-Event-ID` is exposed via {@link WalletNotificationStream.lastEventId}
145
+ * so it can be persisted and used to resume the stream later, e.g. across
146
+ * process restarts.
147
+ */
148
+ class WalletNotificationStream extends events_1.EventEmitter {
149
+ constructor(configuration, walletId, options = {}) {
150
+ var _a, _b, _c, _d, _e, _f;
151
+ super();
152
+ this.configuration = configuration;
153
+ this.walletId = walletId;
154
+ this.closed = false;
155
+ this.queue = [];
156
+ this.waiters = [];
157
+ this.axiosInstance = (_a = options.axios) !== null && _a !== void 0 ? _a : axios_1.default;
158
+ this.basePath = (_c = (_b = options.basePath) !== null && _b !== void 0 ? _b : configuration.basePath) !== null && _c !== void 0 ? _c : base_1.BASE_PATH;
159
+ this.eventType = options.eventType;
160
+ this.state = options.state;
161
+ this._lastEventId = options.lastEventId;
162
+ this.reconnect = (_d = options.reconnect) !== null && _d !== void 0 ? _d : true;
163
+ this.retryDelayMs = (_e = options.retryDelayMs) !== null && _e !== void 0 ? _e : 3000;
164
+ this.maxRetryDelayMs = (_f = options.maxRetryDelayMs) !== null && _f !== void 0 ? _f : 30000;
165
+ void this.connect();
166
+ }
167
+ /** Stops the stream and prevents further reconnection attempts. */
168
+ close() {
169
+ var _a;
170
+ if (this.closed)
171
+ return;
172
+ this.closed = true;
173
+ if (this.retryTimer)
174
+ clearTimeout(this.retryTimer);
175
+ (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
176
+ this.settleWaiters();
177
+ this.emit('close');
178
+ }
179
+ get isClosed() {
180
+ return this.closed;
181
+ }
182
+ /**
183
+ * The ID of the last SSE event received on this stream (per the `id:` field),
184
+ * or the `lastEventId` the stream was constructed/reconnected with if no event
185
+ * has been received yet.
186
+ *
187
+ * Persist this value and pass it back as `options.lastEventId` when creating a
188
+ * new {@link WalletNotificationStream} (e.g. after a process restart) to resume
189
+ * the stream and replay any notifications queued since that event.
190
+ */
191
+ get lastEventId() {
192
+ return this._lastEventId;
193
+ }
194
+ connect() {
195
+ return __awaiter(this, void 0, void 0, function* () {
196
+ var _a, e_2, _b, _c;
197
+ var _d;
198
+ if (this.closed)
199
+ return;
200
+ this.abortController = new AbortController();
201
+ try {
202
+ const requestArgs = yield (0, api_1.DefaultApiAxiosParamCreator)(this.configuration).walletNotifications(this.walletId, this.eventType, this.state, this._lastEventId, { signal: this.abortController.signal, responseType: 'stream' });
203
+ const request = (0, common_1.createRequestFunction)(requestArgs, this.axiosInstance, this.basePath, this.configuration);
204
+ const response = yield request(this.axiosInstance, this.basePath);
205
+ this.emit('open');
206
+ try {
207
+ for (var _e = true, _f = __asyncValues(parseSseStream(response.data)), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
208
+ _c = _g.value;
209
+ _e = false;
210
+ const message = _c;
211
+ if (this.closed)
212
+ break;
213
+ if (message.kind === 'retry') {
214
+ this.nextRetryDelayMs = message.delayMs;
215
+ continue;
216
+ }
217
+ if (message.id !== undefined)
218
+ this._lastEventId = message.id;
219
+ if (!message.data)
220
+ continue;
221
+ let notification;
222
+ try {
223
+ notification = JSON.parse(message.data);
224
+ }
225
+ catch (_h) {
226
+ this.emit('error', new Error(`Failed to parse wallet notification payload: ${message.data}`));
227
+ continue;
228
+ }
229
+ this.emit('notification', notification);
230
+ this.dispatchToQueue(notification);
231
+ }
232
+ }
233
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
234
+ finally {
235
+ try {
236
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
237
+ }
238
+ finally { if (e_2) throw e_2.error; }
239
+ }
240
+ if (this.closed)
241
+ return;
242
+ this.scheduleReconnect();
243
+ }
244
+ catch (err) {
245
+ if (this.closed)
246
+ return;
247
+ this.emit('error', err instanceof Error ? err : new Error(String(err)));
248
+ const status = (_d = err === null || err === void 0 ? void 0 : err.response) === null || _d === void 0 ? void 0 : _d.status;
249
+ if (status !== undefined && status >= 400 && status < 500) {
250
+ // Client errors (bad wallet ID, invalid/expired token, ...) won't be fixed by retrying.
251
+ this.close();
252
+ return;
253
+ }
254
+ this.scheduleReconnect();
255
+ }
256
+ });
257
+ }
258
+ scheduleReconnect() {
259
+ var _a;
260
+ if (this.closed || !this.reconnect) {
261
+ this.close();
262
+ return;
263
+ }
264
+ const delay = Math.min((_a = this.nextRetryDelayMs) !== null && _a !== void 0 ? _a : this.retryDelayMs, this.maxRetryDelayMs);
265
+ this.retryTimer = setTimeout(() => void this.connect(), delay);
266
+ }
267
+ dispatchToQueue(notification) {
268
+ const waiter = this.waiters.shift();
269
+ if (waiter) {
270
+ waiter({ value: notification, done: false });
271
+ }
272
+ else {
273
+ this.queue.push(notification);
274
+ }
275
+ }
276
+ settleWaiters() {
277
+ while (this.waiters.length) {
278
+ this.waiters.shift()({ value: undefined, done: true });
279
+ }
280
+ }
281
+ [Symbol.asyncIterator]() {
282
+ return {
283
+ next: () => {
284
+ if (this.queue.length) {
285
+ return Promise.resolve({ value: this.queue.shift(), done: false });
286
+ }
287
+ if (this.closed) {
288
+ return Promise.resolve({ value: undefined, done: true });
289
+ }
290
+ return new Promise((resolve) => this.waiters.push(resolve));
291
+ },
292
+ return: () => {
293
+ this.close();
294
+ return Promise.resolve({ value: undefined, done: true });
295
+ },
296
+ };
297
+ }
298
+ }
299
+ exports.WalletNotificationStream = WalletNotificationStream;
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@triveria/wallet",
3
+ "private": false,
4
+ "version": "0.0.0-dev-c46c1cf",
5
+ "description": "",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/triveria-com/wallet-sdk-ts.git"
9
+ },
10
+ "main": "index.js",
11
+ "scripts": {
12
+ "test": "echo \"Error: no test specified\" && exit 1",
13
+ "build": "tsc && cp package.json dist/",
14
+ "gen-docs": "typedoc --plugin typedoc-plugin-markdown --out ts-docs ./index.ts"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "axios": "^1.6.5",
21
+ "jwt-decode": "^4.0.0",
22
+ "node-localstorage": "^3.0.5",
23
+ "ts-localstorage": "^3.1.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^20.10.6",
27
+ "@types/node-localstorage": "^1.3.3",
28
+ "typedoc": "^0.25.7",
29
+ "typedoc-plugin-markdown": "^3.17.1",
30
+ "typescript": "^5.3.3"
31
+ }
32
+ }