@muhkoo/connect 0.14.0-alpha.7 → 0.14.0-alpha.9

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.
@@ -220,6 +220,32 @@ class Logger {
220
220
  // Export default instance for general use
221
221
  const log = new Logger('CONNECT', 'INFO', false);
222
222
 
223
+ /**
224
+ * The SDK's process-global logger.
225
+ *
226
+ * Eight modules under `src/` read a bare `appLogger` off `globalThis` rather
227
+ * than importing one (`src/crypto/{KeyStore,DoubleRatchet,DoubleRatchetManager,
228
+ * Authenticator}.ts`, `src/core/Client.ts`,
229
+ * `src/core/namespaces/HostedAuth.ts`, `src/events/EventCore.ts`), so the
230
+ * binding has to exist before any of them runs, and the `declare global` below
231
+ * is what makes that typecheck.
232
+ *
233
+ * This module is the single place that bootstrap happens. It previously lived
234
+ * inline in three entry files — `src/browser/index.ts`, `src/workers/index.ts`,
235
+ * and `src/core/index.ts` (which `src/server/index.ts` picked up as an import
236
+ * side effect) — three copies that could drift.
237
+ *
238
+ * `??=` makes it idempotent, so importing it from several entries in one
239
+ * process is safe and the first writer wins.
240
+ *
241
+ * KNOWN HAZARD: package.json declares `"sideEffects": false`, which entitles a
242
+ * tree-shaker to drop a module imported purely for effect. The entries import
243
+ * this one for its `export`s as well, so it survives today — but the durable
244
+ * fix is to convert those eight bare reads into real imports of `appLogger`
245
+ * from here, which removes the reliance on a global entirely.
246
+ */
247
+ (globalThis.appLogger ??= new Logger("connect", "ERROR"));
248
+
223
249
  /**
224
250
  * Byte conversion helpers usable in both Node and the browser.
225
251
  *
@@ -1600,26 +1626,56 @@ var EventCoreEvents;
1600
1626
  */
1601
1627
  class EventCore {
1602
1628
  static eventTarget = new EventTarget();
1629
+ /**
1630
+ * `event -> (caller's handler -> the wrapper actually registered on the
1631
+ * EventTarget)`. Two levels because one event can have many listeners and
1632
+ * {@link off} must remove exactly the wrapper it registered for that caller.
1633
+ */
1603
1634
  static events = new Map();
1604
1635
  /**
1605
1636
  * @remarks Listen to an event.
1606
1637
  * @example EventCore.on('event', (e) =\> appLogger.debug(e));
1607
1638
  */
1608
1639
  static on(event, handler) {
1609
- if (!EventCore.events.has(event)) {
1610
- EventCore.events.set(event, handler);
1611
- }
1612
- EventCore.eventTarget.addEventListener(event, handler);
1640
+ let handlers = EventCore.events.get(event);
1641
+ if (!handlers) {
1642
+ handlers = new Map();
1643
+ EventCore.events.set(event, handlers);
1644
+ }
1645
+ // `addEventListener` ignores a duplicate (type, callback) pair; mirror
1646
+ // that here so the bookkeeping can't drift from the EventTarget.
1647
+ if (handlers.has(handler))
1648
+ return;
1649
+ // A throwing listener must not take down its siblings or escape to the
1650
+ // host: Node's EventTarget surfaces an uncaught listener error as an
1651
+ // `uncaughtException`, which terminates the process by default, and
1652
+ // browsers fire `window.onerror`. Contain it and keep dispatching.
1653
+ const wrapped = (e) => {
1654
+ try {
1655
+ handler(e);
1656
+ }
1657
+ catch (err) {
1658
+ appLogger.error(`EventCore: listener for "${event}" threw`, err);
1659
+ }
1660
+ };
1661
+ handlers.set(handler, wrapped);
1662
+ EventCore.eventTarget.addEventListener(event, wrapped);
1613
1663
  }
1614
1664
  /**
1615
1665
  * @remarks Stop listening to an event.
1616
1666
  * @example EventCore.off('event', handler);
1617
1667
  */
1618
1668
  static off(event, handler) {
1619
- if (EventCore.events.has(event)) {
1669
+ const handlers = EventCore.events.get(event);
1670
+ const wrapped = handlers?.get(handler);
1671
+ if (!handlers || !wrapped)
1672
+ return;
1673
+ EventCore.eventTarget.removeEventListener(event, wrapped);
1674
+ handlers.delete(handler);
1675
+ // Drop the event only once its LAST listener is gone, so the
1676
+ // "no listeners" warning in `emit` stays truthful.
1677
+ if (handlers.size === 0)
1620
1678
  EventCore.events.delete(event);
1621
- }
1622
- EventCore.eventTarget.removeEventListener(event, handler);
1623
1679
  }
1624
1680
  /**
1625
1681
  * @remarks Emit an event.
@@ -1628,7 +1684,6 @@ class EventCore {
1628
1684
  static emit(event, data) {
1629
1685
  if (!EventCore.events.has(event)) {
1630
1686
  appLogger.debug(`Warning: No listeners for event: ${event}`);
1631
- // appLogger.verbose(`Event data:`, data);
1632
1687
  }
1633
1688
  EventCore.eventTarget.dispatchEvent(new CustomEvent(event, { detail: data }));
1634
1689
  }
@@ -1887,7 +1942,7 @@ function isCipherFrame(frame) {
1887
1942
  *
1888
1943
  * Hands raw frame strings up (via the `MESSAGE` event) and accepts raw frame
1889
1944
  * strings down (via `send`). Anything frame-shape, encryption, or serialization
1890
- * related belongs in the layer above (e.g. `Network` for client↔server
1945
+ * related belongs in the layer above (e.g. `BroadcastChannel` for client↔server
1891
1946
  * encrypted pipes, `BroadcastChannel` for multi-peer E2E rooms).
1892
1947
  *
1893
1948
  * Responsibilities:
@@ -2199,7 +2254,7 @@ class WSTransport extends EventCore {
2199
2254
  * anything else — passes through as `raw_frame`
2200
2255
  * (the app decides what to do)
2201
2256
  *
2202
- * **Event scoping.** Unlike `Network`, BroadcastChannel uses a per-instance
2257
+ * **Event scoping.** BroadcastChannel uses a per-instance
2203
2258
  * `EventTarget`, so two channels (e.g. two rooms in the same app) don't
2204
2259
  * cross-contaminate each other's events.
2205
2260
  *
@@ -2491,6 +2546,14 @@ async function verifyGroth16(wasmInstance, memory, initialPFree, verificationKey
2491
2546
  const vk = unstringifyBigInts(verificationKey);
2492
2547
  const p = unstringifyBigInts(proof);
2493
2548
  const signals = unstringifyBigInts(publicSignals);
2549
+ // ARITY. vk_x is computed as IC[0] + sum(IC[i+1] * signals[i]), so the
2550
+ // number of public signals is not free — it must be exactly IC.length - 1.
2551
+ // Without this check a caller can pass FEWER signals than the circuit
2552
+ // declares: the loop below simply computes vk_x from fewer terms, which is a
2553
+ // proof of a DIFFERENT statement, and it can verify as true. Passing more
2554
+ // would read past the end of IC.
2555
+ if (!Array.isArray(vk.IC) || signals.length !== vk.IC.length - 1)
2556
+ return false;
2494
2557
  // Validate proof coordinates are within the base field
2495
2558
  if (!validateG1InField(p.pi_a))
2496
2559
  return false;
@@ -2663,8 +2726,5 @@ function alloc(u32, size) {
2663
2726
  return ptr;
2664
2727
  }
2665
2728
 
2666
- const appLogger$1 = new Logger("connect", 'ERROR');
2667
- globalThis.appLogger = appLogger$1;
2668
-
2669
2729
  export { AES_GCM_IV_BYTES, AES_GCM_KEY_BYTES, AES_GCM_TAG_BYTES, BroadcastChannel, BroadcastChannelEvents, ChunkCipher, DoubleRatchet, EncryptedSession, EventCore, EventCoreEvents, KeyStore, Message, Message as MessageDefault, PBKDF2_DEFAULT_ITERATIONS, PREIMAGE_POK_VERIFICATION_KEY, Packet, SerializeMessage, WSTransport, decorators, decryptAesGcm, deriveAesKeyFromPassphrase, deriveBitsHkdf, encryptAesGcm, getSubtle, initBn128Wasm, randomBytes, unwrapWithPassphrase, verifyGroth16, wrapWithPassphrase };
2670
2730
  //# sourceMappingURL=index.js.map