@ait-co/polyfill 0.1.1 → 0.1.3

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.
@@ -2,14 +2,19 @@
2
2
  /**
3
3
  * Environment detection: are we running inside Apps in Toss, or a plain browser?
4
4
  *
5
- * Strategy: feature-sniff `@apps-in-toss/web-framework`. The SDK is declared as
6
- * an **optional** peer dependency. If it resolves and exposes a known export,
7
- * we assume we can route calls through it; otherwise we fall back to the
8
- * browser's native implementation in each shim.
5
+ * Strategy: call the SDK's `getAppsInTossGlobals()` a synchronous export
6
+ * that returns the runtime's Toss globals (deploymentId, brand name, …)
7
+ * inside the Apps in Toss runtime and throws (RN bridge unavailable)
8
+ * anywhere else. The SDK itself is an **optional** peer dependency; if its
9
+ * module can't be imported we are definitely not inside Toss.
9
10
  *
10
- * We deliberately avoid UA sniffing (spoofable) and avoid calling any SDK
11
- * function during detection (could prompt permission dialogs, fire analytics,
12
- * etc.).
11
+ * Just having the SDK module resolvable is not enough apps can bundle it
12
+ * and still run in a plain browser. We need the bridge probe to confirm.
13
+ *
14
+ * UA sniffing (spoofable) is avoided. We do call `getAppsInTossGlobals`, but
15
+ * that's a constant read from the bridge — no permission dialogs, no
16
+ * analytics fire. In a plain browser the bridge lookup fails fast (sync
17
+ * throw, microsecond-scale), so the startup cost is negligible.
13
18
  */
14
19
  let cached;
15
20
  /**
@@ -39,7 +44,17 @@ async function isTossEnvironment() {
39
44
  if (force === "toss") return true;
40
45
  if (force === "browser") return false;
41
46
  if (cached !== void 0) return cached;
42
- cached = typeof (await loadTossSdk())?.getClipboardText === "function";
47
+ const mod = await loadTossSdk();
48
+ if (typeof mod?.getAppsInTossGlobals !== "function") {
49
+ cached = false;
50
+ return cached;
51
+ }
52
+ try {
53
+ const globals = mod.getAppsInTossGlobals();
54
+ cached = Boolean(globals) && typeof globals === "object";
55
+ } catch {
56
+ cached = false;
57
+ }
43
58
  return cached;
44
59
  }
45
60
  /**
@@ -54,6 +69,54 @@ async function loadTossSdk() {
54
69
  }
55
70
  }
56
71
  //#endregion
72
+ //#region src/shims/_install-helpers.ts
73
+ /**
74
+ * Install `descriptor` at `navigator[prop]`. Prefer instance-level; if the
75
+ * browser refuses (property is non-configurable on the instance), install on
76
+ * `Navigator.prototype` instead.
77
+ *
78
+ * Returns a snapshot describing where the original value was, which
79
+ * `restoreNavigatorProperty` uses to undo the install.
80
+ */
81
+ function installNavigatorProperty(prop, descriptor) {
82
+ const nav = navigator;
83
+ const instanceDesc = Object.getOwnPropertyDescriptor(nav, prop);
84
+ const instanceHadOwn = instanceDesc !== void 0;
85
+ if (!instanceDesc || instanceDesc.configurable) try {
86
+ Object.defineProperty(nav, prop, descriptor);
87
+ return {
88
+ location: "instance",
89
+ originalDescriptor: instanceDesc,
90
+ instanceHadOwn
91
+ };
92
+ } catch {}
93
+ const proto = Object.getPrototypeOf(nav);
94
+ const protoDesc = Object.getOwnPropertyDescriptor(proto, prop);
95
+ if (instanceHadOwn) try {
96
+ delete nav[prop];
97
+ } catch {}
98
+ Object.defineProperty(proto, prop, descriptor);
99
+ return {
100
+ location: "prototype",
101
+ originalDescriptor: protoDesc,
102
+ instanceHadOwn
103
+ };
104
+ }
105
+ /**
106
+ * Reverse the install recorded in `snapshot`. If the original descriptor was
107
+ * `undefined` (property didn't exist before), delete the property instead of
108
+ * re-defining it.
109
+ */
110
+ function restoreNavigatorProperty(prop, snapshot) {
111
+ const target = snapshot.location === "instance" ? navigator : Object.getPrototypeOf(navigator);
112
+ if (snapshot.originalDescriptor) try {
113
+ Object.defineProperty(target, prop, snapshot.originalDescriptor);
114
+ } catch {}
115
+ else try {
116
+ delete target[prop];
117
+ } catch {}
118
+ }
119
+ //#endregion
57
120
  //#region src/shims/share.ts
58
121
  /**
59
122
  * `navigator.share` shim.
@@ -70,6 +133,8 @@ async function loadTossSdk() {
70
133
  * says Toss is active (or is being forced via the test override).
71
134
  */
72
135
  const SHARE_BACKUP_KEY = Symbol.for("@ait-co/polyfill/share.original");
136
+ const SHARE_SNAPSHOT_KEY = Symbol.for("@ait-co/polyfill/share.snapshot");
137
+ const CAN_SHARE_SNAPSHOT_KEY = Symbol.for("@ait-co/polyfill/canShare.snapshot");
73
138
  function buildSdkMessage(data) {
74
139
  const parts = [];
75
140
  if (data?.title != null && data.title !== "") parts.push(data.title);
@@ -121,12 +186,12 @@ function installShareShim() {
121
186
  hadShare: "share" in nav,
122
187
  hadCanShare: "canShare" in nav
123
188
  };
124
- Object.defineProperty(navigator, "share", {
189
+ host[SHARE_SNAPSHOT_KEY] = installNavigatorProperty("share", {
125
190
  value: shareShim,
126
191
  configurable: true,
127
192
  writable: true
128
193
  });
129
- Object.defineProperty(navigator, "canShare", {
194
+ host[CAN_SHARE_SNAPSHOT_KEY] = installNavigatorProperty("canShare", {
130
195
  value: canShareShim,
131
196
  configurable: true,
132
197
  writable: true
@@ -137,20 +202,13 @@ function uninstallShareShim() {
137
202
  if (typeof navigator === "undefined") return;
138
203
  const host = navigator;
139
204
  if (!(SHARE_BACKUP_KEY in host)) return;
140
- const backup = host[SHARE_BACKUP_KEY];
141
- delete navigator.share;
142
- if (backup?.hadShare && navigator.share !== backup.share) Object.defineProperty(navigator, "share", {
143
- value: backup.share,
144
- configurable: true,
145
- writable: true
146
- });
147
- delete navigator.canShare;
148
- if (backup?.hadCanShare && navigator.canShare !== backup.canShare) Object.defineProperty(navigator, "canShare", {
149
- value: backup.canShare,
150
- configurable: true,
151
- writable: true
152
- });
205
+ const shareSnap = host[SHARE_SNAPSHOT_KEY];
206
+ if (shareSnap) restoreNavigatorProperty("share", shareSnap);
207
+ const canShareSnap = host[CAN_SHARE_SNAPSHOT_KEY];
208
+ if (canShareSnap) restoreNavigatorProperty("canShare", canShareSnap);
153
209
  delete host[SHARE_BACKUP_KEY];
210
+ delete host[SHARE_SNAPSHOT_KEY];
211
+ delete host[CAN_SHARE_SNAPSHOT_KEY];
154
212
  }
155
213
  //#endregion
156
214
  export { installShareShim, uninstallShareShim };
@@ -1 +1 @@
1
- {"version":3,"file":"share.js","names":[],"sources":["../../src/detect.ts","../../src/shims/share.ts"],"sourcesContent":["/**\n * Environment detection: are we running inside Apps in Toss, or a plain browser?\n *\n * Strategy: feature-sniff `@apps-in-toss/web-framework`. The SDK is declared as\n * an **optional** peer dependency. If it resolves and exposes a known export,\n * we assume we can route calls through it; otherwise we fall back to the\n * browser's native implementation in each shim.\n *\n * We deliberately avoid UA sniffing (spoofable) and avoid calling any SDK\n * function during detection (could prompt permission dialogs, fire analytics,\n * etc.).\n */\n\nlet cached: boolean | undefined;\n\n/**\n * Reset the cached detection result. Primarily for tests.\n */\nexport function resetDetection(): void {\n cached = undefined;\n}\n\n/**\n * Synchronous read of the cached detection result. Returns:\n * - `true` / `false` if an override is active or the async detection has\n * already resolved\n * - `undefined` if detection hasn't run yet\n *\n * Used by spec-sync APIs (e.g. `navigator.canShare`) that can't `await`\n * detection.\n */\nexport function isTossEnvironmentCached(): boolean | undefined {\n const force = globalThis.__AIT_POLYFILL_FORCE__;\n if (force === 'toss') return true;\n if (force === 'browser') return false;\n return cached;\n}\n\n/**\n * Returns `true` iff we detect we are running in an environment where the\n * Apps in Toss SDK (`@apps-in-toss/web-framework`) is present and usable.\n *\n * Async because we use dynamic `import()` to probe the optional peer dep\n * without forcing it into the consumer's bundle.\n */\nexport async function isTossEnvironment(): Promise<boolean> {\n // Override check precedes cache so `devtools` / tests can flip the result\n // mid-session without a `resetDetection()` call.\n const force = globalThis.__AIT_POLYFILL_FORCE__;\n if (force === 'toss') return true;\n if (force === 'browser') return false;\n\n if (cached !== undefined) return cached;\n\n const mod = await loadTossSdk();\n // Presence of a well-known export is our smoke test.\n cached = typeof mod?.getClipboardText === 'function';\n return cached;\n}\n\n/**\n * Lazy SDK accessor — returns the module if available, else `null`. Callers\n * are expected to `await` and null-check. Never throws.\n */\nexport async function loadTossSdk(): Promise<typeof import('@apps-in-toss/web-framework') | null> {\n try {\n return await import('@apps-in-toss/web-framework');\n } catch {\n return null;\n }\n}\n","/**\n * `navigator.share` shim.\n *\n * Inside Apps in Toss → routes through SDK `share({ message })`. The SDK only\n * accepts a single `message` string, so we concatenate `title`, `text`, and\n * `url` with newline separators (skipping missing/empty values).\n *\n * Outside Apps in Toss → defers to the browser's native `navigator.share`, or\n * throws `NotSupportedError` if unavailable.\n *\n * Caveat: the SDK's share has no counterpart for `files` (Web Share Level 2).\n * `canShare({ files })` returns `false` whenever the sync-accessible detection\n * says Toss is active (or is being forced via the test override).\n */\n\nimport { isTossEnvironment, isTossEnvironmentCached, loadTossSdk } from '../detect.js';\n\nconst SHARE_BACKUP_KEY = Symbol.for('@ait-co/polyfill/share.original');\n\ntype ShareFn = (data?: ShareData) => Promise<void>;\ntype CanShareFn = (data?: ShareData) => boolean;\n\ninterface Backup {\n share?: ShareFn | undefined;\n canShare?: CanShareFn | undefined;\n hadShare: boolean;\n hadCanShare: boolean;\n}\n\ninterface BackupHost {\n [SHARE_BACKUP_KEY]?: Backup | undefined;\n}\n\nfunction buildSdkMessage(data: ShareData | undefined): string {\n // Use presence checks rather than truthiness so an intentionally empty\n // string in one field is handled correctly alongside a non-empty sibling.\n const parts: string[] = [];\n if (data?.title != null && data.title !== '') parts.push(data.title);\n if (data?.text != null && data.text !== '') parts.push(data.text);\n if (data?.url != null && data.url !== '') parts.push(data.url);\n return parts.join('\\n');\n}\n\nasync function shareShim(data?: ShareData): Promise<void> {\n if (await isTossEnvironment()) {\n const sdk = await loadTossSdk();\n const fn = (sdk as { share?: unknown } | null)?.share;\n if (typeof fn === 'function') {\n const message = buildSdkMessage(data);\n if (!message) {\n throw new TypeError(\n '[@ait-co/polyfill] navigator.share requires at least one of title, text, or url.',\n );\n }\n try {\n await (fn as (o: { message: string }) => Promise<void>)({ message });\n } catch (e) {\n // Spec says navigator.share rejects with a DOMException. Wrap SDK\n // errors as AbortError (the most common cause is user cancellation),\n // attaching the original as `.cause` for Sentry-style telemetry.\n const message_ = e instanceof Error ? e.message : String(e);\n const wrapped = new DOMException(message_, 'AbortError');\n if (e instanceof Error) {\n (wrapped as Error).cause = e;\n }\n throw wrapped;\n }\n return;\n }\n }\n const host = navigator as unknown as BackupHost;\n const backup = host[SHARE_BACKUP_KEY];\n const original = backup?.share;\n if (!original) {\n throw new DOMException(\n '[@ait-co/polyfill] navigator.share is not available in this environment.',\n 'NotSupportedError',\n );\n }\n return original.call(navigator, data);\n}\n\nfunction canShareShim(data?: ShareData): boolean {\n const hasFiles = Boolean(data?.files && data.files.length > 0);\n const toss = isTossEnvironmentCached();\n\n if (hasFiles) {\n // SDK does not share files. If we know we're in Toss (or it's being\n // forced), say so honestly. If detection hasn't resolved yet, be\n // pessimistic — a false negative is safer than promising a capability\n // we'll turn around and deny.\n if (toss === true) return false;\n if (toss === undefined) return false;\n }\n\n // Toss with non-file payloads: true iff there's at least one field.\n if (toss === true) {\n return Boolean(\n (data?.title != null && data.title !== '') ||\n (data?.text != null && data.text !== '') ||\n (data?.url != null && data.url !== ''),\n );\n }\n\n // `toss === undefined` (detection not resolved) with non-file payload falls\n // through to the browser-native answer. Rationale: `canShare` is rarely\n // load-bearing — consumers care about `share()` itself, which awaits the\n // async detection correctly. A false-negative here would needlessly hide a\n // Share button while detection settles.\n // Browser path: delegate to native when present.\n const host = navigator as unknown as BackupHost;\n const backup = host[SHARE_BACKUP_KEY];\n const originalCanShare = backup?.canShare;\n if (originalCanShare) {\n return originalCanShare.call(navigator, data);\n }\n return Boolean(\n (data?.title != null && data.title !== '') ||\n (data?.text != null && data.text !== '') ||\n (data?.url != null && data.url !== ''),\n );\n}\n\nexport function installShareShim(): () => void {\n if (typeof navigator === 'undefined') {\n return () => {};\n }\n\n const host = navigator as unknown as BackupHost;\n if (SHARE_BACKUP_KEY in host) {\n // Already installed. Use `in` so the absence of `share` / `canShare` on\n // the pre-install navigator (legitimately stored as `undefined`) doesn't\n // re-trigger install.\n return () => uninstallShareShim();\n }\n\n const nav = navigator as Navigator & {\n share?: ShareFn;\n canShare?: CanShareFn;\n };\n host[SHARE_BACKUP_KEY] = {\n share: nav.share,\n canShare: nav.canShare,\n hadShare: 'share' in nav,\n hadCanShare: 'canShare' in nav,\n };\n\n Object.defineProperty(navigator, 'share', {\n value: shareShim,\n configurable: true,\n writable: true,\n });\n Object.defineProperty(navigator, 'canShare', {\n value: canShareShim,\n configurable: true,\n writable: true,\n });\n\n return uninstallShareShim;\n}\n\nexport function uninstallShareShim(): void {\n if (typeof navigator === 'undefined') return;\n const host = navigator as unknown as BackupHost;\n if (!(SHARE_BACKUP_KEY in host)) return;\n\n const backup = host[SHARE_BACKUP_KEY];\n\n // Prototype-safe restore: delete the instance override first so a prototype\n // descriptor (real browsers put `share` / `canShare` on `Navigator.prototype`\n // when they exist at all) shows through. Only redefine on the instance if\n // the original was an own property that the prototype doesn't provide —\n // otherwise we'd permanently shadow the prototype getter.\n delete (navigator as unknown as { share?: ShareFn }).share;\n if (backup?.hadShare && navigator.share !== backup.share) {\n Object.defineProperty(navigator, 'share', {\n value: backup.share,\n configurable: true,\n writable: true,\n });\n }\n delete (navigator as unknown as { canShare?: CanShareFn }).canShare;\n if (backup?.hadCanShare && navigator.canShare !== backup.canShare) {\n Object.defineProperty(navigator, 'canShare', {\n value: backup.canShare,\n configurable: true,\n writable: true,\n });\n }\n\n delete host[SHARE_BACKUP_KEY];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"share.js","names":[],"sources":["../../src/detect.ts","../../src/shims/_install-helpers.ts","../../src/shims/share.ts"],"sourcesContent":["/**\n * Environment detection: are we running inside Apps in Toss, or a plain browser?\n *\n * Strategy: call the SDK's `getAppsInTossGlobals()` — a synchronous export\n * that returns the runtime's Toss globals (deploymentId, brand name, …)\n * inside the Apps in Toss runtime and throws (RN bridge unavailable)\n * anywhere else. The SDK itself is an **optional** peer dependency; if its\n * module can't be imported we are definitely not inside Toss.\n *\n * Just having the SDK module resolvable is not enough — apps can bundle it\n * and still run in a plain browser. We need the bridge probe to confirm.\n *\n * UA sniffing (spoofable) is avoided. We do call `getAppsInTossGlobals`, but\n * that's a constant read from the bridge — no permission dialogs, no\n * analytics fire. In a plain browser the bridge lookup fails fast (sync\n * throw, microsecond-scale), so the startup cost is negligible.\n */\n\nlet cached: boolean | undefined;\n\n/**\n * Reset the cached detection result. Primarily for tests.\n */\nexport function resetDetection(): void {\n cached = undefined;\n}\n\n/**\n * Synchronous read of the cached detection result. Returns:\n * - `true` / `false` if an override is active or the async detection has\n * already resolved\n * - `undefined` if detection hasn't run yet\n *\n * Used by spec-sync APIs (e.g. `navigator.canShare`) that can't `await`\n * detection.\n */\nexport function isTossEnvironmentCached(): boolean | undefined {\n const force = globalThis.__AIT_POLYFILL_FORCE__;\n if (force === 'toss') return true;\n if (force === 'browser') return false;\n return cached;\n}\n\n/**\n * Returns `true` iff we detect we are running in an environment where the\n * Apps in Toss SDK (`@apps-in-toss/web-framework`) is present and usable.\n *\n * Async because we use dynamic `import()` to probe the optional peer dep\n * without forcing it into the consumer's bundle.\n */\nexport async function isTossEnvironment(): Promise<boolean> {\n // Override check precedes cache so `devtools` / tests can flip the result\n // mid-session without a `resetDetection()` call.\n const force = globalThis.__AIT_POLYFILL_FORCE__;\n if (force === 'toss') return true;\n if (force === 'browser') return false;\n\n if (cached !== undefined) return cached;\n\n const mod = await loadTossSdk();\n if (typeof mod?.getAppsInTossGlobals !== 'function') {\n cached = false;\n return cached;\n }\n // Inside Toss the bridge returns a populated globals object. In a plain\n // browser the RN bridge isn't attached and the call throws — that's our\n // signal. Any non-throwing call with an object return is treated as Toss.\n try {\n const globals = mod.getAppsInTossGlobals();\n cached = Boolean(globals) && typeof globals === 'object';\n } catch {\n cached = false;\n }\n return cached;\n}\n\n/**\n * Lazy SDK accessor — returns the module if available, else `null`. Callers\n * are expected to `await` and null-check. Never throws.\n */\nexport async function loadTossSdk(): Promise<typeof import('@apps-in-toss/web-framework') | null> {\n try {\n return await import('@apps-in-toss/web-framework');\n } catch {\n return null;\n }\n}\n","/**\n * Shared helpers for installing shims on `navigator`.\n *\n * Chromium now marks a handful of `navigator` properties (e.g. `geolocation`,\n * `clipboard`) as non-configurable **own** properties on the instance. That\n * means a plain `Object.defineProperty(navigator, 'x', …)` throws\n * `TypeError: Cannot redefine property`.\n *\n * The workaround is to shim at the prototype level — `Navigator.prototype`\n * keeps these as configurable accessors, so we can swap them there and every\n * instance that falls through to the prototype (including `window.navigator`)\n * sees the shim. We only reach for the prototype when the instance-level\n * assignment refuses.\n *\n * For restoration we remember the descriptor chain (instance + prototype) so\n * `uninstall()` puts the browser back in its original state.\n */\n\ntype PropertyLocation = 'instance' | 'prototype';\n\nexport interface InstallSnapshot {\n /** Where we ended up writing the shim. */\n location: PropertyLocation;\n /** Original descriptor at that location (may be undefined if nothing was there). */\n originalDescriptor: PropertyDescriptor | undefined;\n /** `true` iff the original property lived on the instance before we touched it. */\n instanceHadOwn: boolean;\n}\n\n/**\n * Install `descriptor` at `navigator[prop]`. Prefer instance-level; if the\n * browser refuses (property is non-configurable on the instance), install on\n * `Navigator.prototype` instead.\n *\n * Returns a snapshot describing where the original value was, which\n * `restoreNavigatorProperty` uses to undo the install.\n */\nexport function installNavigatorProperty(\n prop: string,\n descriptor: PropertyDescriptor,\n): InstallSnapshot {\n const nav = navigator as unknown as Record<PropertyKey, unknown>;\n const instanceDesc = Object.getOwnPropertyDescriptor(nav, prop);\n const instanceHadOwn = instanceDesc !== undefined;\n\n // Fast path: instance-level property is missing or configurable.\n if (!instanceDesc || instanceDesc.configurable) {\n try {\n Object.defineProperty(nav, prop, descriptor);\n return { location: 'instance', originalDescriptor: instanceDesc, instanceHadOwn };\n } catch {\n // Fall through to prototype-level install.\n }\n }\n\n // Prototype-level install. Drop the instance-level shadow so the prototype\n // accessor is visible to readers on `navigator`.\n const proto = Object.getPrototypeOf(nav) as object;\n const protoDesc = Object.getOwnPropertyDescriptor(proto, prop);\n\n if (instanceHadOwn) {\n // Try to remove the instance-level shadow. On non-configurable it throws —\n // we deliberately ignore that; prototype-level install still wins because\n // the prototype accessor shows through when we read via `navigator[prop]`.\n try {\n delete nav[prop];\n } catch {\n /* non-configurable own — leave it; prototype install still useful */\n }\n }\n\n Object.defineProperty(proto, prop, descriptor);\n return { location: 'prototype', originalDescriptor: protoDesc, instanceHadOwn };\n}\n\n/**\n * Reverse the install recorded in `snapshot`. If the original descriptor was\n * `undefined` (property didn't exist before), delete the property instead of\n * re-defining it.\n */\nexport function restoreNavigatorProperty(prop: string, snapshot: InstallSnapshot): void {\n const target =\n snapshot.location === 'instance'\n ? (navigator as unknown as Record<PropertyKey, unknown>)\n : (Object.getPrototypeOf(navigator) as object);\n\n if (snapshot.originalDescriptor) {\n try {\n Object.defineProperty(target, prop, snapshot.originalDescriptor);\n } catch {\n /* descriptor was non-configurable upstream; we can't undo — rare. */\n }\n } else {\n try {\n // biome-ignore lint/performance/noDelete: property deletion is the uninstall intent\n delete (target as Record<PropertyKey, unknown>)[prop];\n } catch {\n /* non-configurable — rare. */\n }\n }\n\n // If our install pushed past an instance shadow, we leave the instance alone\n // — the descriptor we captured for `instanceHadOwn: true` lives on the\n // instance and was not modified at install time.\n}\n","/**\n * `navigator.share` shim.\n *\n * Inside Apps in Toss → routes through SDK `share({ message })`. The SDK only\n * accepts a single `message` string, so we concatenate `title`, `text`, and\n * `url` with newline separators (skipping missing/empty values).\n *\n * Outside Apps in Toss → defers to the browser's native `navigator.share`, or\n * throws `NotSupportedError` if unavailable.\n *\n * Caveat: the SDK's share has no counterpart for `files` (Web Share Level 2).\n * `canShare({ files })` returns `false` whenever the sync-accessible detection\n * says Toss is active (or is being forced via the test override).\n */\n\nimport { isTossEnvironment, isTossEnvironmentCached, loadTossSdk } from '../detect.js';\nimport {\n type InstallSnapshot,\n installNavigatorProperty,\n restoreNavigatorProperty,\n} from './_install-helpers.js';\n\nconst SHARE_BACKUP_KEY = Symbol.for('@ait-co/polyfill/share.original');\nconst SHARE_SNAPSHOT_KEY = Symbol.for('@ait-co/polyfill/share.snapshot');\nconst CAN_SHARE_SNAPSHOT_KEY = Symbol.for('@ait-co/polyfill/canShare.snapshot');\n\ntype ShareFn = (data?: ShareData) => Promise<void>;\ntype CanShareFn = (data?: ShareData) => boolean;\n\ninterface Backup {\n share?: ShareFn | undefined;\n canShare?: CanShareFn | undefined;\n hadShare: boolean;\n hadCanShare: boolean;\n}\n\ninterface BackupHost {\n [SHARE_BACKUP_KEY]?: Backup | undefined;\n [SHARE_SNAPSHOT_KEY]?: InstallSnapshot | undefined;\n [CAN_SHARE_SNAPSHOT_KEY]?: InstallSnapshot | undefined;\n}\n\nfunction buildSdkMessage(data: ShareData | undefined): string {\n // Use presence checks rather than truthiness so an intentionally empty\n // string in one field is handled correctly alongside a non-empty sibling.\n const parts: string[] = [];\n if (data?.title != null && data.title !== '') parts.push(data.title);\n if (data?.text != null && data.text !== '') parts.push(data.text);\n if (data?.url != null && data.url !== '') parts.push(data.url);\n return parts.join('\\n');\n}\n\nasync function shareShim(data?: ShareData): Promise<void> {\n if (await isTossEnvironment()) {\n const sdk = await loadTossSdk();\n const fn = (sdk as { share?: unknown } | null)?.share;\n if (typeof fn === 'function') {\n const message = buildSdkMessage(data);\n if (!message) {\n throw new TypeError(\n '[@ait-co/polyfill] navigator.share requires at least one of title, text, or url.',\n );\n }\n try {\n await (fn as (o: { message: string }) => Promise<void>)({ message });\n } catch (e) {\n // Spec says navigator.share rejects with a DOMException. Wrap SDK\n // errors as AbortError (the most common cause is user cancellation),\n // attaching the original as `.cause` for Sentry-style telemetry.\n const message_ = e instanceof Error ? e.message : String(e);\n const wrapped = new DOMException(message_, 'AbortError');\n if (e instanceof Error) {\n (wrapped as Error).cause = e;\n }\n throw wrapped;\n }\n return;\n }\n }\n const host = navigator as unknown as BackupHost;\n const backup = host[SHARE_BACKUP_KEY];\n const original = backup?.share;\n if (!original) {\n throw new DOMException(\n '[@ait-co/polyfill] navigator.share is not available in this environment.',\n 'NotSupportedError',\n );\n }\n return original.call(navigator, data);\n}\n\nfunction canShareShim(data?: ShareData): boolean {\n const hasFiles = Boolean(data?.files && data.files.length > 0);\n const toss = isTossEnvironmentCached();\n\n if (hasFiles) {\n // SDK does not share files. If we know we're in Toss (or it's being\n // forced), say so honestly. If detection hasn't resolved yet, be\n // pessimistic — a false negative is safer than promising a capability\n // we'll turn around and deny.\n if (toss === true) return false;\n if (toss === undefined) return false;\n }\n\n // Toss with non-file payloads: true iff there's at least one field.\n if (toss === true) {\n return Boolean(\n (data?.title != null && data.title !== '') ||\n (data?.text != null && data.text !== '') ||\n (data?.url != null && data.url !== ''),\n );\n }\n\n // `toss === undefined` (detection not resolved) with non-file payload falls\n // through to the browser-native answer. Rationale: `canShare` is rarely\n // load-bearing — consumers care about `share()` itself, which awaits the\n // async detection correctly. A false-negative here would needlessly hide a\n // Share button while detection settles.\n // Browser path: delegate to native when present.\n const host = navigator as unknown as BackupHost;\n const backup = host[SHARE_BACKUP_KEY];\n const originalCanShare = backup?.canShare;\n if (originalCanShare) {\n return originalCanShare.call(navigator, data);\n }\n return Boolean(\n (data?.title != null && data.title !== '') ||\n (data?.text != null && data.text !== '') ||\n (data?.url != null && data.url !== ''),\n );\n}\n\nexport function installShareShim(): () => void {\n if (typeof navigator === 'undefined') {\n return () => {};\n }\n\n const host = navigator as unknown as BackupHost;\n if (SHARE_BACKUP_KEY in host) {\n // Already installed. Use `in` so the absence of `share` / `canShare` on\n // the pre-install navigator (legitimately stored as `undefined`) doesn't\n // re-trigger install.\n return () => uninstallShareShim();\n }\n\n const nav = navigator as Navigator & {\n share?: ShareFn;\n canShare?: CanShareFn;\n };\n host[SHARE_BACKUP_KEY] = {\n share: nav.share,\n canShare: nav.canShare,\n hadShare: 'share' in nav,\n hadCanShare: 'canShare' in nav,\n };\n\n host[SHARE_SNAPSHOT_KEY] = installNavigatorProperty('share', {\n value: shareShim,\n configurable: true,\n writable: true,\n });\n host[CAN_SHARE_SNAPSHOT_KEY] = installNavigatorProperty('canShare', {\n value: canShareShim,\n configurable: true,\n writable: true,\n });\n\n return uninstallShareShim;\n}\n\nexport function uninstallShareShim(): void {\n if (typeof navigator === 'undefined') return;\n const host = navigator as unknown as BackupHost;\n if (!(SHARE_BACKUP_KEY in host)) return;\n\n const shareSnap = host[SHARE_SNAPSHOT_KEY];\n if (shareSnap) restoreNavigatorProperty('share', shareSnap);\n const canShareSnap = host[CAN_SHARE_SNAPSHOT_KEY];\n if (canShareSnap) restoreNavigatorProperty('canShare', canShareSnap);\n\n delete host[SHARE_BACKUP_KEY];\n delete host[SHARE_SNAPSHOT_KEY];\n delete host[CAN_SHARE_SNAPSHOT_KEY];\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"vibrate.d.ts","names":[],"sources":["../../src/shims/vibrate.ts"],"mappings":";;AA2GA;;;;;AAuBA;;;;;;;;;;;;;;;iBAvBgB,kBAAA,CAAA;AAAA,iBAuBA,oBAAA,CAAA"}
1
+ {"version":3,"file":"vibrate.d.ts","names":[],"sources":["../../src/shims/vibrate.ts"],"mappings":";;AAgHA;;;;;AAsBA;;;;;;;;;;;;;;;iBAtBgB,kBAAA,CAAA;AAAA,iBAsBA,oBAAA,CAAA"}
@@ -2,14 +2,19 @@
2
2
  /**
3
3
  * Environment detection: are we running inside Apps in Toss, or a plain browser?
4
4
  *
5
- * Strategy: feature-sniff `@apps-in-toss/web-framework`. The SDK is declared as
6
- * an **optional** peer dependency. If it resolves and exposes a known export,
7
- * we assume we can route calls through it; otherwise we fall back to the
8
- * browser's native implementation in each shim.
5
+ * Strategy: call the SDK's `getAppsInTossGlobals()` a synchronous export
6
+ * that returns the runtime's Toss globals (deploymentId, brand name, …)
7
+ * inside the Apps in Toss runtime and throws (RN bridge unavailable)
8
+ * anywhere else. The SDK itself is an **optional** peer dependency; if its
9
+ * module can't be imported we are definitely not inside Toss.
9
10
  *
10
- * We deliberately avoid UA sniffing (spoofable) and avoid calling any SDK
11
- * function during detection (could prompt permission dialogs, fire analytics,
12
- * etc.).
11
+ * Just having the SDK module resolvable is not enough apps can bundle it
12
+ * and still run in a plain browser. We need the bridge probe to confirm.
13
+ *
14
+ * UA sniffing (spoofable) is avoided. We do call `getAppsInTossGlobals`, but
15
+ * that's a constant read from the bridge — no permission dialogs, no
16
+ * analytics fire. In a plain browser the bridge lookup fails fast (sync
17
+ * throw, microsecond-scale), so the startup cost is negligible.
13
18
  */
14
19
  let cached;
15
20
  /**
@@ -24,7 +29,17 @@ async function isTossEnvironment() {
24
29
  if (force === "toss") return true;
25
30
  if (force === "browser") return false;
26
31
  if (cached !== void 0) return cached;
27
- cached = typeof (await loadTossSdk())?.getClipboardText === "function";
32
+ const mod = await loadTossSdk();
33
+ if (typeof mod?.getAppsInTossGlobals !== "function") {
34
+ cached = false;
35
+ return cached;
36
+ }
37
+ try {
38
+ const globals = mod.getAppsInTossGlobals();
39
+ cached = Boolean(globals) && typeof globals === "object";
40
+ } catch {
41
+ cached = false;
42
+ }
28
43
  return cached;
29
44
  }
30
45
  /**
@@ -39,6 +54,54 @@ async function loadTossSdk() {
39
54
  }
40
55
  }
41
56
  //#endregion
57
+ //#region src/shims/_install-helpers.ts
58
+ /**
59
+ * Install `descriptor` at `navigator[prop]`. Prefer instance-level; if the
60
+ * browser refuses (property is non-configurable on the instance), install on
61
+ * `Navigator.prototype` instead.
62
+ *
63
+ * Returns a snapshot describing where the original value was, which
64
+ * `restoreNavigatorProperty` uses to undo the install.
65
+ */
66
+ function installNavigatorProperty(prop, descriptor) {
67
+ const nav = navigator;
68
+ const instanceDesc = Object.getOwnPropertyDescriptor(nav, prop);
69
+ const instanceHadOwn = instanceDesc !== void 0;
70
+ if (!instanceDesc || instanceDesc.configurable) try {
71
+ Object.defineProperty(nav, prop, descriptor);
72
+ return {
73
+ location: "instance",
74
+ originalDescriptor: instanceDesc,
75
+ instanceHadOwn
76
+ };
77
+ } catch {}
78
+ const proto = Object.getPrototypeOf(nav);
79
+ const protoDesc = Object.getOwnPropertyDescriptor(proto, prop);
80
+ if (instanceHadOwn) try {
81
+ delete nav[prop];
82
+ } catch {}
83
+ Object.defineProperty(proto, prop, descriptor);
84
+ return {
85
+ location: "prototype",
86
+ originalDescriptor: protoDesc,
87
+ instanceHadOwn
88
+ };
89
+ }
90
+ /**
91
+ * Reverse the install recorded in `snapshot`. If the original descriptor was
92
+ * `undefined` (property didn't exist before), delete the property instead of
93
+ * re-defining it.
94
+ */
95
+ function restoreNavigatorProperty(prop, snapshot) {
96
+ const target = snapshot.location === "instance" ? navigator : Object.getPrototypeOf(navigator);
97
+ if (snapshot.originalDescriptor) try {
98
+ Object.defineProperty(target, prop, snapshot.originalDescriptor);
99
+ } catch {}
100
+ else try {
101
+ delete target[prop];
102
+ } catch {}
103
+ }
104
+ //#endregion
42
105
  //#region src/shims/vibrate.ts
43
106
  /**
44
107
  * `navigator.vibrate` shim.
@@ -62,7 +125,7 @@ async function loadTossSdk() {
62
125
  * `true` immediately (fire-and-forget). Errors from the SDK are swallowed.
63
126
  */
64
127
  const BACKUP_KEY = Symbol.for("@ait-co/polyfill/vibrate.original");
65
- const HAD_KEY = Symbol.for("@ait-co/polyfill/vibrate.hadOriginal");
128
+ const SNAPSHOT_KEY = Symbol.for("@ait-co/polyfill/vibrate.snapshot");
66
129
  const SHORT_VIBRATION_MS = 40;
67
130
  async function haptic(type) {
68
131
  const fn = (await loadTossSdk())?.generateHapticFeedback;
@@ -104,10 +167,8 @@ function installVibrateShim() {
104
167
  if (typeof navigator === "undefined") return () => {};
105
168
  const host = navigator;
106
169
  if (BACKUP_KEY in host) return () => uninstallVibrateShim();
107
- const nav = navigator;
108
- host[BACKUP_KEY] = nav.vibrate;
109
- host[HAD_KEY] = "vibrate" in nav;
110
- Object.defineProperty(navigator, "vibrate", {
170
+ host[BACKUP_KEY] = navigator.vibrate?.bind(navigator);
171
+ host[SNAPSHOT_KEY] = installNavigatorProperty("vibrate", {
111
172
  value: vibrateShim,
112
173
  configurable: true,
113
174
  writable: true
@@ -118,16 +179,10 @@ function uninstallVibrateShim() {
118
179
  if (typeof navigator === "undefined") return;
119
180
  const host = navigator;
120
181
  if (!(BACKUP_KEY in host)) return;
121
- const original = host[BACKUP_KEY];
122
- const had = host[HAD_KEY];
123
- delete navigator.vibrate;
124
- if (had && navigator.vibrate !== original) Object.defineProperty(navigator, "vibrate", {
125
- value: original,
126
- configurable: true,
127
- writable: true
128
- });
182
+ const snapshot = host[SNAPSHOT_KEY];
183
+ if (snapshot) restoreNavigatorProperty("vibrate", snapshot);
129
184
  delete host[BACKUP_KEY];
130
- delete host[HAD_KEY];
185
+ delete host[SNAPSHOT_KEY];
131
186
  }
132
187
  //#endregion
133
188
  export { installVibrateShim, uninstallVibrateShim };
@@ -1 +1 @@
1
- {"version":3,"file":"vibrate.js","names":[],"sources":["../../src/detect.ts","../../src/shims/vibrate.ts"],"sourcesContent":["/**\n * Environment detection: are we running inside Apps in Toss, or a plain browser?\n *\n * Strategy: feature-sniff `@apps-in-toss/web-framework`. The SDK is declared as\n * an **optional** peer dependency. If it resolves and exposes a known export,\n * we assume we can route calls through it; otherwise we fall back to the\n * browser's native implementation in each shim.\n *\n * We deliberately avoid UA sniffing (spoofable) and avoid calling any SDK\n * function during detection (could prompt permission dialogs, fire analytics,\n * etc.).\n */\n\nlet cached: boolean | undefined;\n\n/**\n * Reset the cached detection result. Primarily for tests.\n */\nexport function resetDetection(): void {\n cached = undefined;\n}\n\n/**\n * Synchronous read of the cached detection result. Returns:\n * - `true` / `false` if an override is active or the async detection has\n * already resolved\n * - `undefined` if detection hasn't run yet\n *\n * Used by spec-sync APIs (e.g. `navigator.canShare`) that can't `await`\n * detection.\n */\nexport function isTossEnvironmentCached(): boolean | undefined {\n const force = globalThis.__AIT_POLYFILL_FORCE__;\n if (force === 'toss') return true;\n if (force === 'browser') return false;\n return cached;\n}\n\n/**\n * Returns `true` iff we detect we are running in an environment where the\n * Apps in Toss SDK (`@apps-in-toss/web-framework`) is present and usable.\n *\n * Async because we use dynamic `import()` to probe the optional peer dep\n * without forcing it into the consumer's bundle.\n */\nexport async function isTossEnvironment(): Promise<boolean> {\n // Override check precedes cache so `devtools` / tests can flip the result\n // mid-session without a `resetDetection()` call.\n const force = globalThis.__AIT_POLYFILL_FORCE__;\n if (force === 'toss') return true;\n if (force === 'browser') return false;\n\n if (cached !== undefined) return cached;\n\n const mod = await loadTossSdk();\n // Presence of a well-known export is our smoke test.\n cached = typeof mod?.getClipboardText === 'function';\n return cached;\n}\n\n/**\n * Lazy SDK accessor — returns the module if available, else `null`. Callers\n * are expected to `await` and null-check. Never throws.\n */\nexport async function loadTossSdk(): Promise<typeof import('@apps-in-toss/web-framework') | null> {\n try {\n return await import('@apps-in-toss/web-framework');\n } catch {\n return null;\n }\n}\n","/**\n * `navigator.vibrate` shim.\n *\n * Inside Apps in Toss → best-effort mapping to SDK `generateHapticFeedback`:\n * - `vibrate(0)` → no-op (web standard: cancels pending vibration)\n * - `vibrate(number)`: short (< 40ms) → `tickWeak`, long (≥ 40ms) → `basicMedium`\n * - `vibrate(number[])`: iterate \"on\" segments (even indices) as `tap` pulses\n *\n * Outside Apps in Toss → defers to the browser's native `navigator.vibrate`,\n * or returns `false` when unavailable (matches the spec — browsers that don't\n * support vibration simply return `false`).\n *\n * Caveats (documented in CLAUDE.md as the known lossy trade-off):\n * - SDK haptics are qualitative (\"tickWeak\", \"basicMedium\"), not millisecond\n * durations. The shim approximates intensity from duration but cannot\n * reproduce exact patterns.\n * - Arrays are fired sequentially via `setTimeout`; gaps between pulses are\n * honoured only as \"time until the next tap\", not as silent-vs-vibrating.\n * - `vibrate` is spec'd as **synchronous**; the SDK call is async. We return\n * `true` immediately (fire-and-forget). Errors from the SDK are swallowed.\n */\n\nimport { isTossEnvironment, loadTossSdk } from '../detect.js';\n\nconst BACKUP_KEY = Symbol.for('@ait-co/polyfill/vibrate.original');\nconst HAD_KEY = Symbol.for('@ait-co/polyfill/vibrate.hadOriginal');\n\ninterface BackupHost {\n [BACKUP_KEY]?: ((pattern: VibratePattern) => boolean) | undefined;\n [HAD_KEY]?: boolean;\n}\n\nconst SHORT_VIBRATION_MS = 40;\n\ntype HapticType =\n | 'tickWeak'\n | 'tap'\n | 'tickMedium'\n | 'softMedium'\n | 'basicWeak'\n | 'basicMedium'\n | 'success'\n | 'error'\n | 'wiggle'\n | 'confetti';\n\nasync function haptic(type: HapticType): Promise<void> {\n const sdk = await loadTossSdk();\n const fn = (sdk as { generateHapticFeedback?: unknown } | null)?.generateHapticFeedback;\n if (typeof fn === 'function') {\n try {\n await (fn as (o: { type: HapticType }) => Promise<void>)({ type });\n } catch {\n // Best-effort; spec-level `vibrate` cannot surface errors.\n }\n }\n}\n\nfunction durationToHaptic(duration: number): HapticType {\n return duration < SHORT_VIBRATION_MS ? 'tickWeak' : 'basicMedium';\n}\n\nfunction vibrateShim(pattern: VibratePattern): boolean {\n // Matches the spec: `vibrate(0)` or `vibrate([])` cancels pending vibration.\n // We can't cancel an in-flight SDK haptic (no cancel API), but we still\n // forward the cancel to the browser fallback so native vibration stops.\n const arr = Array.isArray(pattern) ? pattern : [pattern];\n if (arr.length === 0 || arr.every((n) => n === 0)) {\n void (async () => {\n if (!(await isTossEnvironment())) {\n const host = navigator as unknown as BackupHost;\n host[BACKUP_KEY]?.call(navigator, pattern);\n }\n })();\n return true;\n }\n\n void (async () => {\n if (await isTossEnvironment()) {\n if (!Array.isArray(pattern)) {\n await haptic(durationToHaptic(pattern));\n return;\n }\n // Even indices = \"on\" durations, odd indices = pauses. `pattern[i]` is\n // `number | undefined` under `noUncheckedIndexedAccess`; the `undefined`\n // case only arises on out-of-bounds, which our length bound prevents.\n for (let i = 0; i < pattern.length; i += 2) {\n const on = pattern[i];\n if (on === undefined) break;\n if (on > 0) {\n await haptic('tap');\n }\n const pause = pattern[i + 1];\n if (typeof pause === 'number' && pause > 0) {\n await new Promise<void>((r) => setTimeout(r, pause));\n }\n }\n return;\n }\n const host = navigator as unknown as BackupHost;\n const original = host[BACKUP_KEY];\n original?.call(navigator, pattern);\n })();\n\n return true;\n}\n\nexport function installVibrateShim(): () => void {\n if (typeof navigator === 'undefined') {\n return () => {};\n }\n\n const host = navigator as unknown as BackupHost;\n if (BACKUP_KEY in host) {\n return () => uninstallVibrateShim();\n }\n\n const nav = navigator as Navigator & { vibrate?: (p: VibratePattern) => boolean };\n host[BACKUP_KEY] = nav.vibrate;\n host[HAD_KEY] = 'vibrate' in nav;\n\n Object.defineProperty(navigator, 'vibrate', {\n value: vibrateShim,\n configurable: true,\n writable: true,\n });\n\n return uninstallVibrateShim;\n}\n\nexport function uninstallVibrateShim(): void {\n if (typeof navigator === 'undefined') return;\n const host = navigator as unknown as BackupHost;\n if (!(BACKUP_KEY in host)) return;\n\n const original = host[BACKUP_KEY];\n const had = host[HAD_KEY];\n // Prototype-safe restore: delete the instance override first, then only\n // redefine on the instance if the original was an own property the\n // prototype doesn't provide — prevents permanent shadowing of a prototype\n // `vibrate` getter on real browsers.\n delete (navigator as unknown as { vibrate?: (p: VibratePattern) => boolean }).vibrate;\n if (had && navigator.vibrate !== original) {\n Object.defineProperty(navigator, 'vibrate', {\n value: original,\n configurable: true,\n writable: true,\n });\n }\n delete host[BACKUP_KEY];\n delete host[HAD_KEY];\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAI;;;;;;;;AAgCJ,eAAsB,oBAAsC;CAG1D,MAAM,QAAQ,WAAW;AACzB,KAAI,UAAU,OAAQ,QAAO;AAC7B,KAAI,UAAU,UAAW,QAAO;AAEhC,KAAI,WAAW,KAAA,EAAW,QAAO;AAIjC,UAAS,QAFG,MAAM,aAAa,GAEV,qBAAqB;AAC1C,QAAO;;;;;;AAOT,eAAsB,cAA4E;AAChG,KAAI;AACF,SAAO,MAAM,OAAO;SACd;AACN,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AC5CX,MAAM,aAAa,OAAO,IAAI,oCAAoC;AAClE,MAAM,UAAU,OAAO,IAAI,uCAAuC;AAOlE,MAAM,qBAAqB;AAc3B,eAAe,OAAO,MAAiC;CAErD,MAAM,MADM,MAAM,aAAa,GACkC;AACjE,KAAI,OAAO,OAAO,WAChB,KAAI;AACF,QAAO,GAAkD,EAAE,MAAM,CAAC;SAC5D;;AAMZ,SAAS,iBAAiB,UAA8B;AACtD,QAAO,WAAW,qBAAqB,aAAa;;AAGtD,SAAS,YAAY,SAAkC;CAIrD,MAAM,MAAM,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC,QAAQ;AACxD,KAAI,IAAI,WAAW,KAAK,IAAI,OAAO,MAAM,MAAM,EAAE,EAAE;AACjD,GAAM,YAAY;AAChB,OAAI,CAAE,MAAM,mBAAmB,CAChB,WACR,aAAa,KAAK,WAAW,QAAQ;MAE1C;AACJ,SAAO;;AAGT,EAAM,YAAY;AAChB,MAAI,MAAM,mBAAmB,EAAE;AAC7B,OAAI,CAAC,MAAM,QAAQ,QAAQ,EAAE;AAC3B,UAAM,OAAO,iBAAiB,QAAQ,CAAC;AACvC;;AAKF,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,GAAG;IAC1C,MAAM,KAAK,QAAQ;AACnB,QAAI,OAAO,KAAA,EAAW;AACtB,QAAI,KAAK,EACP,OAAM,OAAO,MAAM;IAErB,MAAM,QAAQ,QAAQ,IAAI;AAC1B,QAAI,OAAO,UAAU,YAAY,QAAQ,EACvC,OAAM,IAAI,SAAe,MAAM,WAAW,GAAG,MAAM,CAAC;;AAGxD;;AAEW,YACS,aACZ,KAAK,WAAW,QAAQ;KAChC;AAEJ,QAAO;;AAGT,SAAgB,qBAAiC;AAC/C,KAAI,OAAO,cAAc,YACvB,cAAa;CAGf,MAAM,OAAO;AACb,KAAI,cAAc,KAChB,cAAa,sBAAsB;CAGrC,MAAM,MAAM;AACZ,MAAK,cAAc,IAAI;AACvB,MAAK,WAAW,aAAa;AAE7B,QAAO,eAAe,WAAW,WAAW;EAC1C,OAAO;EACP,cAAc;EACd,UAAU;EACX,CAAC;AAEF,QAAO;;AAGT,SAAgB,uBAA6B;AAC3C,KAAI,OAAO,cAAc,YAAa;CACtC,MAAM,OAAO;AACb,KAAI,EAAE,cAAc,MAAO;CAE3B,MAAM,WAAW,KAAK;CACtB,MAAM,MAAM,KAAK;AAKjB,QAAQ,UAAsE;AAC9E,KAAI,OAAO,UAAU,YAAY,SAC/B,QAAO,eAAe,WAAW,WAAW;EAC1C,OAAO;EACP,cAAc;EACd,UAAU;EACX,CAAC;AAEJ,QAAO,KAAK;AACZ,QAAO,KAAK"}
1
+ {"version":3,"file":"vibrate.js","names":[],"sources":["../../src/detect.ts","../../src/shims/_install-helpers.ts","../../src/shims/vibrate.ts"],"sourcesContent":["/**\n * Environment detection: are we running inside Apps in Toss, or a plain browser?\n *\n * Strategy: call the SDK's `getAppsInTossGlobals()` — a synchronous export\n * that returns the runtime's Toss globals (deploymentId, brand name, …)\n * inside the Apps in Toss runtime and throws (RN bridge unavailable)\n * anywhere else. The SDK itself is an **optional** peer dependency; if its\n * module can't be imported we are definitely not inside Toss.\n *\n * Just having the SDK module resolvable is not enough — apps can bundle it\n * and still run in a plain browser. We need the bridge probe to confirm.\n *\n * UA sniffing (spoofable) is avoided. We do call `getAppsInTossGlobals`, but\n * that's a constant read from the bridge — no permission dialogs, no\n * analytics fire. In a plain browser the bridge lookup fails fast (sync\n * throw, microsecond-scale), so the startup cost is negligible.\n */\n\nlet cached: boolean | undefined;\n\n/**\n * Reset the cached detection result. Primarily for tests.\n */\nexport function resetDetection(): void {\n cached = undefined;\n}\n\n/**\n * Synchronous read of the cached detection result. Returns:\n * - `true` / `false` if an override is active or the async detection has\n * already resolved\n * - `undefined` if detection hasn't run yet\n *\n * Used by spec-sync APIs (e.g. `navigator.canShare`) that can't `await`\n * detection.\n */\nexport function isTossEnvironmentCached(): boolean | undefined {\n const force = globalThis.__AIT_POLYFILL_FORCE__;\n if (force === 'toss') return true;\n if (force === 'browser') return false;\n return cached;\n}\n\n/**\n * Returns `true` iff we detect we are running in an environment where the\n * Apps in Toss SDK (`@apps-in-toss/web-framework`) is present and usable.\n *\n * Async because we use dynamic `import()` to probe the optional peer dep\n * without forcing it into the consumer's bundle.\n */\nexport async function isTossEnvironment(): Promise<boolean> {\n // Override check precedes cache so `devtools` / tests can flip the result\n // mid-session without a `resetDetection()` call.\n const force = globalThis.__AIT_POLYFILL_FORCE__;\n if (force === 'toss') return true;\n if (force === 'browser') return false;\n\n if (cached !== undefined) return cached;\n\n const mod = await loadTossSdk();\n if (typeof mod?.getAppsInTossGlobals !== 'function') {\n cached = false;\n return cached;\n }\n // Inside Toss the bridge returns a populated globals object. In a plain\n // browser the RN bridge isn't attached and the call throws — that's our\n // signal. Any non-throwing call with an object return is treated as Toss.\n try {\n const globals = mod.getAppsInTossGlobals();\n cached = Boolean(globals) && typeof globals === 'object';\n } catch {\n cached = false;\n }\n return cached;\n}\n\n/**\n * Lazy SDK accessor — returns the module if available, else `null`. Callers\n * are expected to `await` and null-check. Never throws.\n */\nexport async function loadTossSdk(): Promise<typeof import('@apps-in-toss/web-framework') | null> {\n try {\n return await import('@apps-in-toss/web-framework');\n } catch {\n return null;\n }\n}\n","/**\n * Shared helpers for installing shims on `navigator`.\n *\n * Chromium now marks a handful of `navigator` properties (e.g. `geolocation`,\n * `clipboard`) as non-configurable **own** properties on the instance. That\n * means a plain `Object.defineProperty(navigator, 'x', …)` throws\n * `TypeError: Cannot redefine property`.\n *\n * The workaround is to shim at the prototype level — `Navigator.prototype`\n * keeps these as configurable accessors, so we can swap them there and every\n * instance that falls through to the prototype (including `window.navigator`)\n * sees the shim. We only reach for the prototype when the instance-level\n * assignment refuses.\n *\n * For restoration we remember the descriptor chain (instance + prototype) so\n * `uninstall()` puts the browser back in its original state.\n */\n\ntype PropertyLocation = 'instance' | 'prototype';\n\nexport interface InstallSnapshot {\n /** Where we ended up writing the shim. */\n location: PropertyLocation;\n /** Original descriptor at that location (may be undefined if nothing was there). */\n originalDescriptor: PropertyDescriptor | undefined;\n /** `true` iff the original property lived on the instance before we touched it. */\n instanceHadOwn: boolean;\n}\n\n/**\n * Install `descriptor` at `navigator[prop]`. Prefer instance-level; if the\n * browser refuses (property is non-configurable on the instance), install on\n * `Navigator.prototype` instead.\n *\n * Returns a snapshot describing where the original value was, which\n * `restoreNavigatorProperty` uses to undo the install.\n */\nexport function installNavigatorProperty(\n prop: string,\n descriptor: PropertyDescriptor,\n): InstallSnapshot {\n const nav = navigator as unknown as Record<PropertyKey, unknown>;\n const instanceDesc = Object.getOwnPropertyDescriptor(nav, prop);\n const instanceHadOwn = instanceDesc !== undefined;\n\n // Fast path: instance-level property is missing or configurable.\n if (!instanceDesc || instanceDesc.configurable) {\n try {\n Object.defineProperty(nav, prop, descriptor);\n return { location: 'instance', originalDescriptor: instanceDesc, instanceHadOwn };\n } catch {\n // Fall through to prototype-level install.\n }\n }\n\n // Prototype-level install. Drop the instance-level shadow so the prototype\n // accessor is visible to readers on `navigator`.\n const proto = Object.getPrototypeOf(nav) as object;\n const protoDesc = Object.getOwnPropertyDescriptor(proto, prop);\n\n if (instanceHadOwn) {\n // Try to remove the instance-level shadow. On non-configurable it throws —\n // we deliberately ignore that; prototype-level install still wins because\n // the prototype accessor shows through when we read via `navigator[prop]`.\n try {\n delete nav[prop];\n } catch {\n /* non-configurable own — leave it; prototype install still useful */\n }\n }\n\n Object.defineProperty(proto, prop, descriptor);\n return { location: 'prototype', originalDescriptor: protoDesc, instanceHadOwn };\n}\n\n/**\n * Reverse the install recorded in `snapshot`. If the original descriptor was\n * `undefined` (property didn't exist before), delete the property instead of\n * re-defining it.\n */\nexport function restoreNavigatorProperty(prop: string, snapshot: InstallSnapshot): void {\n const target =\n snapshot.location === 'instance'\n ? (navigator as unknown as Record<PropertyKey, unknown>)\n : (Object.getPrototypeOf(navigator) as object);\n\n if (snapshot.originalDescriptor) {\n try {\n Object.defineProperty(target, prop, snapshot.originalDescriptor);\n } catch {\n /* descriptor was non-configurable upstream; we can't undo — rare. */\n }\n } else {\n try {\n // biome-ignore lint/performance/noDelete: property deletion is the uninstall intent\n delete (target as Record<PropertyKey, unknown>)[prop];\n } catch {\n /* non-configurable — rare. */\n }\n }\n\n // If our install pushed past an instance shadow, we leave the instance alone\n // — the descriptor we captured for `instanceHadOwn: true` lives on the\n // instance and was not modified at install time.\n}\n","/**\n * `navigator.vibrate` shim.\n *\n * Inside Apps in Toss → best-effort mapping to SDK `generateHapticFeedback`:\n * - `vibrate(0)` → no-op (web standard: cancels pending vibration)\n * - `vibrate(number)`: short (< 40ms) → `tickWeak`, long (≥ 40ms) → `basicMedium`\n * - `vibrate(number[])`: iterate \"on\" segments (even indices) as `tap` pulses\n *\n * Outside Apps in Toss → defers to the browser's native `navigator.vibrate`,\n * or returns `false` when unavailable (matches the spec — browsers that don't\n * support vibration simply return `false`).\n *\n * Caveats (documented in CLAUDE.md as the known lossy trade-off):\n * - SDK haptics are qualitative (\"tickWeak\", \"basicMedium\"), not millisecond\n * durations. The shim approximates intensity from duration but cannot\n * reproduce exact patterns.\n * - Arrays are fired sequentially via `setTimeout`; gaps between pulses are\n * honoured only as \"time until the next tap\", not as silent-vs-vibrating.\n * - `vibrate` is spec'd as **synchronous**; the SDK call is async. We return\n * `true` immediately (fire-and-forget). Errors from the SDK are swallowed.\n */\n\nimport { isTossEnvironment, loadTossSdk } from '../detect.js';\nimport {\n type InstallSnapshot,\n installNavigatorProperty,\n restoreNavigatorProperty,\n} from './_install-helpers.js';\n\nconst BACKUP_KEY = Symbol.for('@ait-co/polyfill/vibrate.original');\nconst SNAPSHOT_KEY = Symbol.for('@ait-co/polyfill/vibrate.snapshot');\n\ninterface BackupHost {\n [BACKUP_KEY]?: ((pattern: VibratePattern) => boolean) | undefined;\n [SNAPSHOT_KEY]?: InstallSnapshot | undefined;\n}\n\nconst SHORT_VIBRATION_MS = 40;\n\ntype HapticType =\n | 'tickWeak'\n | 'tap'\n | 'tickMedium'\n | 'softMedium'\n | 'basicWeak'\n | 'basicMedium'\n | 'success'\n | 'error'\n | 'wiggle'\n | 'confetti';\n\nasync function haptic(type: HapticType): Promise<void> {\n const sdk = await loadTossSdk();\n const fn = (sdk as { generateHapticFeedback?: unknown } | null)?.generateHapticFeedback;\n if (typeof fn === 'function') {\n try {\n await (fn as (o: { type: HapticType }) => Promise<void>)({ type });\n } catch {\n // Best-effort; spec-level `vibrate` cannot surface errors.\n }\n }\n}\n\nfunction durationToHaptic(duration: number): HapticType {\n return duration < SHORT_VIBRATION_MS ? 'tickWeak' : 'basicMedium';\n}\n\nfunction vibrateShim(pattern: VibratePattern): boolean {\n // Matches the spec: `vibrate(0)` or `vibrate([])` cancels pending vibration.\n // We can't cancel an in-flight SDK haptic (no cancel API), but we still\n // forward the cancel to the browser fallback so native vibration stops.\n const arr = Array.isArray(pattern) ? pattern : [pattern];\n if (arr.length === 0 || arr.every((n) => n === 0)) {\n void (async () => {\n if (!(await isTossEnvironment())) {\n const host = navigator as unknown as BackupHost;\n host[BACKUP_KEY]?.call(navigator, pattern);\n }\n })();\n return true;\n }\n\n void (async () => {\n if (await isTossEnvironment()) {\n if (!Array.isArray(pattern)) {\n await haptic(durationToHaptic(pattern));\n return;\n }\n // Even indices = \"on\" durations, odd indices = pauses. `pattern[i]` is\n // `number | undefined` under `noUncheckedIndexedAccess`; the `undefined`\n // case only arises on out-of-bounds, which our length bound prevents.\n for (let i = 0; i < pattern.length; i += 2) {\n const on = pattern[i];\n if (on === undefined) break;\n if (on > 0) {\n await haptic('tap');\n }\n const pause = pattern[i + 1];\n if (typeof pause === 'number' && pause > 0) {\n await new Promise<void>((r) => setTimeout(r, pause));\n }\n }\n return;\n }\n const host = navigator as unknown as BackupHost;\n const original = host[BACKUP_KEY];\n original?.call(navigator, pattern);\n })();\n\n return true;\n}\n\nexport function installVibrateShim(): () => void {\n if (typeof navigator === 'undefined') {\n return () => {};\n }\n\n const host = navigator as unknown as BackupHost;\n if (BACKUP_KEY in host) {\n return () => uninstallVibrateShim();\n }\n\n const nav = navigator as Navigator & { vibrate?: (p: VibratePattern) => boolean };\n host[BACKUP_KEY] = nav.vibrate?.bind(navigator);\n\n host[SNAPSHOT_KEY] = installNavigatorProperty('vibrate', {\n value: vibrateShim,\n configurable: true,\n writable: true,\n });\n\n return uninstallVibrateShim;\n}\n\nexport function uninstallVibrateShim(): void {\n if (typeof navigator === 'undefined') return;\n const host = navigator as unknown as BackupHost;\n if (!(BACKUP_KEY in host)) return;\n\n const snapshot = host[SNAPSHOT_KEY];\n if (snapshot) restoreNavigatorProperty('vibrate', snapshot);\n delete host[BACKUP_KEY];\n delete host[SNAPSHOT_KEY];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,IAAI;;;;;;;;AAgCJ,eAAsB,oBAAsC;CAG1D,MAAM,QAAQ,WAAW;AACzB,KAAI,UAAU,OAAQ,QAAO;AAC7B,KAAI,UAAU,UAAW,QAAO;AAEhC,KAAI,WAAW,KAAA,EAAW,QAAO;CAEjC,MAAM,MAAM,MAAM,aAAa;AAC/B,KAAI,OAAO,KAAK,yBAAyB,YAAY;AACnD,WAAS;AACT,SAAO;;AAKT,KAAI;EACF,MAAM,UAAU,IAAI,sBAAsB;AAC1C,WAAS,QAAQ,QAAQ,IAAI,OAAO,YAAY;SAC1C;AACN,WAAS;;AAEX,QAAO;;;;;;AAOT,eAAsB,cAA4E;AAChG,KAAI;AACF,SAAO,MAAM,OAAO;SACd;AACN,SAAO;;;;;;;;;;;;;AC/CX,SAAgB,yBACd,MACA,YACiB;CACjB,MAAM,MAAM;CACZ,MAAM,eAAe,OAAO,yBAAyB,KAAK,KAAK;CAC/D,MAAM,iBAAiB,iBAAiB,KAAA;AAGxC,KAAI,CAAC,gBAAgB,aAAa,aAChC,KAAI;AACF,SAAO,eAAe,KAAK,MAAM,WAAW;AAC5C,SAAO;GAAE,UAAU;GAAY,oBAAoB;GAAc;GAAgB;SAC3E;CAOV,MAAM,QAAQ,OAAO,eAAe,IAAI;CACxC,MAAM,YAAY,OAAO,yBAAyB,OAAO,KAAK;AAE9D,KAAI,eAIF,KAAI;AACF,SAAO,IAAI;SACL;AAKV,QAAO,eAAe,OAAO,MAAM,WAAW;AAC9C,QAAO;EAAE,UAAU;EAAa,oBAAoB;EAAW;EAAgB;;;;;;;AAQjF,SAAgB,yBAAyB,MAAc,UAAiC;CACtF,MAAM,SACJ,SAAS,aAAa,aACjB,YACA,OAAO,eAAe,UAAU;AAEvC,KAAI,SAAS,mBACX,KAAI;AACF,SAAO,eAAe,QAAQ,MAAM,SAAS,mBAAmB;SAC1D;KAIR,KAAI;AAEF,SAAQ,OAAwC;SAC1C;;;;;;;;;;;;;;;;;;;;;;;;;ACnEZ,MAAM,aAAa,OAAO,IAAI,oCAAoC;AAClE,MAAM,eAAe,OAAO,IAAI,oCAAoC;AAOpE,MAAM,qBAAqB;AAc3B,eAAe,OAAO,MAAiC;CAErD,MAAM,MADM,MAAM,aAAa,GACkC;AACjE,KAAI,OAAO,OAAO,WAChB,KAAI;AACF,QAAO,GAAkD,EAAE,MAAM,CAAC;SAC5D;;AAMZ,SAAS,iBAAiB,UAA8B;AACtD,QAAO,WAAW,qBAAqB,aAAa;;AAGtD,SAAS,YAAY,SAAkC;CAIrD,MAAM,MAAM,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC,QAAQ;AACxD,KAAI,IAAI,WAAW,KAAK,IAAI,OAAO,MAAM,MAAM,EAAE,EAAE;AACjD,GAAM,YAAY;AAChB,OAAI,CAAE,MAAM,mBAAmB,CAChB,WACR,aAAa,KAAK,WAAW,QAAQ;MAE1C;AACJ,SAAO;;AAGT,EAAM,YAAY;AAChB,MAAI,MAAM,mBAAmB,EAAE;AAC7B,OAAI,CAAC,MAAM,QAAQ,QAAQ,EAAE;AAC3B,UAAM,OAAO,iBAAiB,QAAQ,CAAC;AACvC;;AAKF,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,GAAG;IAC1C,MAAM,KAAK,QAAQ;AACnB,QAAI,OAAO,KAAA,EAAW;AACtB,QAAI,KAAK,EACP,OAAM,OAAO,MAAM;IAErB,MAAM,QAAQ,QAAQ,IAAI;AAC1B,QAAI,OAAO,UAAU,YAAY,QAAQ,EACvC,OAAM,IAAI,SAAe,MAAM,WAAW,GAAG,MAAM,CAAC;;AAGxD;;AAEW,YACS,aACZ,KAAK,WAAW,QAAQ;KAChC;AAEJ,QAAO;;AAGT,SAAgB,qBAAiC;AAC/C,KAAI,OAAO,cAAc,YACvB,cAAa;CAGf,MAAM,OAAO;AACb,KAAI,cAAc,KAChB,cAAa,sBAAsB;AAIrC,MAAK,cADO,UACW,SAAS,KAAK,UAAU;AAE/C,MAAK,gBAAgB,yBAAyB,WAAW;EACvD,OAAO;EACP,cAAc;EACd,UAAU;EACX,CAAC;AAEF,QAAO;;AAGT,SAAgB,uBAA6B;AAC3C,KAAI,OAAO,cAAc,YAAa;CACtC,MAAM,OAAO;AACb,KAAI,EAAE,cAAc,MAAO;CAE3B,MAAM,WAAW,KAAK;AACtB,KAAI,SAAU,0BAAyB,WAAW,SAAS;AAC3D,QAAO,KAAK;AACZ,QAAO,KAAK"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ait-co/polyfill",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Polyfill so you can build Apps in Toss mini-apps with standard Web APIs (navigator.clipboard, navigator.geolocation, ...) instead of the proprietary SDK",
5
5
  "type": "module",
6
6
  "engines": {
@@ -36,12 +36,18 @@
36
36
  "./detect": {
37
37
  "types": "./dist/detect.d.ts",
38
38
  "import": "./dist/detect.js"
39
+ },
40
+ "./auto": {
41
+ "types": "./dist/auto.d.ts",
42
+ "import": "./dist/auto.js"
39
43
  }
40
44
  },
41
45
  "files": [
42
46
  "dist"
43
47
  ],
44
- "sideEffects": false,
48
+ "sideEffects": [
49
+ "./dist/auto.js"
50
+ ],
45
51
  "scripts": {
46
52
  "build": "tsdown",
47
53
  "dev": "tsdown --watch",