@ait-co/polyfill 0.1.3 → 0.1.5

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/auto.js CHANGED
@@ -116,6 +116,74 @@ function restoreNavigatorProperty(prop, snapshot) {
116
116
  delete target[prop];
117
117
  } catch {}
118
118
  }
119
+ /**
120
+ * Mutate methods on an existing object rather than replacing the object
121
+ * itself. This is the path we take for `navigator.geolocation`, `navigator.share`,
122
+ * and `navigator.vibrate` in Chromium, where the slot on `navigator` is a
123
+ * non-configurable own property that we cannot replace — but the methods
124
+ * themselves (or the methods on the referenced object) are still
125
+ * `configurable: true, writable: true`.
126
+ *
127
+ * Each replacement is installed via plain assignment. If any slot is not
128
+ * writable (e.g. frozen object), install is aborted and previously-applied
129
+ * replacements are rolled back, so the caller observes an atomic "all or
130
+ * nothing" failure as `null`. The caller is expected to degrade gracefully
131
+ * (e.g. log a one-time `console.warn`) when that happens.
132
+ */
133
+ function installObjectMethods(target, replacements) {
134
+ const methods = {};
135
+ const applied = [];
136
+ const bag = target;
137
+ for (const key of Object.keys(replacements)) {
138
+ const hadOwn = Object.hasOwn(target, key);
139
+ const original = bag[key];
140
+ try {
141
+ bag[key] = replacements[key];
142
+ } catch {
143
+ for (const applieKey of applied) {
144
+ const prev = methods[applieKey];
145
+ if (!prev) continue;
146
+ if (prev.hadOwn) bag[applieKey] = prev.original;
147
+ else delete bag[applieKey];
148
+ }
149
+ return null;
150
+ }
151
+ if (bag[key] !== replacements[key]) {
152
+ for (const applieKey of applied) {
153
+ const prev = methods[applieKey];
154
+ if (!prev) continue;
155
+ if (prev.hadOwn) bag[applieKey] = prev.original;
156
+ else delete bag[applieKey];
157
+ }
158
+ return null;
159
+ }
160
+ methods[key] = {
161
+ hadOwn,
162
+ original
163
+ };
164
+ applied.push(key);
165
+ }
166
+ return {
167
+ target,
168
+ methods
169
+ };
170
+ }
171
+ /**
172
+ * Reverse an `installObjectMethods` snapshot. Reassigns originals for slots
173
+ * that were own properties before install; deletes the override for slots
174
+ * that were inherited (so the prototype method surfaces again).
175
+ */
176
+ function restoreObjectMethods(snapshot) {
177
+ const bag = snapshot.target;
178
+ for (const key of Object.keys(snapshot.methods)) {
179
+ const entry = snapshot.methods[key];
180
+ if (!entry) continue;
181
+ try {
182
+ if (entry.hadOwn) bag[key] = entry.original;
183
+ else delete bag[key];
184
+ } catch {}
185
+ }
186
+ }
119
187
  //#endregion
120
188
  //#region src/shims/clipboard.ts
121
189
  /**
@@ -211,6 +279,13 @@ function uninstallClipboardShim() {
211
279
  * Outside Apps in Toss → defers to the browser's native `navigator.geolocation`.
212
280
  * If neither is available, the error callback receives a `GeolocationPositionError`.
213
281
  *
282
+ * Install strategy: **method-level**. We do **not** replace `navigator.geolocation`
283
+ * itself — Chromium marks that slot as a non-configurable own property, which
284
+ * both `defineProperty(navigator, 'geolocation', …)` and the prototype-level
285
+ * fallback cannot override (the instance shadow blocks prototype reads). We
286
+ * instead mutate the methods on the existing `Geolocation` object, whose own
287
+ * method descriptors are configurable+writable in every browser we've seen.
288
+ *
214
289
  * SDK/Web shape mismatch handled here:
215
290
  * - SDK `Accuracy` is a numeric enum (1 = Lowest … 6 = BestForNavigation); the
216
291
  * standard `PositionOptions.enableHighAccuracy` is a boolean. We map
@@ -302,7 +377,7 @@ function createGeolocationShim(fallback) {
302
377
  return;
303
378
  }
304
379
  }
305
- if (!fallback) {
380
+ if (!fallback.getCurrentPosition) {
306
381
  error?.(toPositionError(2, "[@ait-co/polyfill] navigator.geolocation is not available in this environment."));
307
382
  return;
308
383
  }
@@ -340,7 +415,7 @@ function createGeolocationShim(fallback) {
340
415
  return;
341
416
  }
342
417
  }
343
- if (!fallback) {
418
+ if (!fallback.watchPosition) {
344
419
  pendingWatches.delete(id);
345
420
  error?.(toPositionError(2, "[@ait-co/polyfill] navigator.geolocation is not available in this environment."));
346
421
  return;
@@ -351,7 +426,7 @@ function createGeolocationShim(fallback) {
351
426
  }
352
427
  const nativeId = fallback.watchPosition(success, error, options);
353
428
  if (pending.cancelled) {
354
- fallback.clearWatch(nativeId);
429
+ fallback.clearWatch?.(nativeId);
355
430
  pendingWatches.delete(id);
356
431
  return;
357
432
  }
@@ -374,7 +449,7 @@ function createGeolocationShim(fallback) {
374
449
  return;
375
450
  }
376
451
  const nativeId = nativeWatches.get(id);
377
- if (nativeId !== void 0 && fallback) {
452
+ if (nativeId !== void 0 && fallback.clearWatch) {
378
453
  fallback.clearWatch(nativeId);
379
454
  nativeWatches.delete(id);
380
455
  }
@@ -385,13 +460,27 @@ function installGeolocationShim() {
385
460
  if (typeof navigator === "undefined") return () => {};
386
461
  const host = navigator;
387
462
  if (BACKUP_KEY$1 in host) return () => uninstallGeolocationShim();
388
- const original = navigator.geolocation;
389
- host[BACKUP_KEY$1] = original;
390
- host[SNAPSHOT_KEY$1] = installNavigatorProperty("geolocation", {
391
- value: createGeolocationShim(original),
392
- configurable: true,
393
- writable: true
463
+ const target = navigator.geolocation;
464
+ if (!target) {
465
+ host[BACKUP_KEY$1] = void 0;
466
+ return () => uninstallGeolocationShim();
467
+ }
468
+ const shim = createGeolocationShim({
469
+ getCurrentPosition: target.getCurrentPosition?.bind(target),
470
+ watchPosition: target.watchPosition?.bind(target),
471
+ clearWatch: target.clearWatch?.bind(target)
472
+ });
473
+ const snapshot = installObjectMethods(target, {
474
+ getCurrentPosition: shim.getCurrentPosition,
475
+ watchPosition: shim.watchPosition,
476
+ clearWatch: shim.clearWatch
394
477
  });
478
+ if (!snapshot) {
479
+ host[BACKUP_KEY$1] = void 0;
480
+ return () => uninstallGeolocationShim();
481
+ }
482
+ host[BACKUP_KEY$1] = { target };
483
+ host[SNAPSHOT_KEY$1] = snapshot;
395
484
  return uninstallGeolocationShim;
396
485
  }
397
486
  function uninstallGeolocationShim() {
@@ -399,7 +488,7 @@ function uninstallGeolocationShim() {
399
488
  const host = navigator;
400
489
  if (!(BACKUP_KEY$1 in host)) return;
401
490
  const snapshot = host[SNAPSHOT_KEY$1];
402
- if (snapshot) restoreNavigatorProperty("geolocation", snapshot);
491
+ if (snapshot) restoreObjectMethods(snapshot);
403
492
  delete host[BACKUP_KEY$1];
404
493
  delete host[SNAPSHOT_KEY$1];
405
494
  }
@@ -426,6 +515,15 @@ function uninstallGeolocationShim() {
426
515
  * visible again. We never mutate the prototype — doing so would throw in
427
516
  * browsers where the descriptor is non-configurable.
428
517
  *
518
+ * Browser-compat caveat (Chromium): `navigator.onLine` and `navigator.connection`
519
+ * are value slots, not methods, so the method-level install trick we use for
520
+ * `geolocation`/`share`/`vibrate` does not apply here. When Chromium marks the
521
+ * instance descriptor as non-configurable AND the prototype descriptor is also
522
+ * non-configurable, we cannot install. In that case the shim logs a one-time
523
+ * `console.warn` and leaves the native values in place — consumers keep the
524
+ * browser's own `onLine`/`connection` values; the SDK-synced state is simply
525
+ * disabled for that session.
526
+ *
429
527
  * Caveat: the Web NetworkInformation API is evented (`change` fires on
430
528
  * transitions). The SDK exposes only a one-shot query, so listeners attached
431
529
  * to `navigator.connection` are accepted but never fire from a `change` event
@@ -531,22 +629,36 @@ function installNetworkShim() {
531
629
  const nativeOnLine = navigator.onLine;
532
630
  const nativeConnection = navigator.connection;
533
631
  refresh();
534
- host[ON_LINE_SNAPSHOT_KEY] = installNavigatorProperty("onLine", {
535
- configurable: true,
536
- get() {
537
- refresh();
538
- if (cachedStatus !== null) return statusToOnline(cachedStatus);
539
- return nativeOnLine ?? true;
540
- }
541
- });
542
- host[CONNECTION_SNAPSHOT_KEY] = installNavigatorProperty("connection", {
543
- configurable: true,
544
- get() {
545
- refresh();
546
- if (cachedStatus === null && nativeConnection !== void 0) return nativeConnection;
547
- return connection;
548
- }
549
- });
632
+ let installWarned = false;
633
+ const warnNonConfigurable = (e) => {
634
+ if (installWarned) return;
635
+ installWarned = true;
636
+ console.warn("[@ait-co/polyfill] navigator.onLine/connection is non-configurable in this browser; Toss network status sync disabled.", e);
637
+ };
638
+ try {
639
+ host[ON_LINE_SNAPSHOT_KEY] = installNavigatorProperty("onLine", {
640
+ configurable: true,
641
+ get() {
642
+ refresh();
643
+ if (cachedStatus !== null) return statusToOnline(cachedStatus);
644
+ return nativeOnLine ?? true;
645
+ }
646
+ });
647
+ } catch (e) {
648
+ warnNonConfigurable(e);
649
+ }
650
+ try {
651
+ host[CONNECTION_SNAPSHOT_KEY] = installNavigatorProperty("connection", {
652
+ configurable: true,
653
+ get() {
654
+ refresh();
655
+ if (cachedStatus === null && nativeConnection !== void 0) return nativeConnection;
656
+ return connection;
657
+ }
658
+ });
659
+ } catch (e) {
660
+ warnNonConfigurable(e);
661
+ }
550
662
  return uninstallNetworkShim;
551
663
  }
552
664
  function uninstallNetworkShim() {
@@ -573,13 +685,18 @@ function uninstallNetworkShim() {
573
685
  * Outside Apps in Toss → defers to the browser's native `navigator.share`, or
574
686
  * throws `NotSupportedError` if unavailable.
575
687
  *
688
+ * Install strategy: **method-level** on `navigator`. Assigning
689
+ * `navigator.share = fn` creates an own property that shadows the prototype
690
+ * method. Uninstall deletes the own shadow so the prototype method surfaces
691
+ * again. We do not mutate `Navigator.prototype` — in real browsers its
692
+ * descriptor may be non-configurable, which would throw on reassignment.
693
+ *
576
694
  * Caveat: the SDK's share has no counterpart for `files` (Web Share Level 2).
577
695
  * `canShare({ files })` returns `false` whenever the sync-accessible detection
578
696
  * says Toss is active (or is being forced via the test override).
579
697
  */
580
698
  const SHARE_BACKUP_KEY = Symbol.for("@ait-co/polyfill/share.original");
581
699
  const SHARE_SNAPSHOT_KEY = Symbol.for("@ait-co/polyfill/share.snapshot");
582
- const CAN_SHARE_SNAPSHOT_KEY = Symbol.for("@ait-co/polyfill/canShare.snapshot");
583
700
  function buildSdkMessage(data) {
584
701
  const parts = [];
585
702
  if (data?.title != null && data.title !== "") parts.push(data.title);
@@ -606,7 +723,7 @@ async function shareShim(data) {
606
723
  }
607
724
  const original = navigator[SHARE_BACKUP_KEY]?.share;
608
725
  if (!original) throw new DOMException("[@ait-co/polyfill] navigator.share is not available in this environment.", "NotSupportedError");
609
- return original.call(navigator, data);
726
+ return original(data);
610
727
  }
611
728
  function canShareShim(data) {
612
729
  const hasFiles = Boolean(data?.files && data.files.length > 0);
@@ -617,7 +734,7 @@ function canShareShim(data) {
617
734
  }
618
735
  if (toss === true) return Boolean(data?.title != null && data.title !== "" || data?.text != null && data.text !== "" || data?.url != null && data.url !== "");
619
736
  const originalCanShare = navigator[SHARE_BACKUP_KEY]?.canShare;
620
- if (originalCanShare) return originalCanShare.call(navigator, data);
737
+ if (originalCanShare) return originalCanShare(data);
621
738
  return Boolean(data?.title != null && data.title !== "" || data?.text != null && data.text !== "" || data?.url != null && data.url !== "");
622
739
  }
623
740
  function installShareShim() {
@@ -626,34 +743,28 @@ function installShareShim() {
626
743
  if (SHARE_BACKUP_KEY in host) return () => uninstallShareShim();
627
744
  const nav = navigator;
628
745
  host[SHARE_BACKUP_KEY] = {
629
- share: nav.share,
630
- canShare: nav.canShare,
631
- hadShare: "share" in nav,
632
- hadCanShare: "canShare" in nav
746
+ share: nav.share ? nav.share.bind(navigator) : void 0,
747
+ canShare: nav.canShare ? nav.canShare.bind(navigator) : void 0
633
748
  };
634
- host[SHARE_SNAPSHOT_KEY] = installNavigatorProperty("share", {
635
- value: shareShim,
636
- configurable: true,
637
- writable: true
638
- });
639
- host[CAN_SHARE_SNAPSHOT_KEY] = installNavigatorProperty("canShare", {
640
- value: canShareShim,
641
- configurable: true,
642
- writable: true
749
+ const snapshot = installObjectMethods(navigator, {
750
+ share: shareShim,
751
+ canShare: canShareShim
643
752
  });
753
+ if (!snapshot) {
754
+ delete host[SHARE_BACKUP_KEY];
755
+ return () => uninstallShareShim();
756
+ }
757
+ host[SHARE_SNAPSHOT_KEY] = snapshot;
644
758
  return uninstallShareShim;
645
759
  }
646
760
  function uninstallShareShim() {
647
761
  if (typeof navigator === "undefined") return;
648
762
  const host = navigator;
649
763
  if (!(SHARE_BACKUP_KEY in host)) return;
650
- const shareSnap = host[SHARE_SNAPSHOT_KEY];
651
- if (shareSnap) restoreNavigatorProperty("share", shareSnap);
652
- const canShareSnap = host[CAN_SHARE_SNAPSHOT_KEY];
653
- if (canShareSnap) restoreNavigatorProperty("canShare", canShareSnap);
764
+ const snapshot = host[SHARE_SNAPSHOT_KEY];
765
+ if (snapshot) restoreObjectMethods(snapshot);
654
766
  delete host[SHARE_BACKUP_KEY];
655
767
  delete host[SHARE_SNAPSHOT_KEY];
656
- delete host[CAN_SHARE_SNAPSHOT_KEY];
657
768
  }
658
769
  //#endregion
659
770
  //#region src/shims/vibrate.ts
@@ -669,6 +780,11 @@ function uninstallShareShim() {
669
780
  * or returns `false` when unavailable (matches the spec — browsers that don't
670
781
  * support vibration simply return `false`).
671
782
  *
783
+ * Install strategy: **method-level** on `navigator`. We assign our wrapper to
784
+ * `navigator.vibrate` (creating an own shadow over the prototype method) and
785
+ * delete it on uninstall so the prototype re-surfaces. We do not mutate
786
+ * `Navigator.prototype` itself — browsers may mark it non-configurable.
787
+ *
672
788
  * Caveats (documented in CLAUDE.md as the known lossy trade-off):
673
789
  * - SDK haptics are qualitative ("tickWeak", "basicMedium"), not millisecond
674
790
  * durations. The shim approximates intensity from duration but cannot
@@ -694,7 +810,7 @@ function vibrateShim(pattern) {
694
810
  const arr = Array.isArray(pattern) ? pattern : [pattern];
695
811
  if (arr.length === 0 || arr.every((n) => n === 0)) {
696
812
  (async () => {
697
- if (!await isTossEnvironment()) navigator[BACKUP_KEY]?.call(navigator, pattern);
813
+ if (!await isTossEnvironment()) navigator[BACKUP_KEY]?.(pattern);
698
814
  })();
699
815
  return true;
700
816
  }
@@ -713,7 +829,8 @@ function vibrateShim(pattern) {
713
829
  }
714
830
  return;
715
831
  }
716
- navigator[BACKUP_KEY]?.call(navigator, pattern);
832
+ const original = navigator[BACKUP_KEY];
833
+ original?.(pattern);
717
834
  })();
718
835
  return true;
719
836
  }
@@ -721,12 +838,14 @@ function installVibrateShim() {
721
838
  if (typeof navigator === "undefined") return () => {};
722
839
  const host = navigator;
723
840
  if (BACKUP_KEY in host) return () => uninstallVibrateShim();
724
- host[BACKUP_KEY] = navigator.vibrate?.bind(navigator);
725
- host[SNAPSHOT_KEY] = installNavigatorProperty("vibrate", {
726
- value: vibrateShim,
727
- configurable: true,
728
- writable: true
729
- });
841
+ const nav = navigator;
842
+ host[BACKUP_KEY] = nav.vibrate ? nav.vibrate.bind(navigator) : void 0;
843
+ const snapshot = installObjectMethods(navigator, { vibrate: vibrateShim });
844
+ if (!snapshot) {
845
+ delete host[BACKUP_KEY];
846
+ return () => uninstallVibrateShim();
847
+ }
848
+ host[SNAPSHOT_KEY] = snapshot;
730
849
  return uninstallVibrateShim;
731
850
  }
732
851
  function uninstallVibrateShim() {
@@ -734,7 +853,7 @@ function uninstallVibrateShim() {
734
853
  const host = navigator;
735
854
  if (!(BACKUP_KEY in host)) return;
736
855
  const snapshot = host[SNAPSHOT_KEY];
737
- if (snapshot) restoreNavigatorProperty("vibrate", snapshot);
856
+ if (snapshot) restoreObjectMethods(snapshot);
738
857
  delete host[BACKUP_KEY];
739
858
  delete host[SNAPSHOT_KEY];
740
859
  }