webview.io 1.1.0 → 1.2.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.
package/dist/index.js CHANGED
@@ -10,6 +10,42 @@ var __assign = (this && this.__assign) || function () {
10
10
  };
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
24
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
13
49
  var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
14
50
  if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
15
51
  if (ar || !(i in from)) {
@@ -20,6 +56,33 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
20
56
  return to.concat(ar || Array.prototype.slice.call(from));
21
57
  };
22
58
  Object.defineProperty(exports, "__esModule", { value: true });
59
+ // Current protocol version
60
+ var PROTOCOL_VERSION = 1;
61
+ /**
62
+ * The exact fields, in the exact order, that a signature covers.
63
+ *
64
+ * Kept as data rather than written out at each call site because there are
65
+ * three implementations of this protocol — the class signing, the class
66
+ * verifying, and the hand-written bridge that getInjectedJavaScript() injects
67
+ * into the WebView — and they did not agree. The class signed over `size`; the
68
+ * injected bridge left it out of both its sign and its verify. Every signed
69
+ * message the native side sent was therefore refused by the WebView, in
70
+ * silence, for as long as cryptoAuth has existed.
71
+ *
72
+ * The injected bridge now interpolates this same array, so the three cannot
73
+ * drift apart again without changing one line.
74
+ */
75
+ var CANONICAL_FIELDS = ['v', '_event', 'payload', 'cid', 'timestamp', 'size', 'token'];
76
+ function canonicalMessage(data, ts, nonce) {
77
+ var canonical = {};
78
+ for (var _i = 0, CANONICAL_FIELDS_1 = CANONICAL_FIELDS; _i < CANONICAL_FIELDS_1.length; _i++) {
79
+ var field = CANONICAL_FIELDS_1[_i];
80
+ canonical[field] = data[field];
81
+ }
82
+ canonical.ts = ts;
83
+ canonical.nonce = nonce;
84
+ return JSON.stringify(canonical);
85
+ }
23
86
  function newObject(data) {
24
87
  return JSON.parse(JSON.stringify(data));
25
88
  }
@@ -40,6 +103,80 @@ function sanitizePayload(payload, maxSize) {
40
103
  // Basic sanitization - remove functions and undefined values
41
104
  return JSON.parse(JSON.stringify(payload));
42
105
  }
106
+ function constantTimeEqual(a, b) {
107
+ if (a.length !== b.length)
108
+ return false;
109
+ var out = 0;
110
+ for (var i = 0; i < a.length; i++)
111
+ out |= a.charCodeAt(i) ^ b.charCodeAt(i);
112
+ return out === 0;
113
+ }
114
+ function getGlobalCrypto() {
115
+ return (typeof crypto !== 'undefined'
116
+ ? crypto
117
+ : (typeof window !== 'undefined' && window.crypto)
118
+ || (typeof globalThis !== 'undefined' && globalThis.crypto));
119
+ }
120
+ function randomHex(bytes) {
121
+ try {
122
+ var globalCrypto = getGlobalCrypto();
123
+ if (globalCrypto && typeof globalCrypto.getRandomValues === 'function') {
124
+ var buf = new Uint8Array(bytes);
125
+ globalCrypto.getRandomValues(buf);
126
+ return Array.from(buf).map(function (b) { return b.toString(16).padStart(2, '0'); }).join('');
127
+ }
128
+ }
129
+ catch (_a) { }
130
+ // Fallback (NOT cryptographically strong)
131
+ return Array.from({ length: bytes }, function () { return Math.floor(Math.random() * 256).toString(16).padStart(2, '0'); }).join('');
132
+ }
133
+ function hmacSha256Base64Url(secret, message) {
134
+ return __awaiter(this, void 0, void 0, function () {
135
+ var globalCrypto, subtle, enc, key, sig, bytes, bin, i, b64, _a, nodeCrypto, b64;
136
+ return __generator(this, function (_b) {
137
+ switch (_b.label) {
138
+ case 0:
139
+ _b.trys.push([0, 4, , 5]);
140
+ globalCrypto = getGlobalCrypto();
141
+ subtle = globalCrypto === null || globalCrypto === void 0 ? void 0 : globalCrypto.subtle;
142
+ if (!(subtle && typeof subtle.importKey === 'function')) return [3 /*break*/, 3];
143
+ enc = new TextEncoder();
144
+ return [4 /*yield*/, subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])];
145
+ case 1:
146
+ key = _b.sent();
147
+ return [4 /*yield*/, subtle.sign('HMAC', key, enc.encode(message))];
148
+ case 2:
149
+ sig = _b.sent();
150
+ bytes = new Uint8Array(sig);
151
+ bin = '';
152
+ for (i = 0; i < bytes.length; i++)
153
+ bin += String.fromCharCode(bytes[i]);
154
+ b64 = btoa(bin);
155
+ return [2 /*return*/, b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '')];
156
+ case 3: return [3 /*break*/, 5];
157
+ case 4:
158
+ _a = _b.sent();
159
+ return [3 /*break*/, 5];
160
+ case 5:
161
+ // Node.js (commonjs) - optional (React Native metro can provide crypto polyfills in some setups)
162
+ try {
163
+ nodeCrypto = globalThis.__wio_node_crypto
164
+ || (globalThis.__wio_node_crypto = (typeof globalThis.require === 'function'
165
+ ? globalThis.require('crypto')
166
+ : undefined));
167
+ if (!nodeCrypto)
168
+ throw new Error('node crypto unavailable');
169
+ b64 = nodeCrypto.createHmac('sha256', secret).update(message).digest('base64');
170
+ return [2 /*return*/, b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '')];
171
+ }
172
+ catch (_c) {
173
+ throw new Error('No crypto implementation available for HMAC-SHA256');
174
+ }
175
+ return [2 /*return*/];
176
+ }
177
+ });
178
+ });
179
+ }
43
180
  var ackId = function () {
44
181
  var rmin = 100000, rmax = 999999, timestamp = Date.now(), random = Math.floor(Math.random() * (rmax - rmin + 1) + rmin);
45
182
  return "".concat(timestamp, "_").concat(random);
@@ -80,6 +217,7 @@ var WIO = /** @class */ (function () {
80
217
  this.reconnectAttempts = 0;
81
218
  this.maxReconnectAttempts = 5;
82
219
  this.connectionAttempts = 0;
220
+ this.seenNonces = new Map();
83
221
  if (options && typeof options !== 'object')
84
222
  throw new Error('Invalid Options');
85
223
  this.options = __assign({ debug: false, heartbeatInterval: 30000, connectionTimeout: 10000, maxMessageSize: 1024 * 1024, maxMessagesPerSecond: 100, autoReconnect: true, messageQueueSize: 50, connectionPingInterval: 2000, maxConnectionAttempts: 5 }, options);
@@ -88,6 +226,102 @@ var WIO = /** @class */ (function () {
88
226
  if (options.type)
89
227
  this.peer.type = options.type;
90
228
  }
229
+ WIO.prototype.cryptoCfg = function () {
230
+ var _a, _b;
231
+ if (!this.options.cryptoAuth)
232
+ return undefined;
233
+ return {
234
+ secret: this.options.cryptoAuth.secret,
235
+ requireSigned: !!this.options.cryptoAuth.requireSigned,
236
+ maxSkewMs: (_a = this.options.cryptoAuth.maxSkewMs) !== null && _a !== void 0 ? _a : 2 * 60 * 1000,
237
+ replayWindowSize: (_b = this.options.cryptoAuth.replayWindowSize) !== null && _b !== void 0 ? _b : 500
238
+ };
239
+ };
240
+ /**
241
+ * Forget nonces that can no longer be replayed, and only then cap the map.
242
+ *
243
+ * Age is what decides replayability: a captured message is refused once its
244
+ * `ts` falls outside maxSkewMs, so a nonce is only worth keeping that long.
245
+ * Pruning purely by count made the two defaults contradict each other — 500
246
+ * remembered nonces at the default 100 messages a second is five seconds of
247
+ * history guarding a two-minute acceptance window.
248
+ */
249
+ WIO.prototype.pruneNonces = function (maxSize) {
250
+ var _this = this;
251
+ var _a, _b;
252
+ var cutoff = Date.now() - ((_b = (_a = this.cryptoCfg()) === null || _a === void 0 ? void 0 : _a.maxSkewMs) !== null && _b !== void 0 ? _b : 2 * 60 * 1000), stale = [];
253
+ this.seenNonces.forEach(function (ts, nonce) { ts < cutoff && stale.push(nonce); });
254
+ stale.forEach(function (nonce) { return _this.seenNonces.delete(nonce); });
255
+ if (this.seenNonces.size <= maxSize)
256
+ return;
257
+ this.fire('error', {
258
+ type: 'REPLAY_WINDOW_EXCEEDED',
259
+ remembered: this.seenNonces.size,
260
+ maxSize: maxSize
261
+ });
262
+ var toRemove = this.seenNonces.size - maxSize, keys = Array.from(this.seenNonces.keys());
263
+ for (var k = 0; k < toRemove && k < keys.length; k++)
264
+ this.seenNonces.delete(keys[k]);
265
+ };
266
+ WIO.prototype.signOutgoing = function (messageData) {
267
+ return __awaiter(this, void 0, void 0, function () {
268
+ var cfg, ts, nonce, sig;
269
+ return __generator(this, function (_a) {
270
+ switch (_a.label) {
271
+ case 0:
272
+ cfg = this.cryptoCfg();
273
+ if (!cfg)
274
+ return [2 /*return*/, undefined];
275
+ ts = Date.now(), nonce = randomHex(16);
276
+ return [4 /*yield*/, hmacSha256Base64Url(cfg.secret, canonicalMessage(messageData, ts, nonce))];
277
+ case 1:
278
+ sig = _a.sent();
279
+ return [2 /*return*/, { alg: 'HMAC-SHA256', ts: ts, nonce: nonce, sig: sig }];
280
+ }
281
+ });
282
+ });
283
+ };
284
+ WIO.prototype.verifyIncomingAuth = function (data) {
285
+ return __awaiter(this, void 0, void 0, function () {
286
+ var cfg, _a, alg, ts, nonce, sig, now, expected;
287
+ return __generator(this, function (_b) {
288
+ switch (_b.label) {
289
+ case 0:
290
+ cfg = this.cryptoCfg();
291
+ if (!cfg)
292
+ return [2 /*return*/, true];
293
+ if (!data.auth) {
294
+ return [2 /*return*/, !cfg.requireSigned];
295
+ }
296
+ _a = data.auth, alg = _a.alg, ts = _a.ts, nonce = _a.nonce, sig = _a.sig;
297
+ if (alg !== 'HMAC-SHA256')
298
+ return [2 /*return*/, false];
299
+ if (typeof ts !== 'number' || typeof nonce !== 'string' || typeof sig !== 'string')
300
+ return [2 /*return*/, false];
301
+ now = Date.now();
302
+ if (Math.abs(now - ts) > cfg.maxSkewMs)
303
+ return [2 /*return*/, false
304
+ // The nonce is recorded only once the signature is known good: burning it
305
+ // here let an unsigned or badly signed message consume the nonce of a
306
+ // legitimate one still in flight.
307
+ ];
308
+ // The nonce is recorded only once the signature is known good: burning it
309
+ // here let an unsigned or badly signed message consume the nonce of a
310
+ // legitimate one still in flight.
311
+ if (this.seenNonces.has(nonce))
312
+ return [2 /*return*/, false];
313
+ return [4 /*yield*/, hmacSha256Base64Url(cfg.secret, canonicalMessage(data, ts, nonce))];
314
+ case 1:
315
+ expected = _b.sent();
316
+ if (!constantTimeEqual(expected, sig))
317
+ return [2 /*return*/, false];
318
+ this.seenNonces.set(nonce, ts);
319
+ this.pruneNonces(cfg.replayWindowSize);
320
+ return [2 /*return*/, true];
321
+ }
322
+ });
323
+ });
324
+ };
91
325
  WIO.prototype.debug = function () {
92
326
  var args = [];
93
327
  for (var _i = 0; _i < arguments.length; _i++) {
@@ -340,12 +574,29 @@ var WIO = /** @class */ (function () {
340
574
  * Handle incoming message from WebView
341
575
  */
342
576
  WIO.prototype.handleMessage = function (event) {
577
+ var _this = this;
343
578
  try {
344
579
  var data = JSON.parse(event.nativeEvent.data);
345
580
  // Enhanced security: check valid message structure
346
581
  if (typeof data !== 'object' || !data.hasOwnProperty('_event'))
347
582
  return;
348
- var _a = data, _event = _a._event, payload = _a.payload, cid = _a.cid, timestamp = _a.timestamp, token = _a.token;
583
+ var _a = data, v = _a.v, _event_1 = _a._event, payload_1 = _a.payload, cid_1 = _a.cid, timestamp = _a.timestamp, token = _a.token;
584
+ /**
585
+ * A peer that predates versioning sends no `v`, so absence reads as 1
586
+ * rather than as a refusal. Only a peer speaking a NEWER protocol than
587
+ * this build understands is turned away.
588
+ */
589
+ var messageVersion = v || 1;
590
+ if (messageVersion > PROTOCOL_VERSION) {
591
+ this.fire('error', {
592
+ type: 'UNSUPPORTED_VERSION',
593
+ received: messageVersion,
594
+ supported: PROTOCOL_VERSION
595
+ });
596
+ return;
597
+ }
598
+ if (!this.peer.protocolVersion || this.peer.protocolVersion < messageVersion)
599
+ this.peer.protocolVersion = messageVersion;
349
600
  // Validate origin if specified
350
601
  if (this.peer.origin && event.nativeEvent && 'origin' in event.nativeEvent) {
351
602
  var messageOrigin = event.nativeEvent.origin;
@@ -355,18 +606,18 @@ var WIO = /** @class */ (function () {
355
606
  }
356
607
  }
357
608
  // Handle heartbeat responses
358
- if (_event === '__heartbeat_response') {
609
+ if (_event_1 === '__heartbeat_response') {
359
610
  this.peer.lastHeartbeat = Date.now();
360
611
  return;
361
612
  }
362
613
  // Handle heartbeat requests
363
- if (_event === '__heartbeat') {
614
+ if (_event_1 === '__heartbeat') {
364
615
  this.emit('__heartbeat_response', { timestamp: Date.now() });
365
616
  this.peer.lastHeartbeat = Date.now();
366
617
  return;
367
618
  }
368
619
  // Handle embedded ready announcement
369
- if (_event === '__embedded_ready') {
620
+ if (_event_1 === '__embedded_ready') {
370
621
  this.peer.embeddedReady = true;
371
622
  this.debug("[".concat(this.peer.type, "] Embedded peer ready"));
372
623
  // If we're WEBVIEW and not connected, send ping
@@ -376,13 +627,13 @@ var WIO = /** @class */ (function () {
376
627
  return;
377
628
  }
378
629
  // Handle webview ready signal
379
- if (_event === '__webview_ready') {
630
+ if (_event_1 === '__webview_ready') {
380
631
  this.debug("[".concat(this.peer.type, "] WebView peer ready"));
381
632
  return;
382
633
  }
383
- this.debug("[".concat(this.peer.type, "] Message: ").concat(_event), payload || '');
634
+ this.debug("[".concat(this.peer.type, "] Message: ").concat(_event_1), payload_1 || '');
384
635
  // Handshake: ping event
385
- if (_event === 'ping') {
636
+ if (_event_1 === 'ping') {
386
637
  // EMBEDDED receives ping from WEBVIEW
387
638
  if (this.peer.type === 'EMBEDDED') {
388
639
  this.connectionToken = token;
@@ -393,7 +644,7 @@ var WIO = /** @class */ (function () {
393
644
  return;
394
645
  }
395
646
  // Handshake: pong event
396
- if (_event === 'pong') {
647
+ if (_event_1 === 'pong') {
397
648
  // WEBVIEW receives pong from EMBEDDED
398
649
  if (this.peer.type === 'WEBVIEW') {
399
650
  // Validate token if provided
@@ -416,7 +667,7 @@ var WIO = /** @class */ (function () {
416
667
  return;
417
668
  }
418
669
  // Handshake: connection ack
419
- if (_event === '__connection_ack') {
670
+ if (_event_1 === '__connection_ack') {
420
671
  // EMBEDDED receives ack from WEBVIEW
421
672
  if (this.peer.type === 'EMBEDDED') {
422
673
  // Validate token if provided
@@ -435,29 +686,63 @@ var WIO = /** @class */ (function () {
435
686
  }
436
687
  return;
437
688
  }
689
+ // Cryptographic authentication (optional)
690
+ if (this.options.cryptoAuth) {
691
+ this.verifyIncomingAuth(data)
692
+ .then(function (ok) {
693
+ if (!ok) {
694
+ _this.fire('error', { type: 'AUTH_FAILED', event: _event_1 });
695
+ return;
696
+ }
697
+ // Optional application-level incoming validation (non-reserved events only)
698
+ if (!RESERVED_EVENTS.includes(_event_1)) {
699
+ if (_this.options.allowedIncomingEvents
700
+ && !_this.options.allowedIncomingEvents.includes(_event_1)) {
701
+ _this.fire('error', {
702
+ type: 'DISALLOWED_EVENT',
703
+ direction: 'incoming',
704
+ event: _event_1
705
+ });
706
+ return;
707
+ }
708
+ if (_this.options.validateIncoming
709
+ && !_this.options.validateIncoming(_event_1, payload_1)) {
710
+ _this.fire('error', {
711
+ type: 'INVALID_MESSAGE',
712
+ direction: 'incoming',
713
+ event: _event_1
714
+ });
715
+ return;
716
+ }
717
+ }
718
+ _this.fire(_event_1, payload_1, cid_1);
719
+ })
720
+ .catch(function (error) { return _this.fire('error', { type: 'AUTH_ERROR', event: _event_1, error: String(error) }); });
721
+ return;
722
+ }
438
723
  // Optional application-level incoming validation (non-reserved events only)
439
- if (!RESERVED_EVENTS.includes(_event)) {
724
+ if (!RESERVED_EVENTS.includes(_event_1)) {
440
725
  if (this.options.allowedIncomingEvents
441
- && !this.options.allowedIncomingEvents.includes(_event)) {
726
+ && !this.options.allowedIncomingEvents.includes(_event_1)) {
442
727
  this.fire('error', {
443
728
  type: 'DISALLOWED_EVENT',
444
729
  direction: 'incoming',
445
- event: _event
730
+ event: _event_1
446
731
  });
447
732
  return;
448
733
  }
449
734
  if (this.options.validateIncoming
450
- && !this.options.validateIncoming(_event, payload)) {
735
+ && !this.options.validateIncoming(_event_1, payload_1)) {
451
736
  this.fire('error', {
452
737
  type: 'INVALID_MESSAGE',
453
738
  direction: 'incoming',
454
- event: _event
739
+ event: _event_1
455
740
  });
456
741
  return;
457
742
  }
458
743
  }
459
744
  // Fire available event listeners
460
- this.fire(_event, payload, cid);
745
+ this.fire(_event_1, payload_1, cid_1);
461
746
  }
462
747
  catch (error) {
463
748
  this.debug("[".concat(this.peer.type, "] Message handling error:"), error);
@@ -546,6 +831,7 @@ var WIO = /** @class */ (function () {
546
831
  });
547
832
  }
548
833
  var messageData = {
834
+ v: PROTOCOL_VERSION,
549
835
  _event: _event,
550
836
  payload: sanitizedPayload,
551
837
  cid: cid,
@@ -568,6 +854,105 @@ var WIO = /** @class */ (function () {
568
854
  }
569
855
  return this;
570
856
  };
857
+ /**
858
+ * Send a signed message (HMAC-SHA256) when `options.cryptoAuth` is configured.
859
+ * This is async because WebCrypto signing is async.
860
+ */
861
+ WIO.prototype.emitSigned = function (_event, payload, fn) {
862
+ var _a;
863
+ return __awaiter(this, void 0, void 0, function () {
864
+ var sanitizedPayload, cid, ackFunction_2, unsigned, auth, messageData, error_1;
865
+ return __generator(this, function (_b) {
866
+ switch (_b.label) {
867
+ case 0:
868
+ if (!this.checkRateLimit())
869
+ return [2 /*return*/, this];
870
+ if (!this.options.cryptoAuth) {
871
+ this.emit(_event, payload, fn);
872
+ return [2 /*return*/, this];
873
+ }
874
+ if (!this.isConnected() && !RESERVED_EVENTS.includes(_event)) {
875
+ this.queueMessage(_event, payload, fn);
876
+ return [2 /*return*/, this];
877
+ }
878
+ if (!this.peer.webViewRef) {
879
+ this.fire('error', { type: 'NO_CONNECTION', event: _event });
880
+ return [2 /*return*/, this];
881
+ }
882
+ if (typeof payload == 'function') {
883
+ fn = payload;
884
+ payload = undefined;
885
+ }
886
+ _b.label = 1;
887
+ case 1:
888
+ _b.trys.push([1, 3, , 4]);
889
+ sanitizedPayload = payload
890
+ ? sanitizePayload(payload, this.options.maxMessageSize)
891
+ : payload;
892
+ cid = void 0;
893
+ if (typeof fn === 'function') {
894
+ ackFunction_2 = fn;
895
+ cid = ackId();
896
+ this.once("".concat(_event, "--").concat(cid, "--@ack"), function (_a) {
897
+ var error = _a.error, args = _a.args;
898
+ return ackFunction_2.apply(void 0, __spreadArray([error], args, false));
899
+ });
900
+ }
901
+ unsigned = {
902
+ v: PROTOCOL_VERSION,
903
+ _event: _event,
904
+ payload: sanitizedPayload,
905
+ cid: cid,
906
+ timestamp: Date.now(),
907
+ size: getMessageSize(sanitizedPayload),
908
+ token: RESERVED_EVENTS.includes(_event) ? this.connectionToken : undefined
909
+ };
910
+ return [4 /*yield*/, this.signOutgoing(unsigned)];
911
+ case 2:
912
+ auth = _b.sent();
913
+ messageData = __assign(__assign({}, unsigned), { auth: auth });
914
+ (_a = this.peer.webViewRef.current) === null || _a === void 0 ? void 0 : _a.postMessage(JSON.stringify(newObject(messageData)));
915
+ return [3 /*break*/, 4];
916
+ case 3:
917
+ error_1 = _b.sent();
918
+ this.debug("[".concat(this.peer.type, "] EmitSigned error:"), error_1);
919
+ this.fire('error', {
920
+ type: 'EMIT_ERROR',
921
+ event: _event,
922
+ error: error_1 instanceof Error ? error_1.message : String(error_1)
923
+ });
924
+ typeof fn === 'function'
925
+ && fn(error_1 instanceof Error ? error_1.message : String(error_1));
926
+ return [3 /*break*/, 4];
927
+ case 4: return [2 /*return*/, this];
928
+ }
929
+ });
930
+ });
931
+ };
932
+ WIO.prototype.emitAsyncSigned = function (_event, payload, timeout) {
933
+ if (timeout === void 0) { timeout = 5000; }
934
+ return __awaiter(this, void 0, void 0, function () {
935
+ var _this = this;
936
+ return __generator(this, function (_a) {
937
+ return [2 /*return*/, new Promise(function (resolve, reject) {
938
+ var timeoutId = setTimeout(function () { return reject(new Error("Event '".concat(_event, "' acknowledgment timeout after ").concat(timeout, "ms"))); }, timeout);
939
+ _this.emitSigned(_event, payload, function (error) {
940
+ var args = [];
941
+ for (var _i = 1; _i < arguments.length; _i++) {
942
+ args[_i - 1] = arguments[_i];
943
+ }
944
+ clearTimeout(timeoutId);
945
+ error
946
+ ? reject(new Error(typeof error === 'string' ? error : 'Ack error'))
947
+ : resolve(args.length === 0 ? undefined : args.length === 1 ? args[0] : args);
948
+ }).catch(function (err) {
949
+ clearTimeout(timeoutId);
950
+ reject(err);
951
+ });
952
+ })];
953
+ });
954
+ });
955
+ };
571
956
  WIO.prototype.on = function (_event, fn) {
572
957
  // Add Event listener
573
958
  if (!this.Events[_event])
@@ -710,7 +1095,9 @@ var WIO = /** @class */ (function () {
710
1095
  * NOTE: Does not auto-initialize - page must call window._wio.listen()
711
1096
  */
712
1097
  WIO.prototype.getInjectedJavaScript = function () {
713
- return "\n (function() {\n try {\n console.debug('[EMBEDDED] Initializing WIO bridge...')\n \n const RESERVED_EVENTS = [\n 'ping',\n 'pong',\n '__heartbeat',\n '__heartbeat_response',\n '__embedded_ready',\n '__connection_ack',\n '__webview_ready'\n ]\n \n // Use closure variable to avoid 'this' binding issues\n window._wio = {\n type: 'EMBEDDED',\n connected: false,\n Events: {},\n messageQueue: [],\n connectionToken: null,\n setupComplete: false,\n\n listen: function(){\n if( this.setupComplete ){\n console.warn('[EMBEDDED] Already listening')\n return this\n }\n\n console.debug('[EMBEDDED] Setting up message listeners...')\n\n // Listen to messages from React Native\n window.addEventListener('message', function( event ){\n try {\n const message = typeof event.data === 'string' ? JSON.parse( event.data ) : event.data\n window._wio.handleMessage( message )\n }\n catch( error ){ console.error('[EMBEDDED] Parse error:', error ) }\n })\n \n // Android support\n if( typeof document !== 'undefined' ){\n document.addEventListener('message', function( event ){\n try {\n const message = typeof event.data === 'string' ? JSON.parse( event.data ) : event.data\n window._wio.handleMessage( message )\n }\n catch( error ){ console.error('[EMBEDDED] Parse error:', error ) }\n })\n }\n\n this.setupComplete = true\n console.debug('[EMBEDDED] Setup complete, starting ready announcements')\n\n // Start announcing readiness\n this.announceReady()\n\n return this\n },\n \n ackId: function(){\n const\n rmin = 100000,\n rmax = 999999,\n timestamp = Date.now(),\n random = Math.floor( Math.random() * ( rmax - rmin + 1 ) + rmin )\n\n return timestamp + '_' + random\n },\n \n fire: function( _event, payload, cid ){\n if( !window._wio.Events[_event] && !window._wio.Events[_event + '--@once'] ){\n console.debug('[EMBEDDED] No listener for:', _event)\n return\n }\n \n const ackFn = cid\n ? ( error, ...args ) => window._wio.emit( _event + '--' + cid + '--@ack', { error: error || false, args } )\n : undefined\n \n let listeners = []\n if( window._wio.Events[_event + '--@once'] ){\n _event += '--@once'\n listeners = window._wio.Events[_event]\n\n delete window._wio.Events[_event]\n }\n else listeners = window._wio.Events[_event] || []\n \n listeners.forEach( fn => {\n try { payload !== undefined ? fn( payload, ackFn ) : fn( ackFn ) }\n catch( error ){ console.error('[EMBEDDED] Listener error:', error ) }\n })\n },\n \n emit: function( _event, payload, fn ){\n if( typeof payload === 'function' ){\n fn = payload\n payload = undefined\n }\n \n if( !window._wio.connected && !RESERVED_EVENTS.includes(_event) ){\n window._wio.messageQueue.push({ _event, payload, fn, timestamp: Date.now() })\n console.debug('[EMBEDDED] Queued message:', _event)\n return\n }\n \n try {\n let cid\n if( typeof fn === 'function' ){\n cid = window._wio.ackId()\n window._wio.once( _event + '--' + cid + '--@ack', ({ error, args }) => fn( error, ...args ) )\n }\n \n const messageData = {\n _event,\n payload,\n cid,\n timestamp: Date.now(),\n token: RESERVED_EVENTS.includes(_event) ? window._wio.connectionToken : undefined\n }\n \n if( typeof window.ReactNativeWebView !== 'undefined' )\n window.ReactNativeWebView.postMessage( JSON.stringify( messageData ) )\n else console.error('[EMBEDDED] ReactNativeWebView not available')\n }\n catch( error ){\n console.error('[EMBEDDED] Emit error:', error )\n typeof fn === 'function' && fn( String(error) )\n }\n },\n \n on: function( _event, fn ){\n if( !window._wio.Events[_event] ) window._wio.Events[_event] = []\n window._wio.Events[_event].push( fn )\n\n return window._wio\n },\n \n once: function( _event, fn ){\n _event += '--@once'\n if( !window._wio.Events[_event] ) window._wio.Events[_event] = []\n\n window._wio.Events[_event].push( fn )\n\n return window._wio\n },\n \n off: function( _event, fn ){\n if( fn && window._wio.Events[_event] ){\n const index = window._wio.Events[_event].indexOf( fn )\n if( index > -1 ){\n window._wio.Events[_event].splice( index, 1 )\n if( window._wio.Events[_event].length === 0 ) delete window._wio.Events[_event]\n }\n }\n else delete window._wio.Events[_event]\n\n return window._wio\n },\n \n processMessageQueue: function(){\n if( !window._wio.connected || window._wio.messageQueue.length === 0 ) return\n \n console.debug('[EMBEDDED] Processing', window._wio.messageQueue.length, 'queued messages')\n const queue = [ ...window._wio.messageQueue ]\n window._wio.messageQueue = []\n \n queue.forEach( msg => {\n try { window._wio.emit( msg._event, msg.payload, msg.fn ) }\n catch( error ){ console.error('[EMBEDDED] Queue process error:', error ) }\n })\n },\n \n handleMessage: function( data ){\n if( !data || !data._event ) return\n \n const { _event, payload, cid, token } = data\n \n console.debug('[EMBEDDED] Received:', _event )\n \n // Handle heartbeat response\n if( _event === '__heartbeat_response' )\n return\n \n // Handle heartbeat request\n if( _event === '__heartbeat' ){\n window._wio.emit('__heartbeat_response', { timestamp: Date.now() })\n return\n }\n \n // Handle webview ready signal\n if( _event === '__webview_ready' ){\n console.debug('[EMBEDDED] WebView ready signal received')\n return\n }\n \n // Handle ping from WEBVIEW\n if( _event === 'ping' ){\n console.debug('[EMBEDDED] Received ping, sending pong')\n window._wio.connectionToken = token\n\n window._wio.emit('pong', { token: window._wio.connectionToken })\n return\n }\n \n // Handle connection acknowledgment\n if( _event === '__connection_ack' ){\n if( token && token !== window._wio.connectionToken ){\n console.error('[EMBEDDED] Invalid connection token in ack')\n return\n }\n \n console.debug('[EMBEDDED] Connection established (received ack)')\n\n window._wio.connected = true\n window._wio.processMessageQueue()\n window._wio.fire('connect')\n\n return\n }\n \n // Fire event listeners\n window._wio.fire( _event, payload, cid )\n },\n \n announceReady: function(){\n let attempts = 0\n const maxAttempts = 10\n const interval = 1000\n \n console.debug('[EMBEDDED] Starting ready announcements')\n \n const announce = () => {\n if( window._wio.connected ){\n console.debug('[EMBEDDED] Connected, stopping announcements')\n return\n }\n \n attempts++\n if( attempts > maxAttempts ){\n console.debug('[EMBEDDED] Max announcement attempts reached')\n return\n }\n \n console.debug('[EMBEDDED] Announcing ready (attempt', attempts + '/' + maxAttempts + ')')\n window._wio.emit('__embedded_ready')\n \n setTimeout( announce, interval )\n }\n \n announce()\n }\n }\n\n console.debug('[EMBEDDED] WIO bridge ready. Call window._wio.listen() to connect.')\n }\n catch( error ){\n console.error('[EMBEDDED] Setup failed:', error )\n \n // Create minimal fallback\n window._wio = {\n error: error.toString(),\n listen: function(){ console.error('[EMBEDDED] WIO failed to initialize') },\n emit: function(){ console.error('[EMBEDDED] WIO failed to initialize') },\n on: function(){},\n once: function(){},\n off: function(){}\n }\n }\n\n true\n })()\n ";
1098
+ var _a, _b, _c, _d, _e;
1099
+ var authSecret = (_a = this.options.cryptoAuth) === null || _a === void 0 ? void 0 : _a.secret;
1100
+ return "\n (function() {\n try {\n console.debug('[EMBEDDED] Initializing WIO bridge...')\n \n const RESERVED_EVENTS = [\n 'ping',\n 'pong',\n '__heartbeat',\n '__heartbeat_response',\n '__embedded_ready',\n '__connection_ack',\n '__webview_ready'\n ]\n \n // Use closure variable to avoid 'this' binding issues\n window._wio = {\n type: 'EMBEDDED',\n connected: false,\n Events: {},\n messageQueue: [],\n connectionToken: null,\n authSecret: ".concat(authSecret ? JSON.stringify(authSecret) : 'null', ",\n setupComplete: false,\n seenNonces: new Map(),\n maxSkewMs: ").concat(((_c = (_b = this.options.cryptoAuth) === null || _b === void 0 ? void 0 : _b.maxSkewMs) !== null && _c !== void 0 ? _c : 2 * 60 * 1000), ",\n replayWindowSize: ").concat(((_e = (_d = this.options.cryptoAuth) === null || _d === void 0 ? void 0 : _d.replayWindowSize) !== null && _e !== void 0 ? _e : 500), ",\n\n listen: function(){\n if( this.setupComplete ){\n console.warn('[EMBEDDED] Already listening')\n return this\n }\n\n console.debug('[EMBEDDED] Setting up message listeners...')\n\n // Listen to messages from React Native\n window.addEventListener('message', function( event ){\n try {\n const message = typeof event.data === 'string' ? JSON.parse( event.data ) : event.data\n window._wio.handleMessage( message )\n }\n catch( error ){ console.error('[EMBEDDED] Parse error:', error ) }\n })\n \n // Android support\n if( typeof document !== 'undefined' ){\n document.addEventListener('message', function( event ){\n try {\n const message = typeof event.data === 'string' ? JSON.parse( event.data ) : event.data\n window._wio.handleMessage( message )\n }\n catch( error ){ console.error('[EMBEDDED] Parse error:', error ) }\n })\n }\n\n this.setupComplete = true\n console.debug('[EMBEDDED] Setup complete, starting ready announcements')\n\n // Start announcing readiness\n this.announceReady()\n\n return this\n },\n \n ackId: function(){\n const\n rmin = 100000,\n rmax = 999999,\n timestamp = Date.now(),\n random = Math.floor( Math.random() * ( rmax - rmin + 1 ) + rmin )\n\n return timestamp + '_' + random\n },\n \n fire: function( _event, payload, cid ){\n if( !window._wio.Events[_event] && !window._wio.Events[_event + '--@once'] ){\n console.debug('[EMBEDDED] No listener for:', _event)\n return\n }\n \n const ackFn = cid\n ? ( error, ...args ) => window._wio.emit( _event + '--' + cid + '--@ack', { error: error || false, args } )\n : undefined\n \n let listeners = []\n if( window._wio.Events[_event + '--@once'] ){\n _event += '--@once'\n listeners = window._wio.Events[_event]\n\n delete window._wio.Events[_event]\n }\n else listeners = window._wio.Events[_event] || []\n \n listeners.forEach( fn => {\n try { payload !== undefined ? fn( payload, ackFn ) : fn( ackFn ) }\n catch( error ){ console.error('[EMBEDDED] Listener error:', error ) }\n })\n },\n \n emit: function( _event, payload, fn ){\n if( typeof payload === 'function' ){\n fn = payload\n payload = undefined\n }\n \n if( !window._wio.connected && !RESERVED_EVENTS.includes(_event) ){\n window._wio.messageQueue.push({ _event, payload, fn, timestamp: Date.now() })\n console.debug('[EMBEDDED] Queued message:', _event)\n return\n }\n \n try {\n let cid\n if( typeof fn === 'function' ){\n cid = window._wio.ackId()\n window._wio.once( _event + '--' + cid + '--@ack', ({ error, args }) => fn( error, ...args ) )\n }\n \n const messageData = {\n _event,\n payload,\n cid,\n timestamp: Date.now(),\n token: RESERVED_EVENTS.includes(_event) ? window._wio.connectionToken : undefined\n }\n \n if( typeof window.ReactNativeWebView !== 'undefined' )\n window.ReactNativeWebView.postMessage( JSON.stringify( messageData ) )\n else console.error('[EMBEDDED] ReactNativeWebView not available')\n }\n catch( error ){\n console.error('[EMBEDDED] Emit error:', error )\n typeof fn === 'function' && fn( String(error) )\n }\n },\n\n emitSigned: async function( _event, payload, fn ){\n if( typeof payload === 'function' ){\n fn = payload\n payload = undefined\n }\n\n if( !window._wio.connected && !RESERVED_EVENTS.includes(_event) ){\n window._wio.messageQueue.push({ _event, payload, fn, timestamp: Date.now(), signed: true })\n console.debug('[EMBEDDED] Queued signed message:', _event)\n return\n }\n\n try {\n let cid\n if( typeof fn === 'function' ){\n cid = window._wio.ackId()\n window._wio.once( _event + '--' + cid + '--@ack', ({ error, args }) => fn( error, ...args ) )\n }\n\n const unsigned = {\n v: ").concat(PROTOCOL_VERSION, ",\n _event,\n payload,\n cid,\n timestamp: Date.now(),\n size: (function(){ try { return JSON.stringify( payload ).length } catch(e){ return 0 } })(),\n token: RESERVED_EVENTS.includes(_event) ? window._wio.connectionToken : undefined\n }\n\n const auth = await window._wio.sign(unsigned)\n const messageData = { ...unsigned, auth }\n\n if( typeof window.ReactNativeWebView !== 'undefined' )\n window.ReactNativeWebView.postMessage( JSON.stringify( messageData ) )\n else console.error('[EMBEDDED] ReactNativeWebView not available')\n }\n catch( error ){\n console.error('[EMBEDDED] EmitSigned error:', error )\n typeof fn === 'function' && fn( String(error) )\n }\n },\n \n on: function( _event, fn ){\n if( !window._wio.Events[_event] ) window._wio.Events[_event] = []\n window._wio.Events[_event].push( fn )\n\n return window._wio\n },\n \n once: function( _event, fn ){\n _event += '--@once'\n if( !window._wio.Events[_event] ) window._wio.Events[_event] = []\n\n window._wio.Events[_event].push( fn )\n\n return window._wio\n },\n \n off: function( _event, fn ){\n if( fn && window._wio.Events[_event] ){\n const index = window._wio.Events[_event].indexOf( fn )\n if( index > -1 ){\n window._wio.Events[_event].splice( index, 1 )\n if( window._wio.Events[_event].length === 0 ) delete window._wio.Events[_event]\n }\n }\n else delete window._wio.Events[_event]\n\n return window._wio\n },\n \n processMessageQueue: function(){\n if( !window._wio.connected || window._wio.messageQueue.length === 0 ) return\n \n console.debug('[EMBEDDED] Processing', window._wio.messageQueue.length, 'queued messages')\n const queue = [ ...window._wio.messageQueue ]\n window._wio.messageQueue = []\n \n queue.forEach( msg => {\n try {\n msg.signed\n ? window._wio.emitSigned( msg._event, msg.payload, msg.fn )\n : window._wio.emit( msg._event, msg.payload, msg.fn )\n }\n catch( error ){ console.error('[EMBEDDED] Queue process error:', error ) }\n })\n },\n\n canonicalFields: ").concat(JSON.stringify(CANONICAL_FIELDS), ",\n\n // Must produce byte-identical output to canonicalMessage() on the\n // native side \u2014 hence the shared field list above.\n canonical: function( data, ts, nonce ){\n const out = {}\n window._wio.canonicalFields.forEach(function( field ){ out[field] = data[field] })\n out.ts = ts\n out.nonce = nonce\n return JSON.stringify( out )\n },\n\n pruneNonces: function(){\n const cutoff = Date.now() - window._wio.maxSkewMs\n const stale = []\n window._wio.seenNonces.forEach(function( ts, nonce ){ if( ts < cutoff ) stale.push( nonce ) })\n stale.forEach(function( nonce ){ window._wio.seenNonces.delete( nonce ) })\n\n if( window._wio.seenNonces.size <= window._wio.replayWindowSize ) return\n\n const toRemove = window._wio.seenNonces.size - window._wio.replayWindowSize\n const keys = Array.from( window._wio.seenNonces.keys() )\n for( let k = 0; k < toRemove && k < keys.length; k++ )\n window._wio.seenNonces.delete( keys[k] )\n },\n\n constantTimeEqual: function(a, b){\n if( a.length !== b.length ) return false\n let out = 0\n for( let i = 0; i < a.length; i++ ) out |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return out === 0\n },\n\n hmacSha256Base64Url: async function(secret, message){\n if( !secret ) throw new Error('Missing auth secret')\n if( !window.crypto || !window.crypto.subtle ) throw new Error('WebCrypto unavailable')\n\n const enc = new TextEncoder()\n const key = await window.crypto.subtle.importKey(\n 'raw',\n enc.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n const sig = await window.crypto.subtle.sign('HMAC', key, enc.encode(message))\n const bytes = new Uint8Array(sig)\n let bin = ''\n for( let i = 0; i < bytes.length; i++ ) bin += String.fromCharCode(bytes[i])\n const b64 = btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '')\n return b64\n },\n\n sign: async function(unsigned){\n if( !window._wio.authSecret ) return null\n const ts = Date.now()\n const nonce = (function(){\n try {\n if( window.crypto && window.crypto.getRandomValues ){\n const buf = new Uint8Array(16)\n window.crypto.getRandomValues(buf)\n let out = ''\n for( let i = 0; i < buf.length; i++ ){\n out += ('0' + buf[i].toString(16)).slice(-2)\n }\n return out\n }\n } catch(e){}\n return (Math.random().toString(16).slice(2) + Math.random().toString(16).slice(2)).slice(0, 32)\n })()\n const sig = await window._wio.hmacSha256Base64Url(window._wio.authSecret, window._wio.canonical(unsigned, ts, nonce))\n return { alg: 'HMAC-SHA256', ts, nonce, sig }\n },\n\n verify: async function(data){\n if( !window._wio.authSecret ) return true\n if( !data.auth ) return false\n const { alg, ts, nonce, sig } = data.auth\n if( alg !== 'HMAC-SHA256' ) return false\n\n const now = Date.now()\n if( Math.abs(now - ts) > window._wio.maxSkewMs ) return false\n\n if( window._wio.seenNonces.has(nonce) ) return false\n\n const expected = await window._wio.hmacSha256Base64Url(window._wio.authSecret, window._wio.canonical(data, ts, nonce))\n if( !window._wio.constantTimeEqual(expected, sig) ) return false\n\n window._wio.seenNonces.set(nonce, ts)\n window._wio.pruneNonces()\n\n return true\n },\n \n handleMessage: function( data ){\n if( !data || !data._event ) return\n \n const { _event, payload, cid, token } = data\n \n console.debug('[EMBEDDED] Received:', _event )\n \n // Handle heartbeat response\n if( _event === '__heartbeat_response' )\n return\n \n // Handle heartbeat request\n if( _event === '__heartbeat' ){\n window._wio.emit('__heartbeat_response', { timestamp: Date.now() })\n return\n }\n \n // Handle webview ready signal\n if( _event === '__webview_ready' ){\n console.debug('[EMBEDDED] WebView ready signal received')\n return\n }\n \n // Handle ping from WEBVIEW\n if( _event === 'ping' ){\n console.debug('[EMBEDDED] Received ping, sending pong')\n window._wio.connectionToken = token\n\n window._wio.emit('pong', { token: window._wio.connectionToken })\n return\n }\n \n // Handle connection acknowledgment\n if( _event === '__connection_ack' ){\n if( token && token !== window._wio.connectionToken ){\n console.error('[EMBEDDED] Invalid connection token in ack')\n return\n }\n \n console.debug('[EMBEDDED] Connection established (received ack)')\n\n window._wio.connected = true\n window._wio.processMessageQueue()\n window._wio.fire('connect')\n\n return\n }\n\n // Auth verification (optional, if authSecret is set)\n if( window._wio.authSecret ){\n window._wio.verify(data).then(ok => {\n if( !ok ){\n console.error('[EMBEDDED] Auth verification failed for', _event)\n return\n }\n window._wio.fire( _event, payload, cid )\n }).catch(err => console.error('[EMBEDDED] Auth error:', err))\n return\n }\n \n // Fire event listeners\n window._wio.fire( _event, payload, cid )\n },\n \n announceReady: function(){\n let attempts = 0\n const maxAttempts = 10\n const interval = 1000\n \n console.debug('[EMBEDDED] Starting ready announcements')\n \n const announce = () => {\n if( window._wio.connected ){\n console.debug('[EMBEDDED] Connected, stopping announcements')\n return\n }\n \n attempts++\n if( attempts > maxAttempts ){\n console.debug('[EMBEDDED] Max announcement attempts reached')\n return\n }\n \n console.debug('[EMBEDDED] Announcing ready (attempt', attempts + '/' + maxAttempts + ')')\n window._wio.emit('__embedded_ready')\n \n setTimeout( announce, interval )\n }\n \n announce()\n }\n }\n\n console.debug('[EMBEDDED] WIO bridge ready. Call window._wio.listen() to connect.')\n }\n catch( error ){\n console.error('[EMBEDDED] Setup failed:', error )\n \n // Create minimal fallback\n window._wio = {\n error: error.toString(),\n listen: function(){ console.error('[EMBEDDED] WIO failed to initialize') },\n emit: function(){ console.error('[EMBEDDED] WIO failed to initialize') },\n on: function(){},\n once: function(){},\n off: function(){}\n }\n }\n\n true\n })()\n ");
714
1101
  };
715
1102
  return WIO;
716
1103
  }());
package/package.json CHANGED
@@ -1,16 +1,14 @@
1
1
  {
2
2
  "name": "webview.io",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Easy and friendly API to connect and interact between React Native and WebView with enhanced security, reliability, and modern async/await support.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "private": false,
8
8
  "scripts": {
9
9
  "compile": "rimraf ./dist && tsc",
10
- "test": "yarn run compile && yarn run test:types && yarn run test:unit",
11
- "test:types": "tsd",
12
- "test:unit": "nyc mocha --require ts-node/register --reporter spec --slow 200 --bail --timeout 10000 test/webview.io.ts",
13
- "prepack": "yarn run compile"
10
+ "test": "npm run compile && node --test \"test/*.test.mjs\"",
11
+ "prepack": "npm run compile"
14
12
  },
15
13
  "dependencies": {
16
14
  "events": "^3.3.0"