@monetize.software/sdk 3.0.0-alpha.1 → 3.0.0-alpha.10

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.
Files changed (129) hide show
  1. package/README.md +34 -13
  2. package/dist/chunks/PaywallUI-CQG9HCwo.js +3245 -0
  3. package/dist/chunks/PaywallUI-CQG9HCwo.js.map +1 -0
  4. package/dist/chunks/PaywallUI-DQ1Jke8b.js +26 -0
  5. package/dist/chunks/PaywallUI-DQ1Jke8b.js.map +1 -0
  6. package/dist/chunks/ar-7cgIM-Vl.js +2 -0
  7. package/dist/chunks/ar-7cgIM-Vl.js.map +1 -0
  8. package/dist/chunks/ar-B2Wg_IrC.js +126 -0
  9. package/dist/chunks/ar-B2Wg_IrC.js.map +1 -0
  10. package/dist/chunks/cs-BNo9Dx0Q.js +122 -0
  11. package/dist/chunks/cs-BNo9Dx0Q.js.map +1 -0
  12. package/dist/chunks/cs-S05PC5AC.js +2 -0
  13. package/dist/chunks/cs-S05PC5AC.js.map +1 -0
  14. package/dist/chunks/da-Bi4zBG14.js +2 -0
  15. package/dist/chunks/da-Bi4zBG14.js.map +1 -0
  16. package/dist/chunks/da-Do9Lq6En.js +122 -0
  17. package/dist/chunks/da-Do9Lq6En.js.map +1 -0
  18. package/dist/chunks/de-C8pDZNvx.js +141 -0
  19. package/dist/chunks/de-C8pDZNvx.js.map +1 -0
  20. package/dist/chunks/de-nCDB6D2W.js +2 -0
  21. package/dist/chunks/de-nCDB6D2W.js.map +1 -0
  22. package/dist/chunks/el-BrKaa978.js +2 -0
  23. package/dist/chunks/el-BrKaa978.js.map +1 -0
  24. package/dist/chunks/el-DzMNX-_P.js +126 -0
  25. package/dist/chunks/el-DzMNX-_P.js.map +1 -0
  26. package/dist/chunks/es-B-Wtyzrl.js +2 -0
  27. package/dist/chunks/es-B-Wtyzrl.js.map +1 -0
  28. package/dist/chunks/es-YrKt-q4w.js +141 -0
  29. package/dist/chunks/es-YrKt-q4w.js.map +1 -0
  30. package/dist/chunks/fi-Bh44pwZ4.js +122 -0
  31. package/dist/chunks/fi-Bh44pwZ4.js.map +1 -0
  32. package/dist/chunks/fi-D1SGXjnO.js +2 -0
  33. package/dist/chunks/fi-D1SGXjnO.js.map +1 -0
  34. package/dist/chunks/fr-Bc0pw4ws.js +141 -0
  35. package/dist/chunks/fr-Bc0pw4ws.js.map +1 -0
  36. package/dist/chunks/fr-BhYf-iKk.js +2 -0
  37. package/dist/chunks/fr-BhYf-iKk.js.map +1 -0
  38. package/dist/chunks/he-BXAaFv6Y.js +2 -0
  39. package/dist/chunks/he-BXAaFv6Y.js.map +1 -0
  40. package/dist/chunks/he-Bfm-bhe3.js +126 -0
  41. package/dist/chunks/he-Bfm-bhe3.js.map +1 -0
  42. package/dist/chunks/hi-D-O-B9Dn.js +126 -0
  43. package/dist/chunks/hi-D-O-B9Dn.js.map +1 -0
  44. package/dist/chunks/hi-xblDO0O7.js +2 -0
  45. package/dist/chunks/hi-xblDO0O7.js.map +1 -0
  46. package/dist/chunks/hu-CmIuAbLL.js +122 -0
  47. package/dist/chunks/hu-CmIuAbLL.js.map +1 -0
  48. package/dist/chunks/hu-Wa46p0y4.js +2 -0
  49. package/dist/chunks/hu-Wa46p0y4.js.map +1 -0
  50. package/dist/chunks/id-CQEo5X94.js +2 -0
  51. package/dist/chunks/id-CQEo5X94.js.map +1 -0
  52. package/dist/chunks/id-DN7IES-A.js +122 -0
  53. package/dist/chunks/id-DN7IES-A.js.map +1 -0
  54. package/dist/chunks/it-8AYCm0xz.js +2 -0
  55. package/dist/chunks/it-8AYCm0xz.js.map +1 -0
  56. package/dist/chunks/it-Cz5Nmqx5.js +141 -0
  57. package/dist/chunks/it-Cz5Nmqx5.js.map +1 -0
  58. package/dist/chunks/ja-BH9BlBh2.js +145 -0
  59. package/dist/chunks/ja-BH9BlBh2.js.map +1 -0
  60. package/dist/chunks/ja-q-COVayn.js +2 -0
  61. package/dist/chunks/ja-q-COVayn.js.map +1 -0
  62. package/dist/chunks/ko-B6HRCscZ.js +2 -0
  63. package/dist/chunks/ko-B6HRCscZ.js.map +1 -0
  64. package/dist/chunks/ko-CYV9QuYs.js +145 -0
  65. package/dist/chunks/ko-CYV9QuYs.js.map +1 -0
  66. package/dist/chunks/nl-BvkB900D.js +141 -0
  67. package/dist/chunks/nl-BvkB900D.js.map +1 -0
  68. package/dist/chunks/nl-CAd6_xlm.js +2 -0
  69. package/dist/chunks/nl-CAd6_xlm.js.map +1 -0
  70. package/dist/chunks/no-3s9_ormb.js +122 -0
  71. package/dist/chunks/no-3s9_ormb.js.map +1 -0
  72. package/dist/chunks/no-CAmz6bz6.js +2 -0
  73. package/dist/chunks/no-CAmz6bz6.js.map +1 -0
  74. package/dist/chunks/pl-C9WTGQtb.js +122 -0
  75. package/dist/chunks/pl-C9WTGQtb.js.map +1 -0
  76. package/dist/chunks/pl-DqUSTCaF.js +2 -0
  77. package/dist/chunks/pl-DqUSTCaF.js.map +1 -0
  78. package/dist/chunks/pt-8ARZnH0_.js +2 -0
  79. package/dist/chunks/pt-8ARZnH0_.js.map +1 -0
  80. package/dist/chunks/pt-uFVUv_Op.js +141 -0
  81. package/dist/chunks/pt-uFVUv_Op.js.map +1 -0
  82. package/dist/chunks/ro-BrqQ8Au-.js +122 -0
  83. package/dist/chunks/ro-BrqQ8Au-.js.map +1 -0
  84. package/dist/chunks/ro-D-NMbp2F.js +2 -0
  85. package/dist/chunks/ro-D-NMbp2F.js.map +1 -0
  86. package/dist/chunks/ru-8gbHPh0g.js +2 -0
  87. package/dist/chunks/ru-8gbHPh0g.js.map +1 -0
  88. package/dist/chunks/ru-DK594dA8.js +144 -0
  89. package/dist/chunks/ru-DK594dA8.js.map +1 -0
  90. package/dist/chunks/sv-CHNH8-mq.js +122 -0
  91. package/dist/chunks/sv-CHNH8-mq.js.map +1 -0
  92. package/dist/chunks/sv-D8a8hmx9.js +2 -0
  93. package/dist/chunks/sv-D8a8hmx9.js.map +1 -0
  94. package/dist/chunks/th-DfjUK0Y7.js +2 -0
  95. package/dist/chunks/th-DfjUK0Y7.js.map +1 -0
  96. package/dist/chunks/th-l24Pm5q-.js +126 -0
  97. package/dist/chunks/th-l24Pm5q-.js.map +1 -0
  98. package/dist/chunks/tr-ADpigSY5.js +122 -0
  99. package/dist/chunks/tr-ADpigSY5.js.map +1 -0
  100. package/dist/chunks/tr-BdBpz4tL.js +2 -0
  101. package/dist/chunks/tr-BdBpz4tL.js.map +1 -0
  102. package/dist/chunks/uk-CGqo4jek.js +144 -0
  103. package/dist/chunks/uk-CGqo4jek.js.map +1 -0
  104. package/dist/chunks/uk-Cx1zv1ao.js +2 -0
  105. package/dist/chunks/uk-Cx1zv1ao.js.map +1 -0
  106. package/dist/chunks/vi-Dk9bTu6f.js +122 -0
  107. package/dist/chunks/vi-Dk9bTu6f.js.map +1 -0
  108. package/dist/chunks/vi-oe2dW21I.js +2 -0
  109. package/dist/chunks/vi-oe2dW21I.js.map +1 -0
  110. package/dist/chunks/zh-CwczPMPp.js +2 -0
  111. package/dist/chunks/zh-CwczPMPp.js.map +1 -0
  112. package/dist/chunks/zh-LDkEV2D9.js +145 -0
  113. package/dist/chunks/zh-LDkEV2D9.js.map +1 -0
  114. package/dist/core.cjs +1 -1
  115. package/dist/core.cjs.map +1 -1
  116. package/dist/core.d.ts +265 -24
  117. package/dist/core.js +519 -313
  118. package/dist/core.js.map +1 -1
  119. package/dist/index.cjs +1 -1
  120. package/dist/index.d.ts +355 -43
  121. package/dist/index.js +14 -10
  122. package/dist/ui.cjs +1 -1
  123. package/dist/ui.d.ts +322 -44
  124. package/dist/ui.js +1 -1
  125. package/package.json +32 -31
  126. package/dist/chunks/PaywallUI-BHp9afFC.js +0 -2209
  127. package/dist/chunks/PaywallUI-BHp9afFC.js.map +0 -1
  128. package/dist/chunks/PaywallUI-Dr-6q-HL.js +0 -26
  129. package/dist/chunks/PaywallUI-Dr-6q-HL.js.map +0 -1
package/dist/core.js CHANGED
@@ -10,21 +10,21 @@ class R extends r {
10
10
  }), this.name = "QuotaExceededError", this.balances = t.balances, this.queryType = t.queryType, this.currentBalance = t.currentBalance;
11
11
  }
12
12
  }
13
- const g = "3.0.0-alpha.0";
14
- class k {
13
+ const m = "3.0.0-alpha.0";
14
+ class E {
15
15
  constructor(t) {
16
16
  this.opts = t;
17
17
  }
18
18
  async request(t, e = {}) {
19
- const s = new URL(t, this.opts.apiOrigin).toString(), i = this.opts.fetch ?? fetch, n = new Headers(e.headers);
20
- n.set("Accept", "application/json"), n.set("X-SDK-Version", g), n.set("X-Paywall-Id", this.opts.paywallId), this.opts.capabilities?.length && n.set("X-SDK-Capabilities", this.opts.capabilities.join(","));
19
+ const s = new URL(t, this.opts.apiOrigin).toString(), a = this.opts.fetch ?? fetch, n = new Headers(e.headers);
20
+ n.set("Accept", "application/json"), n.set("X-SDK-Version", m), n.set("X-Paywall-Id", this.opts.paywallId), this.opts.capabilities?.length && n.set("X-SDK-Capabilities", this.opts.capabilities.join(","));
21
21
  const o = await this.opts.getAuthToken?.();
22
22
  o && n.set("Authorization", `Bearer ${o}`);
23
23
  const u = typeof FormData < "u" && e.body instanceof FormData;
24
24
  e.body && !n.has("Content-Type") && !u && n.set("Content-Type", "application/json");
25
- let l;
25
+ let d;
26
26
  try {
27
- l = await i(s, {
27
+ d = await a(s, {
28
28
  ...e,
29
29
  headers: n,
30
30
  credentials: "omit"
@@ -32,20 +32,24 @@ class k {
32
32
  } catch (c) {
33
33
  throw (c && typeof c == "object" && "name" in c ? c.name : void 0) === "AbortError" ? new r("aborted", "Request aborted", { cause: c }) : new r("network_error", "Network request failed", { cause: c });
34
34
  }
35
- const f = (l.headers.get("content-type") ?? "").includes("application/json") ? await l.json().catch(() => null) : null;
36
- if (!l.ok) {
37
- const c = f && typeof f == "object" && "code" in f && String(f.code) || `http_${l.status}`, w = f && typeof f == "object" && "message" in f && String(f.message) || l.statusText || "Request failed";
38
- throw new r(c, w, { status: l.status, cause: f });
35
+ const y = (d.headers.get("content-type") ?? "").includes("application/json") ? await d.json().catch(() => null) : null;
36
+ if (!d.ok) {
37
+ const c = y && typeof y == "object" && "code" in y && String(y.code) || `http_${d.status}`, g = y && typeof y == "object" && "message" in y && String(y.message) || d.statusText || "Request failed";
38
+ throw new r(c, g, { status: d.status, cause: y });
39
39
  }
40
- return f;
40
+ return y;
41
41
  }
42
42
  }
43
- const q = "https://appbox.space";
44
- class K {
43
+ class D {
45
44
  constructor(t) {
46
45
  if (!t.paywallId)
47
46
  throw new r("invalid_config", "paywallId is required");
48
- this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin ?? q, this.auth = t.auth, this.userId = t.userId, this.capabilities = t.capabilities, this.customFetch = t.fetch, this.onChargeSuccess = t.onChargeSuccess, this.onQuotaExceeded = t.onQuotaExceeded, t.userId && !t.auth && typeof window < "u" && typeof window.document < "u" && console.warn(
47
+ if (!t.apiOrigin)
48
+ throw new r(
49
+ "invalid_config",
50
+ "apiOrigin is required. Pass the paywall custom_domain configured in the platform."
51
+ );
52
+ this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin, this.auth = t.auth, this.userId = t.userId, this.capabilities = t.capabilities, this.customFetch = t.fetch, this.onChargeSuccess = t.onChargeSuccess, this.onQuotaExceeded = t.onQuotaExceeded, t.userId && !t.auth && typeof window < "u" && typeof window.document < "u" && console.warn(
49
53
  "[paywall] WARNING: ApiGatewayClient.userId set without auth in browser. Client can spoof userId. Use AuthClient + Bearer for trusted user.id."
50
54
  );
51
55
  }
@@ -55,19 +59,19 @@ class K {
55
59
  this.apiOrigin
56
60
  );
57
61
  s.searchParams.set("paywall_id", this.paywallId);
58
- const i = new Headers(t.headers);
59
- i.set("X-SDK-Version", g), i.set("X-Paywall-Id", this.paywallId), this.capabilities?.length && i.set("X-SDK-Capabilities", this.capabilities.join(","));
62
+ const a = new Headers(t.headers);
63
+ a.set("X-SDK-Version", m), a.set("X-Paywall-Id", this.paywallId), this.capabilities?.length && a.set("X-SDK-Capabilities", this.capabilities.join(","));
60
64
  const n = await this.auth?.getAccessToken();
61
- n ? i.set("Authorization", `Bearer ${n}`) : this.userId && i.set("X-User-ID", this.userId);
62
- const o = typeof FormData < "u" && t.body instanceof FormData, u = typeof Blob < "u" && t.body instanceof Blob, l = typeof ReadableStream < "u" && t.body instanceof ReadableStream, d = typeof t.body == "string";
65
+ n ? a.set("Authorization", `Bearer ${n}`) : this.userId && a.set("X-User-ID", this.userId);
66
+ const o = typeof FormData < "u" && t.body instanceof FormData, u = typeof Blob < "u" && t.body instanceof Blob, d = typeof ReadableStream < "u" && t.body instanceof ReadableStream, f = typeof t.body == "string";
63
67
  let h;
64
- t.body === void 0 || t.body === null ? h = void 0 : o || u || l || d ? h = t.body : (h = JSON.stringify(t.body), i.has("Content-Type") || i.set("Content-Type", "application/json"));
65
- const f = this.customFetch ?? fetch;
68
+ t.body === void 0 || t.body === null ? h = void 0 : o || u || d || f ? h = t.body : (h = JSON.stringify(t.body), a.has("Content-Type") || a.set("Content-Type", "application/json"));
69
+ const y = this.customFetch ?? fetch;
66
70
  let c;
67
71
  try {
68
- c = await f(s.toString(), {
72
+ c = await y(s.toString(), {
69
73
  method: t.method ?? "POST",
70
- headers: i,
74
+ headers: a,
71
75
  body: h,
72
76
  signal: t.signal,
73
77
  credentials: "omit"
@@ -77,32 +81,32 @@ class K {
77
81
  throw new r("network_error", `Network request failed: ${P}`, { cause: p });
78
82
  }
79
83
  if (c.status === 402) {
80
- const p = await $(c);
84
+ const p = await q(c);
81
85
  throw this.onQuotaExceeded?.(p), p;
82
86
  }
83
87
  if (!c.ok) {
84
- const p = await F(c.clone());
88
+ const p = await $(c.clone());
85
89
  throw new r(
86
90
  p ?? `http_${c.status}`,
87
91
  c.statusText || "Gateway request failed",
88
92
  { status: c.status }
89
93
  );
90
94
  }
91
- const w = c.headers.get("X-Query-Type") ?? void 0;
92
- return this.onChargeSuccess?.(w), c;
95
+ const g = c.headers.get("X-Query-Type") ?? void 0;
96
+ return this.onChargeSuccess?.(g), c;
93
97
  }
94
98
  }
95
- async function $(a) {
99
+ async function q(i) {
96
100
  let t = {};
97
101
  try {
98
- t = await a.json();
102
+ t = await i.json();
99
103
  } catch {
100
104
  }
101
105
  const e = t.details?.balances;
102
106
  let s = [];
103
107
  if (Array.isArray(e)) {
104
- const i = e[0];
105
- Array.isArray(i) ? s = i : i && Array.isArray(i.balances) && (s = i.balances);
108
+ const a = e[0];
109
+ Array.isArray(a) ? s = a : a && Array.isArray(a.balances) && (s = a.balances);
106
110
  }
107
111
  return new R({
108
112
  balances: s,
@@ -110,174 +114,179 @@ async function $(a) {
110
114
  currentBalance: t.details?.currentBalance ?? null
111
115
  });
112
116
  }
113
- async function F(a) {
114
- if (!(a.headers.get("content-type") ?? "").includes("application/json")) return null;
117
+ async function $(i) {
118
+ if (!(i.headers.get("content-type") ?? "").includes("application/json")) return null;
115
119
  try {
116
- const e = await a.json();
120
+ const e = await i.json();
117
121
  return e.code || e.error || null;
118
122
  } catch {
119
123
  return null;
120
124
  }
121
125
  }
122
- function N() {
126
+ function F() {
123
127
  return typeof chrome < "u" && !!chrome?.storage?.local && !!chrome?.runtime?.id;
124
128
  }
125
- const D = {
126
- getItem(a) {
129
+ const M = {
130
+ getItem(i) {
127
131
  return new Promise((t) => {
128
- chrome.storage.local.get([a], (e) => {
129
- const s = e[a];
132
+ chrome.storage.local.get([i], (e) => {
133
+ const s = e[i];
130
134
  t(typeof s == "string" ? s : null);
131
135
  });
132
136
  });
133
137
  },
134
- setItem(a, t) {
138
+ setItem(i, t) {
135
139
  return new Promise((e) => {
136
- chrome.storage.local.set({ [a]: t }, () => e());
140
+ chrome.storage.local.set({ [i]: t }, () => e());
137
141
  });
138
142
  },
139
- removeItem(a) {
143
+ removeItem(i) {
140
144
  return new Promise((t) => {
141
- chrome.storage.local.remove([a], () => t());
145
+ chrome.storage.local.remove([i], () => t());
142
146
  });
143
147
  },
144
- watch(a, t) {
148
+ watch(i, t) {
145
149
  const e = chrome?.storage?.onChanged;
146
150
  if (!e) return () => {
147
151
  };
148
- const s = (i, n) => {
152
+ const s = (a, n) => {
149
153
  if (n !== "local") return;
150
- const o = i[a];
154
+ const o = a[i];
151
155
  o && t(typeof o.newValue == "string" ? o.newValue : null);
152
156
  };
153
157
  return e.addListener(s), () => e.removeListener(s);
154
158
  }
155
159
  }, x = {
156
- async getItem(a) {
160
+ async getItem(i) {
157
161
  try {
158
- return window.localStorage.getItem(a);
162
+ return window.localStorage.getItem(i);
159
163
  } catch {
160
164
  return null;
161
165
  }
162
166
  },
163
- async setItem(a, t) {
167
+ async setItem(i, t) {
164
168
  try {
165
- window.localStorage.setItem(a, t);
169
+ window.localStorage.setItem(i, t);
166
170
  } catch {
167
171
  }
168
172
  },
169
- async removeItem(a) {
173
+ async removeItem(i) {
170
174
  try {
171
- window.localStorage.removeItem(a);
175
+ window.localStorage.removeItem(i);
172
176
  } catch {
173
177
  }
174
178
  },
175
- watch(a, t) {
179
+ watch(i, t) {
176
180
  if (typeof window > "u") return () => {
177
181
  };
178
182
  const e = (s) => {
179
- s.storageArea === window.localStorage && s.key === a && t(s.newValue);
183
+ s.storageArea === window.localStorage && s.key === i && t(s.newValue);
180
184
  };
181
185
  return window.addEventListener("storage", e), () => window.removeEventListener("storage", e);
182
186
  }
183
- }, m = /* @__PURE__ */ new Map(), M = {
184
- async getItem(a) {
185
- return m.get(a) ?? null;
187
+ }, I = /* @__PURE__ */ new Map(), J = {
188
+ async getItem(i) {
189
+ return I.get(i) ?? null;
186
190
  },
187
- async setItem(a, t) {
188
- m.set(a, t);
191
+ async setItem(i, t) {
192
+ I.set(i, t);
189
193
  },
190
- async removeItem(a) {
191
- m.delete(a);
194
+ async removeItem(i) {
195
+ I.delete(i);
192
196
  }
193
197
  };
194
- function O(a) {
195
- return a || (N() ? D : typeof window < "u" && "localStorage" in window ? x : M);
198
+ function L(i) {
199
+ return i || (F() ? M : typeof window < "u" && "localStorage" in window ? x : J);
196
200
  }
197
- const y = {
201
+ const l = {
198
202
  visitorId: "pw-visitor-id",
199
- lastLoginMethod: (a) => `pw-${a}-last-login-method`,
200
- lastLoginEmail: (a) => `pw-${a}-last-login-email`,
203
+ lastLoginMethod: (i) => `pw-${i}-last-login-method`,
204
+ lastLoginEmail: (i) => `pw-${i}-last-login-email`,
201
205
  // last-known PaywallUser. Используется как offline-fallback на старте, пока
202
206
  // первый getUser() не вернётся. Ключ зависит от paywallId+identity hash —
203
207
  // переключение identity не должно отдавать чужой user.
204
- userState: (a, t) => `pw-${a}-${t}-user-v1`,
208
+ userState: (i, t) => `pw-${i}-${t}-user-v1`,
205
209
  // Persisted auth bundle (access_token, refresh_token, expires_at, user) для
206
210
  // одного пейвола. Ключ привязан к paywallId — мульти-пейвольное приложение
207
211
  // не пересекает сессии. Bump '-v1' на breaking shape change.
208
- authSession: (a) => `pw-${a}-auth-v1`,
212
+ authSession: (i) => `pw-${i}-auth-v1`,
209
213
  // Refresh-token последнего анонимного юзера. Хранится отдельно от authSession,
210
214
  // потому что должен пережить signOut: после signOut() юзер может опять
211
215
  // зайти как тот же аноним — без капчи, через этот токен. signIn другим
212
216
  // методом (email/oauth) тоже его не трогает. Чистится только явным
213
217
  // signOut({forgetAnonymous: true}) или 401 от refresh-эндпоинта (значит
214
218
  // токен отозван, дальше держать бессмысленно).
215
- anonRefreshToken: (a) => `pw-${a}-anon-rt-v1`,
219
+ anonRefreshToken: (i) => `pw-${i}-anon-rt-v1`,
216
220
  // Persisted bootstrap (settings/prices/offers/layout/locales/version) для
217
221
  // stale-while-revalidate. Не зависит от identity — layout одинаков для всех
218
222
  // юзеров одного пейвола; user-state живёт отдельно под `userState(...)`.
219
223
  // Bump '-v1' на breaking shape change.
220
- bootstrap: (a) => `pw-${a}-bootstrap-v1`,
224
+ bootstrap: (i) => `pw-${i}-bootstrap-v1`,
221
225
  // Persisted balances (AI-провайдеры × tokenization_queries). Identity-bound,
222
226
  // т.к. balance считается per-Bearer-юзеру; при re-login ключ меняется и
223
227
  // чужие balances не видны. Меняются после оплаты (бэк) и API-вызовов
224
228
  // (оптимистично через `decrementBalanceLocal`).
225
- balances: (a, t) => `pw-${a}-${t}-balances-v1`
229
+ balances: (i, t) => `pw-${i}-${t}-balances-v1`
226
230
  };
227
231
  function C() {
228
- const a = typeof globalThis < "u" ? globalThis.crypto : void 0;
229
- if (a && typeof a.randomUUID == "function") return a.randomUUID();
232
+ const i = typeof globalThis < "u" ? globalThis.crypto : void 0;
233
+ if (i && typeof i.randomUUID == "function") return i.randomUUID();
230
234
  const t = new Uint8Array(16);
231
- if (a && typeof a.getRandomValues == "function")
232
- a.getRandomValues(t);
235
+ if (i && typeof i.getRandomValues == "function")
236
+ i.getRandomValues(t);
233
237
  else
234
238
  for (let s = 0; s < 16; s++) t[s] = Math.floor(Math.random() * 256);
235
239
  t[6] = t[6] & 15 | 64, t[8] = t[8] & 63 | 128;
236
240
  const e = Array.from(t, (s) => s.toString(16).padStart(2, "0")).join("");
237
241
  return `${e.slice(0, 8)}-${e.slice(8, 12)}-${e.slice(12, 16)}-${e.slice(16, 20)}-${e.slice(20)}`;
238
242
  }
239
- async function I(a) {
243
+ async function b(i) {
240
244
  try {
241
- const e = await a.getItem(y.visitorId);
245
+ const e = await i.getItem(l.visitorId);
242
246
  if (e && typeof e == "string" && e.length >= 16) return e;
243
247
  } catch {
244
248
  }
245
249
  const t = C();
246
250
  try {
247
- await a.setItem(y.visitorId, t);
251
+ await i.setItem(l.visitorId, t);
248
252
  } catch {
249
253
  }
250
254
  return t;
251
255
  }
252
- const J = 5e3, H = 30 * 6e4, v = 60 * 6e4, V = 5 * 6e4, _ = {
256
+ const H = 5e3, V = 30 * 6e4, _ = 60 * 6e4, j = 5 * 6e4, v = {
253
257
  has_active_subscription: !1,
254
258
  purchases: [],
255
- trial: null
259
+ trial: null,
260
+ had_previous_trial: !1
256
261
  };
257
- function B(a) {
258
- return a && (a.email || a.userId || a.anonymousId) || "guest";
262
+ function B(i) {
263
+ return i && (i.email || i.userId || i.anonymousId) || "guest";
259
264
  }
260
- function j(a, t) {
261
- return a === t ? !0 : !a || !t ? !1 : JSON.stringify(a) === JSON.stringify(t);
265
+ function X(i, t) {
266
+ return i === t ? !0 : !i || !t ? !1 : JSON.stringify(i) === JSON.stringify(t);
262
267
  }
263
- const X = 5e3, A = 5 * 6e4, z = 3e4;
264
- function G(a, t) {
265
- if (a === t) return !0;
266
- if (!a || !t || a.length !== t.length) return !1;
267
- for (let e = 0; e < a.length; e++)
268
- if (a[e].type !== t[e].type || a[e].count !== t[e].count) return !1;
268
+ const G = 5e3, A = 5 * 6e4, z = 3e4;
269
+ function Q(i, t) {
270
+ if (i === t) return !0;
271
+ if (!i || !t || i.length !== t.length) return !1;
272
+ for (let e = 0; e < i.length; e++)
273
+ if (i[e].type !== t[e].type || i[e].count !== t[e].count) return !1;
269
274
  return !0;
270
275
  }
271
- const Q = "https://appbox.space";
272
- class ut {
276
+ class yt {
273
277
  constructor(t) {
274
- if (this.cachedBootstrap = null, this.cachedBootstrapAt = 0, this.inflightBootstrap = null, this.bootstrapListeners = /* @__PURE__ */ new Set(), this.bootstrapStorageUnwatch = null, this.authUnsubscribe = null, this.cachedUser = null, this.cachedUserAt = 0, this.inflightUser = null, this.userListeners = /* @__PURE__ */ new Set(), this.visitorIdPromise = null, this.visitorId = null, this.inflightCheckouts = /* @__PURE__ */ new Map(), this.cachedBalances = null, this.cachedBalancesAt = 0, this.balancesStorageUnwatch = null, this.inflightBalances = null, this.balanceListeners = /* @__PURE__ */ new Set(), !t.paywallId)
278
+ if (this.cachedBootstrap = null, this.cachedBootstrapAt = 0, this.inflightBootstrap = null, this.bootstrapListeners = /* @__PURE__ */ new Set(), this.bootstrapStorageUnwatch = null, this.authUnsubscribe = null, this.cachedUser = null, this.cachedUserAt = 0, this.inflightUser = null, this.userListeners = /* @__PURE__ */ new Set(), this.visitorIdPromise = null, this.visitorId = null, this.inflightCheckouts = /* @__PURE__ */ new Map(), this.cachedBalances = null, this.cachedBalancesAt = 0, this.balancesStorageUnwatch = null, this.inflightBalances = null, this.balanceListeners = /* @__PURE__ */ new Set(), this.previewVersionCounter = 0, !t.paywallId)
275
279
  throw new r("invalid_config", "paywallId is required");
276
- this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin ?? Q, this.capabilities = t.capabilities, this.auth = t.auth;
280
+ if (!t.apiOrigin)
281
+ throw new r(
282
+ "invalid_config",
283
+ 'apiOrigin is required. Pass the paywall custom_domain configured in the platform (e.g. "https://pay.your-domain.com"). The legacy "appbox.space" fallback is not used in SDK 3.0.'
284
+ );
285
+ this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin, this.capabilities = t.capabilities, this.auth = t.auth, this.previewMode = t.preview === !0;
277
286
  const e = t.auth?.getCachedUser();
278
287
  this.identity = t.identity ?? (e ? T(e) : void 0), this.apiKey = t.apiKey, this.fetchImpl = t.fetch, t.apiKey && typeof window < "u" && typeof window.document < "u" && console.error(
279
288
  "[paywall] SECURITY: BillingClient.apiKey detected in browser context. This is a server-SDK key and exposes your account. Remove apiKey or move BillingClient to a trusted backend."
280
- ), this.storage = O(t.storage), this.api = new k({
289
+ ), this.storage = L(t.storage), this.api = new E({
281
290
  apiOrigin: this.apiOrigin,
282
291
  paywallId: t.paywallId,
283
292
  capabilities: t.capabilities,
@@ -286,10 +295,10 @@ class ut {
286
295
  // делает lazy refresh, дедупит, на 401 возвращает null — тогда
287
296
  // Authorization-хедер просто не выставится.
288
297
  getAuthToken: t.auth ? () => t.auth.getAccessToken() : void 0
289
- }), t.auth && (this.authUnsubscribe = t.auth.onAuthChange((s) => {
290
- const i = s ? T(s.user) : void 0;
291
- W(this.identity, i) || this.setIdentity(i);
292
- })), this.hydrateUserFromStorage(), this.hydrateBootstrapFromStorage(), this.subscribeBootstrapStorage(), this.hydrateBalancesFromStorage(), this.subscribeBalancesStorage(), this.visitorIdPromise = I(this.storage).then((s) => (this.visitorId = s, s));
298
+ }), t.auth && (this.authUnsubscribe = t.auth.onAuthChange((s, a) => {
299
+ const n = a ? T(a.user) : void 0;
300
+ W(this.identity, n) || this.setIdentity(n);
301
+ })), this.hydrateUserFromStorage(), this.hydrateBootstrapFromStorage(), this.subscribeBootstrapStorage(), this.hydrateBalancesFromStorage(), this.subscribeBalancesStorage(), this.visitorIdPromise = b(this.storage).then((s) => (this.visitorId = s, s));
293
302
  }
294
303
  /**
295
304
  * Stable visitor_id (UUID v4). Первый вызов awaitит первичный резолв из
@@ -297,15 +306,15 @@ class ut {
297
306
  * EventTracker'ом для атрибуции аналитики.
298
307
  */
299
308
  async getVisitorId() {
300
- return this.visitorId ? this.visitorId : (this.visitorIdPromise || (this.visitorIdPromise = I(this.storage).then((t) => (this.visitorId = t, t))), this.visitorIdPromise);
309
+ return this.visitorId ? this.visitorId : (this.visitorIdPromise || (this.visitorIdPromise = b(this.storage).then((t) => (this.visitorId = t, t))), this.visitorIdPromise);
301
310
  }
302
311
  /** Sync-доступ к visitor_id. null если ещё не зарезолвили (первые ms жизни). */
303
312
  getCachedVisitorId() {
304
313
  return this.visitorId;
305
314
  }
306
315
  setIdentity(t) {
307
- this.identity = t, this.cachedUser = null, this.cachedUserAt = 0, this.inflightUser = null, this.cachedBalances = null, this.cachedBalancesAt = 0, this.inflightBalances = null, this.balancesStorageUnwatch && (this.balancesStorageUnwatch(), this.balancesStorageUnwatch = null), this.hydrateBalancesFromStorage(), this.subscribeBalancesStorage(), this.hydrateUserFromStorage(), t && this.getUser({ force: !0 }).catch(() => {
308
- });
316
+ this.identity = t, this.cachedUser = null, this.cachedUserAt = 0, this.inflightUser = null, this.cachedBalances = null, this.cachedBalancesAt = 0, this.inflightBalances = null, this.balancesStorageUnwatch && (this.balancesStorageUnwatch(), this.balancesStorageUnwatch = null), this.hydrateBalancesFromStorage(), this.subscribeBalancesStorage(), this.hydrateUserFromStorage(), t ? this.getUser({ force: !0 }).catch(() => {
317
+ }) : (this.applyUser(v), this.applyBalances([]));
309
318
  }
310
319
  /**
311
320
  * Отписаться от auth-event'ов и сбросить listener'ы. Вызывать когда
@@ -325,9 +334,17 @@ class ut {
325
334
  return this.storage;
326
335
  }
327
336
  async bootstrap(t = !1) {
328
- const e = typeof t == "boolean" ? { force: t } : t, s = Date.now(), i = this.cachedBootstrap && this.cachedBootstrapAt > 0 && s - this.cachedBootstrapAt < v;
329
- return !e.force && i ? (s - this.cachedBootstrapAt > V && this.revalidateBootstrap(e.signal).catch(() => {
330
- }), this.cachedBootstrap) : this.inflightBootstrap ? this.inflightBootstrap : (this.inflightBootstrap = this.fetchBootstrap({
337
+ const e = typeof t == "boolean" ? { force: t } : t;
338
+ if (this.previewMode) {
339
+ if (this.cachedBootstrap) return this.cachedBootstrap;
340
+ throw new r(
341
+ "invalid_config",
342
+ "BillingClient in preview mode but cachedBootstrap is not seeded. Call setBootstrap(bootstrap) before open()."
343
+ );
344
+ }
345
+ const s = Date.now(), a = this.cachedBootstrap && this.cachedBootstrapAt > 0 && s - this.cachedBootstrapAt < _;
346
+ return !e.force && a ? (s - this.cachedBootstrapAt > j && this.revalidateBootstrap(e.signal).catch(() => {
347
+ }), { ...this.cachedBootstrap, user: this.cachedUser ?? void 0 }) : this.inflightBootstrap ? this.inflightBootstrap : (this.inflightBootstrap = this.fetchBootstrap({
331
348
  ifVersion: e.force ? void 0 : this.cachedBootstrap?.version,
332
349
  signal: e.signal
333
350
  }).finally(() => {
@@ -345,6 +362,44 @@ class ut {
345
362
  this.bootstrapListeners.delete(t);
346
363
  };
347
364
  }
365
+ /**
366
+ * Заменить cachedBootstrap частичными или полными данными и эмитнуть всем
367
+ * подписчикам. Используется host'ом в preview-mode (редактор админки) для
368
+ * live-обновления открытой модалки без сетевого revalidate'а.
369
+ *
370
+ * Поведение:
371
+ * - Без `cachedBootstrap` ожидаются как минимум `settings` + `prices` —
372
+ * иначе PaywallRoot не сможет отрендерить тарифы и упадёт.
373
+ * - С существующим кешем партиал мёрджится поверх: `settings` глубокий мёрдж
374
+ * на 1 уровень (поля настроек), массивы `prices`/`offers` перезаписываются.
375
+ * - Каждый вызов бампит `version` ("preview:<n>"), чтобы applyBootstrap'овая
376
+ * проверка `versionChanged` всегда срабатывала и listener'ы дёргались.
377
+ * - Persist в storage НЕ делаем — preview не должен утекать в другие вкладки.
378
+ *
379
+ * В non-preview режиме метод доступен, но это редкий путь (например, для
380
+ * тестов host'а) — production-код должен полагаться на bootstrap() + revalidate.
381
+ */
382
+ setBootstrap(t) {
383
+ const e = this.cachedBootstrap ?? {
384
+ settings: { id: this.paywallId, name: "" },
385
+ prices: [],
386
+ offers: []
387
+ }, s = {
388
+ ...e,
389
+ ...t,
390
+ settings: t.settings !== void 0 ? { ...e.settings, ...t.settings } : e.settings,
391
+ prices: t.prices !== void 0 ? t.prices : e.prices,
392
+ offers: t.offers !== void 0 ? t.offers : e.offers,
393
+ version: `preview:${++this.previewVersionCounter}`
394
+ };
395
+ s.layout || (s.layout = U(s.settings, s.prices)), w(s), this.cachedBootstrap = s, this.cachedBootstrapAt = Date.now();
396
+ for (const a of this.bootstrapListeners)
397
+ try {
398
+ a(s);
399
+ } catch (n) {
400
+ console.warn("[paywall] onBootstrapChange listener threw", n);
401
+ }
402
+ }
348
403
  // Network primitive — единая точка для force-запроса, revalidate'а и
349
404
  // первого холодного bootstrap'а. `ifVersion` шлёт server-side short-circuit:
350
405
  // если совпала — бэк отвечает `{unchanged: true, version, user}` и мы лишь
@@ -352,14 +407,14 @@ class ut {
352
407
  async fetchBootstrap(t) {
353
408
  const e = {};
354
409
  this.identity?.email && (e["X-User-Email"] = this.identity.email);
355
- const s = t.ifVersion ? `/api/v1/paywall/${this.paywallId}/bootstrap?if_version=${encodeURIComponent(t.ifVersion)}` : `/api/v1/paywall/${this.paywallId}/bootstrap`, i = await this.api.request(s, {
410
+ const s = t.ifVersion ? `/api/v1/paywall/${this.paywallId}/bootstrap?if_version=${encodeURIComponent(t.ifVersion)}` : `/api/v1/paywall/${this.paywallId}/bootstrap`, a = await this.api.request(s, {
356
411
  ...Object.keys(e).length ? { headers: e } : {},
357
412
  signal: t.signal
358
413
  });
359
- if ("unchanged" in i && i.unchanged)
360
- return this.cachedBootstrap ? (this.cachedBootstrapAt = Date.now(), i.user && this.applyUser(i.user), this.cachedBootstrap) : this.fetchBootstrap({ signal: t.signal });
361
- const n = i;
362
- return n.layout || (n.layout = Y(n.settings, n.prices)), b(n), this.applyBootstrap(n, { persist: !0 }), n.user && this.applyUser(n.user), n;
414
+ if ("unchanged" in a && a.unchanged)
415
+ return this.cachedBootstrap ? (this.cachedBootstrapAt = Date.now(), a.user && this.applyUser(a.user), this.cachedBootstrap) : this.fetchBootstrap({ signal: t.signal });
416
+ const n = a;
417
+ return Y(n.settings.custom_domain, this.apiOrigin), n.layout || (n.layout = U(n.settings, n.prices)), w(n), this.applyBootstrap(n, { persist: !0 }), n.user && this.applyUser(n.user), n;
363
418
  }
364
419
  // Фоновый revalidate из stale-while-revalidate ветки. Дедуплицируется через
365
420
  // `inflightBootstrap`, чтобы параллельные revalidate'ы не пересекались.
@@ -379,9 +434,9 @@ class ut {
379
434
  applyBootstrap(t, { persist: e }) {
380
435
  const s = !this.cachedBootstrap || this.cachedBootstrap.version !== t.version;
381
436
  if (this.cachedBootstrap = t, this.cachedBootstrapAt = Date.now(), e && this.persistBootstrap(t), s)
382
- for (const i of this.bootstrapListeners)
437
+ for (const a of this.bootstrapListeners)
383
438
  try {
384
- i(t);
439
+ a(t);
385
440
  } catch (n) {
386
441
  console.warn("[paywall] onBootstrapChange listener threw", n);
387
442
  }
@@ -389,16 +444,16 @@ class ut {
389
444
  async hydrateBootstrapFromStorage() {
390
445
  if (!this.cachedBootstrap)
391
446
  try {
392
- const t = await this.storage.getItem(y.bootstrap(this.paywallId));
447
+ const t = await this.storage.getItem(l.bootstrap(this.paywallId));
393
448
  if (!t) return;
394
449
  const e = JSON.parse(t);
395
- if (!e?.bootstrap || Date.now() - e.at > v || this.cachedBootstrap) return;
396
- b(e.bootstrap), this.cachedBootstrap = e.bootstrap, this.cachedBootstrapAt = e.at;
450
+ if (!e?.bootstrap || Date.now() - e.at > _ || this.cachedBootstrap) return;
451
+ w(e.bootstrap), this.cachedBootstrap = e.bootstrap, this.cachedBootstrapAt = e.at;
397
452
  for (const s of this.bootstrapListeners)
398
453
  try {
399
454
  s(e.bootstrap);
400
- } catch (i) {
401
- console.warn("[paywall] onBootstrapChange listener threw", i);
455
+ } catch (a) {
456
+ console.warn("[paywall] onBootstrapChange listener threw", a);
402
457
  }
403
458
  } catch {
404
459
  }
@@ -408,7 +463,7 @@ class ut {
408
463
  try {
409
464
  const { user: e, ...s } = t;
410
465
  await this.storage.setItem(
411
- y.bootstrap(this.paywallId),
466
+ l.bootstrap(this.paywallId),
412
467
  JSON.stringify({ at: Date.now(), bootstrap: s })
413
468
  );
414
469
  } catch {
@@ -419,7 +474,7 @@ class ut {
419
474
  // no-op, всё работает как раньше через сеть.
420
475
  subscribeBootstrapStorage() {
421
476
  typeof this.storage.watch == "function" && (this.bootstrapStorageUnwatch = this.storage.watch(
422
- y.bootstrap(this.paywallId),
477
+ l.bootstrap(this.paywallId),
423
478
  (t) => {
424
479
  if (t)
425
480
  try {
@@ -429,7 +484,7 @@ class ut {
429
484
  this.cachedBootstrapAt = e.at;
430
485
  return;
431
486
  }
432
- b(e.bootstrap), this.applyBootstrap(e.bootstrap, { persist: !1 });
487
+ w(e.bootstrap), this.applyBootstrap(e.bootstrap, { persist: !1 });
433
488
  } catch {
434
489
  }
435
490
  }
@@ -457,6 +512,15 @@ class ut {
457
512
  getCachedPrices() {
458
513
  return this.cachedBootstrap?.prices ?? null;
459
514
  }
515
+ /** Sync-снимок офферов из последнего bootstrap'а. null = bootstrap ещё не
516
+ * загружали, пустой массив = бэк отдал пейвол без офферов. Бэк уже
517
+ * применил серверный таргетинг (target_countries / target_emails /
518
+ * targeting_mode из offer_settings) — наружу выезжает только то, что
519
+ * применимо к текущему юзеру. Клиентская сторона остаётся ответственной
520
+ * за price_id-matching и countdown (см. core/offer.ts → resolveOffer). */
521
+ getCachedOffers() {
522
+ return this.cachedBootstrap?.offers ?? null;
523
+ }
460
524
  /**
461
525
  * Снимок того, какой язык SDK сейчас считает «языком юзера». Полезно для
462
526
  * синхронизации i18n хоста с тем, что фактически показывает пейвол — чтобы
@@ -476,7 +540,7 @@ class ut {
476
540
  * есть `navigator.language`.
477
541
  */
478
542
  getUserLanguage() {
479
- const t = typeof navigator < "u" && navigator.language ? navigator.language : null, e = this.cachedBootstrap?.settings.locale_default ?? null, s = this.cachedBootstrap ? E(this.cachedBootstrap) : null;
543
+ const t = typeof navigator < "u" && navigator.language ? navigator.language : null, e = this.cachedBootstrap?.settings.locale_default ?? null, s = this.cachedBootstrap ? N(this.cachedBootstrap) : null;
480
544
  return { tag: s ?? t ?? e, applied: s, browserLanguage: t, countryLanguage: e };
481
545
  }
482
546
  /**
@@ -488,10 +552,10 @@ class ut {
488
552
  * - Без identity возвращает empty-state (сервер тоже так делает).
489
553
  */
490
554
  async getUser({ force: t = !1, signal: e } = {}) {
491
- return !t && this.cachedUser && Date.now() - this.cachedUserAt < J ? this.cachedUser : this.inflightUser ? this.inflightUser : (this.inflightUser = (async () => {
555
+ return !t && this.cachedUser && Date.now() - this.cachedUserAt < H ? this.cachedUser : this.inflightUser ? this.inflightUser : (this.inflightUser = (async () => {
492
556
  try {
493
557
  if (!this.identity?.email)
494
- return this.applyUser(_), _;
558
+ return this.applyUser(v), v;
495
559
  const s = await this.api.request(
496
560
  `/api/v1/paywall/${this.paywallId}/user-state`,
497
561
  { headers: { "X-User-Email": this.identity.email }, signal: e }
@@ -523,16 +587,16 @@ class ut {
523
587
  this.userListeners.add(t);
524
588
  const s = e.immediate ?? "microtask";
525
589
  if (this.cachedUser && s !== "none") {
526
- const i = this.cachedUser;
590
+ const a = this.cachedUser;
527
591
  if (s === "sync")
528
592
  try {
529
- t(i);
593
+ t(a);
530
594
  } catch (n) {
531
595
  console.warn("[paywall] onUserChange initial sync threw", n);
532
596
  }
533
597
  else
534
598
  queueMicrotask(() => {
535
- this.userListeners.has(t) && t(i);
599
+ this.userListeners.has(t) && t(a);
536
600
  });
537
601
  }
538
602
  return () => {
@@ -544,19 +608,19 @@ class ut {
544
608
  return this.cachedUser;
545
609
  }
546
610
  applyUser(t) {
547
- const e = !j(this.cachedUser, t);
611
+ const e = !X(this.cachedUser, t);
548
612
  if (this.cachedUser = t, this.cachedUserAt = Date.now(), e) {
549
613
  this.persistUser(t);
550
614
  for (const s of this.userListeners)
551
615
  try {
552
616
  s(t);
553
- } catch (i) {
554
- console.warn("[paywall] onUserChange listener threw", i);
617
+ } catch (a) {
618
+ console.warn("[paywall] onUserChange listener threw", a);
555
619
  }
556
620
  }
557
621
  }
558
622
  storageKey() {
559
- return y.userState(this.paywallId, B(this.identity));
623
+ return l.userState(this.paywallId, B(this.identity));
560
624
  }
561
625
  async hydrateUserFromStorage() {
562
626
  if (!this.cachedUser)
@@ -564,7 +628,7 @@ class ut {
564
628
  const t = await this.storage.getItem(this.storageKey());
565
629
  if (!t) return;
566
630
  const e = JSON.parse(t);
567
- if (!e?.user || Date.now() - e.at > H || this.cachedUser) return;
631
+ if (!e?.user || Date.now() - e.at > V || this.cachedUser) return;
568
632
  this.applyUser(e.user);
569
633
  } catch {
570
634
  }
@@ -592,8 +656,8 @@ class ut {
592
656
  * по `currentBalance` в QuotaExceededError или `balances.length`.
593
657
  */
594
658
  async getBalances({ force: t = !1, signal: e } = {}) {
595
- const s = Date.now(), i = this.cachedBalances ? s - this.cachedBalancesAt : 1 / 0;
596
- return !t && this.cachedBalances && (i < X || i < z) ? this.cachedBalances : !t && this.cachedBalances && i < A ? (this.fetchBalances({ signal: e }).catch(() => {
659
+ const s = Date.now(), a = this.cachedBalances ? s - this.cachedBalancesAt : 1 / 0;
660
+ return !t && this.cachedBalances && (a < G || a < z) ? this.cachedBalances : !t && this.cachedBalances && a < A ? (this.fetchBalances({ signal: e }).catch(() => {
597
661
  }), this.cachedBalances) : this.inflightBalances ? this.inflightBalances : this.fetchBalances({ signal: e });
598
662
  }
599
663
  // Network primitive — единая точка для force/stale-revalidate/cold-start.
@@ -624,16 +688,16 @@ class ut {
624
688
  this.balanceListeners.add(t);
625
689
  const s = e.immediate ?? "microtask";
626
690
  if (this.cachedBalances && s !== "none") {
627
- const i = this.cachedBalances;
691
+ const a = this.cachedBalances;
628
692
  if (s === "sync")
629
693
  try {
630
- t(i);
694
+ t(a);
631
695
  } catch (n) {
632
696
  console.warn("[paywall] onBalanceChange initial sync threw", n);
633
697
  }
634
698
  else
635
699
  queueMicrotask(() => {
636
- this.balanceListeners.has(t) && t(i);
700
+ this.balanceListeners.has(t) && t(a);
637
701
  });
638
702
  }
639
703
  return () => {
@@ -662,10 +726,10 @@ class ut {
662
726
  if (!this.cachedBalances) return;
663
727
  const e = this.cachedBalances.findIndex((n) => n.type === t);
664
728
  if (e < 0 || this.cachedBalances[e].count <= 0) return;
665
- const i = this.cachedBalances.map(
729
+ const a = this.cachedBalances.map(
666
730
  (n, o) => o === e ? { ...n, count: n.count - 1 } : n
667
731
  );
668
- this.applyBalances(i);
732
+ this.applyBalances(a);
669
733
  }
670
734
  /** Принудительный re-fetch — типичный вызов после QuotaExceededError, чтобы
671
735
  * UI получил актуальный balance=0 и нарисовал upgrade-prompt. */
@@ -684,7 +748,7 @@ class ut {
684
748
  */
685
749
  createApiGatewayClient(t = {}) {
686
750
  const e = t.onChargeSuccess, s = t.onQuotaExceeded;
687
- return new K({
751
+ return new D({
688
752
  paywallId: this.paywallId,
689
753
  apiOrigin: this.apiOrigin,
690
754
  auth: this.auth,
@@ -692,26 +756,26 @@ class ut {
692
756
  capabilities: this.capabilities,
693
757
  fetch: this.fetchImpl,
694
758
  ...t,
695
- onChargeSuccess: (i) => {
696
- this.decrementBalanceLocal(i), e?.(i);
759
+ onChargeSuccess: (a) => {
760
+ this.decrementBalanceLocal(a), e?.(a);
697
761
  },
698
- onQuotaExceeded: (i) => {
699
- this.refreshBalances(), s?.(i);
762
+ onQuotaExceeded: (a) => {
763
+ this.refreshBalances(), s?.(a);
700
764
  }
701
765
  });
702
766
  }
703
767
  applyBalances(t, { persist: e = !0 } = {}) {
704
- const s = !G(this.cachedBalances, t);
768
+ const s = !Q(this.cachedBalances, t);
705
769
  if (this.cachedBalances = t, this.cachedBalancesAt = Date.now(), e && this.persistBalances(t), s)
706
- for (const i of this.balanceListeners)
770
+ for (const a of this.balanceListeners)
707
771
  try {
708
- i(t);
772
+ a(t);
709
773
  } catch (n) {
710
774
  console.warn("[paywall] onBalanceChange listener threw", n);
711
775
  }
712
776
  }
713
777
  balancesStorageKey() {
714
- return y.balances(this.paywallId, B(this.identity));
778
+ return l.balances(this.paywallId, B(this.identity));
715
779
  }
716
780
  async hydrateBalancesFromStorage() {
717
781
  if (!this.cachedBalances)
@@ -724,8 +788,8 @@ class ut {
724
788
  for (const s of this.balanceListeners)
725
789
  try {
726
790
  s(e.balances);
727
- } catch (i) {
728
- console.warn("[paywall] onBalanceChange listener threw", i);
791
+ } catch (a) {
792
+ console.warn("[paywall] onBalanceChange listener threw", a);
729
793
  }
730
794
  } catch {
731
795
  }
@@ -768,7 +832,7 @@ class ut {
768
832
  "Idempotency-Key": t.idempotencyKey ?? C()
769
833
  };
770
834
  this.apiKey && (n["X-Api-Key"] = this.apiKey);
771
- const o = this.cachedBootstrap?.settings, u = t.successUrl ?? o?.success_redirect_url ?? void 0, l = t.shopUrl ?? o?.checkout_shop_url ?? void 0, d = this.api.request(`/api/v1/paywall/${this.paywallId}/start-checkout`, {
835
+ const o = this.cachedBootstrap?.settings, u = t.successUrl ?? o?.success_redirect_url ?? void 0, d = t.shopUrl ?? o?.checkout_shop_url ?? void 0, f = this.api.request(`/api/v1/paywall/${this.paywallId}/start-checkout`, {
772
836
  method: "POST",
773
837
  headers: n,
774
838
  signal: t.signal,
@@ -777,7 +841,7 @@ class ut {
777
841
  priceId: Number(t.priceId),
778
842
  successUrl: u,
779
843
  errorUrl: t.errorUrl,
780
- shopUrl: l,
844
+ shopUrl: d,
781
845
  productName: o?.checkout_product_name ?? void 0,
782
846
  trial_days: t.trialDays,
783
847
  ignoreActivePurchase: t.ignoreActivePurchase ? !0 : void 0,
@@ -790,10 +854,10 @@ class ut {
790
854
  { status: 409, cause: h.cause }
791
855
  ) : h;
792
856
  });
793
- return this.inflightCheckouts.set(e, d), d.finally(() => {
794
- this.inflightCheckouts.get(e) === d && this.inflightCheckouts.delete(e);
857
+ return this.inflightCheckouts.set(e, f), f.finally(() => {
858
+ this.inflightCheckouts.get(e) === f && this.inflightCheckouts.delete(e);
795
859
  }).catch(() => {
796
- }), d;
860
+ }), f;
797
861
  }
798
862
  /**
799
863
  * URL Stripe/Paddle/Chargebee customer portal — место, где залогиненный
@@ -840,43 +904,65 @@ class ut {
840
904
  * `/api/v1/paywall/[id]/user` без unstable_cache, потому что list для UI
841
905
  * должен быть свежим после cancel-а.
842
906
  *
843
- * Auth: Bearer обязателен (через AuthClient). Без Bearer — 401 от бэка,
844
- * пробрасываем как PaywallError('http_401'). Гость пустой список.
907
+ * Auth (два пути):
908
+ * - Bearer (через AuthClient) — user.id резолвится из сессии, identity
909
+ * в query игнорируется.
910
+ * - `apiKey` + `identity.email`/`identity.userId` — server-SDK путь для
911
+ * интеграций со своей авторизацией. Бэк проверяет, что identity линкована
912
+ * к этому пейволу (защита от cross-paywall lookup).
913
+ * Без auth и без apiKey+identity — `identity_required`.
845
914
  */
846
915
  async listPurchases(t = {}) {
847
- if (!this.auth)
916
+ const e = !!(this.identity?.email || this.identity?.userId);
917
+ if (!this.auth && !(this.apiKey && e))
848
918
  throw new r(
849
- "auth_required",
850
- "listPurchases requires AuthClient (Bearer auth)"
919
+ "identity_required",
920
+ "listPurchases requires AuthClient (Bearer) or apiKey + identity.email/userId"
851
921
  );
852
- return (await this.api.request(`/api/v1/paywall/${this.paywallId}/user`, {
922
+ const s = {};
923
+ this.apiKey && (s["X-Api-Key"] = this.apiKey);
924
+ const a = new URLSearchParams();
925
+ this.apiKey && this.identity?.email && a.set("email", this.identity.email), this.apiKey && this.identity?.userId && a.set("user_id", this.identity.userId);
926
+ const n = a.toString(), o = n ? `/api/v1/paywall/${this.paywallId}/user?${n}` : `/api/v1/paywall/${this.paywallId}/user`;
927
+ return (await this.api.request(o, {
853
928
  method: "GET",
929
+ headers: s,
854
930
  signal: t.signal
855
931
  })).purchases ?? [];
856
932
  }
857
933
  /**
858
- * Отменить подписку. Бэк проверит что subscription принадлежит auth-юзеру
859
- * и сделает cancel у acquiring'а (Stripe/Paddle/Chargebee). По умолчанию
860
- * cancel в конце текущего периода — юзер сохраняет access до renewal date'ы.
934
+ * Отменить подписку. Бэк проверит, что subscription принадлежит юзеру
935
+ * (Bearer-путь из сессии; apiKey-путь — из identity), и сделает cancel у
936
+ * acquiring'а (Stripe/Paddle/Chargebee/Overpay). По умолчанию cancel в
937
+ * конце текущего периода — юзер сохраняет access до renewal date'ы.
861
938
  *
862
- * `reason` обязательна (валидация на бэке). Удобно собрать через select
863
- * причин в host-UI, как в legacy customer portal'е.
939
+ * `reason` обязательна (валидация на бэке).
864
940
  *
865
- * Auth: Bearer обязателен.
941
+ * Auth (два пути):
942
+ * - Bearer (через AuthClient) — стандартный путь для UI customer-portal'a.
943
+ * - `apiKey` + `identity.email`/`identity.userId` — для self-service UI на
944
+ * бэке клиента со своей авторизацией. Бэк дополнительно фильтрует
945
+ * subscription по paywall_id, чтобы owner пейвола A не отменил подписку
946
+ * пейвола B.
866
947
  */
867
948
  async cancelSubscription(t) {
868
- if (!this.auth)
949
+ const e = !!(this.identity?.email || this.identity?.userId);
950
+ if (!this.auth && !(this.apiKey && e))
869
951
  throw new r(
870
- "auth_required",
871
- "cancelSubscription requires AuthClient (Bearer auth)"
952
+ "identity_required",
953
+ "cancelSubscription requires AuthClient (Bearer) or apiKey + identity.email/userId"
872
954
  );
873
- return this.api.request("/api/paywall/cancel-subscription", {
955
+ const s = {};
956
+ this.apiKey && (s["X-Api-Key"] = this.apiKey);
957
+ const a = {
958
+ subscriptionId: t.subscriptionId,
959
+ paywallId: this.paywallId,
960
+ cancellationReason: t.reason
961
+ };
962
+ return this.apiKey && this.identity?.email && (a.email = this.identity.email), this.apiKey && this.identity?.userId && (a.userId = this.identity.userId), this.api.request("/api/paywall/cancel-subscription", {
874
963
  method: "POST",
875
- body: JSON.stringify({
876
- subscriptionId: t.subscriptionId,
877
- paywallId: this.paywallId,
878
- cancellationReason: t.reason
879
- }),
964
+ headers: s,
965
+ body: JSON.stringify(a),
880
966
  signal: t.signal
881
967
  });
882
968
  }
@@ -909,80 +995,108 @@ class ut {
909
995
  });
910
996
  }
911
997
  }
912
- function T(a) {
913
- return { email: a.email, userId: a.id };
998
+ function T(i) {
999
+ return { email: i.email, userId: i.id };
914
1000
  }
915
- function W(a, t) {
916
- return a === t ? !0 : !a || !t ? !1 : a.email === t.email && a.userId === t.userId && a.anonymousId === t.anonymousId;
1001
+ function W(i, t) {
1002
+ return i === t ? !0 : !i || !t ? !1 : i.email === t.email && i.userId === t.userId && i.anonymousId === t.anonymousId;
917
1003
  }
918
- function Y(a, t) {
1004
+ function O(i) {
1005
+ if (!i) return null;
1006
+ const t = i.trim();
1007
+ if (!t) return null;
1008
+ try {
1009
+ return new URL(t.includes("://") ? t : `https://${t}`).origin;
1010
+ } catch {
1011
+ return null;
1012
+ }
1013
+ }
1014
+ function Y(i, t) {
1015
+ const e = O(i);
1016
+ if (!(!e || O(t) === e))
1017
+ throw new r(
1018
+ "invalid_config",
1019
+ `apiOrigin mismatch: SDK initialized with "${t}" but paywall is configured with custom_domain "${i}". Use the custom_domain from the platform paywall settings.`
1020
+ );
1021
+ }
1022
+ function U(i, t) {
919
1023
  return {
920
1024
  type: "modal",
921
1025
  blocks: [
922
- { type: "heading", text: a.name || "Upgrade", level: 1 },
1026
+ // offer_banner НЕ в default layout PaywallRoot рендерит его как
1027
+ // top-tab над dialog'ом (rounded-top, negative margin), за пределами
1028
+ // scrollable area. Блок остаётся в registry для opt-in inline-вариантa.
1029
+ { type: "heading", text: i.name || "Upgrade", level: 1 },
923
1030
  { type: "price_grid", priceIds: t.map((e) => e.id) },
924
- { type: "cta_button", label: "Continue", action: "checkout" }
1031
+ { type: "cta_button", action: "checkout" },
1032
+ { type: "guarantee_badge" },
1033
+ { type: "current_session" }
925
1034
  ]
926
1035
  };
927
1036
  }
928
- function E(a) {
929
- const t = a.locales;
1037
+ function N(i) {
1038
+ const t = i.locales;
930
1039
  if (!t) return null;
931
1040
  const e = [];
932
1041
  if (typeof navigator < "u") {
933
1042
  navigator.language && e.push(navigator.language);
934
- const i = navigator.language?.split("-")[0];
935
- i && i !== navigator.language && e.push(i);
1043
+ const a = navigator.language?.split("-")[0];
1044
+ a && a !== navigator.language && e.push(a);
936
1045
  }
937
- const s = a.settings.locale_default;
1046
+ const s = i.settings.locale_default;
938
1047
  s && e.push(s);
939
- for (const i of e)
940
- if (i && Object.prototype.hasOwnProperty.call(t, i)) return i;
1048
+ for (const a of e)
1049
+ if (a && Object.prototype.hasOwnProperty.call(t, a)) return a;
941
1050
  return null;
942
1051
  }
943
- function b(a) {
944
- const t = E(a);
1052
+ function w(i) {
1053
+ const t = N(i);
945
1054
  if (!t) return;
946
- const e = a.locales?.[t];
947
- e && (e.layout && (a.layout = e.layout), e.prices && (a.prices = a.prices.map((s) => {
948
- const i = e.prices?.[s.id];
949
- if (!i) return s;
1055
+ const e = i.locales?.[t];
1056
+ e && (e.layout && (i.layout = e.layout), e.prices && (i.prices = i.prices.map((s) => {
1057
+ const a = e.prices?.[s.id];
1058
+ if (!a) return s;
950
1059
  const n = { ...s };
951
- return "label" in i && (n.label = i.label ?? null), "description" in i && (n.description = i.description ?? null), n;
1060
+ return "label" in a && (n.label = a.label ?? null), "description" in a && (n.description = a.description ?? null), n;
952
1061
  })));
953
1062
  }
954
- function L(a) {
955
- const t = new Uint8Array(a), e = typeof globalThis < "u" ? globalThis.crypto : void 0;
1063
+ function K(i) {
1064
+ const t = new Uint8Array(i), e = typeof globalThis < "u" ? globalThis.crypto : void 0;
956
1065
  if (e && typeof e.getRandomValues == "function")
957
1066
  e.getRandomValues(t);
958
1067
  else
959
- for (let s = 0; s < a; s++) t[s] = Math.floor(Math.random() * 256);
1068
+ for (let s = 0; s < i; s++) t[s] = Math.floor(Math.random() * 256);
960
1069
  return t;
961
1070
  }
962
- function S(a) {
1071
+ function S(i) {
963
1072
  let t = "";
964
- for (let e = 0; e < a.length; e++) t += String.fromCharCode(a[e]);
1073
+ for (let e = 0; e < i.length; e++) t += String.fromCharCode(i[e]);
965
1074
  return btoa(t).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
966
1075
  }
967
1076
  function Z() {
968
- return S(L(64));
1077
+ return S(K(64));
969
1078
  }
970
- async function tt(a) {
971
- const t = new TextEncoder().encode(a), e = globalThis.crypto;
1079
+ async function tt(i) {
1080
+ const t = new TextEncoder().encode(i), e = globalThis.crypto;
972
1081
  if (!e?.subtle?.digest)
973
1082
  throw new Error("crypto.subtle is required for PKCE");
974
1083
  const s = await e.subtle.digest("SHA-256", t);
975
1084
  return S(new Uint8Array(s));
976
1085
  }
977
1086
  function et() {
978
- return S(L(16));
1087
+ return S(K(16));
979
1088
  }
980
- const st = "https://appbox.space", it = 6e4, at = 600 * 1e3;
981
- class dt {
1089
+ const st = 6e4, it = 600 * 1e3;
1090
+ class pt {
982
1091
  constructor(t) {
983
1092
  if (this.session = null, this.inflightRefresh = null, this.inflightAnonSignin = null, this.listeners = /* @__PURE__ */ new Set(), this.storageUnwatch = null, this.destroyed = !1, this.oauthFlows = /* @__PURE__ */ new Map(), !t.paywallId)
984
1093
  throw new r("invalid_config", "paywallId is required");
985
- this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin ?? st, this.storage = O(t.storage), this.api = new k({
1094
+ if (!t.apiOrigin)
1095
+ throw new r(
1096
+ "invalid_config",
1097
+ "apiOrigin is required. Pass the paywall custom_domain configured in the platform."
1098
+ );
1099
+ this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin, this.storage = L(t.storage), this.api = new E({
986
1100
  apiOrigin: this.apiOrigin,
987
1101
  paywallId: t.paywallId,
988
1102
  fetch: t.fetch
@@ -1008,14 +1122,15 @@ class dt {
1008
1122
  async applyExternalSession(t) {
1009
1123
  if (!this.destroyed && (await this.hydrated, !this.destroyed)) {
1010
1124
  if (t == null) {
1011
- this.session && this.setSession(null, { skipPersist: !0 });
1125
+ this.session && this.setSession(null, { skipPersist: !0, event: "SIGNED_OUT" });
1012
1126
  return;
1013
1127
  }
1014
1128
  try {
1015
1129
  const e = JSON.parse(t);
1016
1130
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1017
1131
  return;
1018
- this.setSession(e, { skipPersist: !0 });
1132
+ const s = !this.session || this.session.user.id !== e.user.id ? "SIGNED_IN" : "TOKEN_REFRESHED";
1133
+ this.setSession(e, { skipPersist: !0, event: s });
1019
1134
  } catch {
1020
1135
  }
1021
1136
  }
@@ -1059,7 +1174,7 @@ class dt {
1059
1174
  await this.hydrated;
1060
1175
  const e = await this.readVisitorId(), s = {};
1061
1176
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1062
- const i = await this.api.request(
1177
+ const a = await this.api.request(
1063
1178
  `/api/v1/paywall/${this.paywallId}/auth/email/signin`,
1064
1179
  {
1065
1180
  method: "POST",
@@ -1071,8 +1186,8 @@ class dt {
1071
1186
  user_meta: t.userMeta
1072
1187
  })
1073
1188
  }
1074
- ), n = this.toSession(i, i.user);
1075
- return this.setSession(n), n;
1189
+ ), n = this.toSession(a, a.user);
1190
+ return this.setSession(n, { event: "SIGNED_IN" }), this.recordLastLogin("email", t.email), n;
1076
1191
  }
1077
1192
  /**
1078
1193
  * Signup. Если в Supabase включён email confirm — сервер возвращает
@@ -1084,7 +1199,7 @@ class dt {
1084
1199
  await this.hydrated;
1085
1200
  const e = await this.readVisitorId(), s = {};
1086
1201
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1087
- const i = await this.api.request(
1202
+ const a = await this.api.request(
1088
1203
  `/api/v1/paywall/${this.paywallId}/auth/email/signup`,
1089
1204
  {
1090
1205
  method: "POST",
@@ -1097,10 +1212,10 @@ class dt {
1097
1212
  })
1098
1213
  }
1099
1214
  );
1100
- if (i.status === "confirmation_required")
1101
- return { kind: "confirmation_required", user: i.user };
1102
- const n = this.toSession(i, i.user);
1103
- return this.setSession(n), { kind: "signed_in", session: n };
1215
+ if (a.status === "confirmation_required")
1216
+ return this.recordLastLogin("email", t.email), { kind: "confirmation_required", user: a.user };
1217
+ const n = this.toSession(a, a.user);
1218
+ return this.setSession(n, { event: "SIGNED_IN" }), this.recordLastLogin("email", t.email), { kind: "signed_in", session: n };
1104
1219
  }
1105
1220
  /**
1106
1221
  * Повторная отправка confirmation-email после signUp с включённым
@@ -1163,8 +1278,8 @@ class dt {
1163
1278
  user_meta: t.userMeta
1164
1279
  })
1165
1280
  }
1166
- ), i = this.toSession(s, s.user);
1167
- return this.setSession(i), i;
1281
+ ), a = this.toSession(s, s.user), n = t.type === "recovery" ? "PASSWORD_RECOVERY" : "SIGNED_IN";
1282
+ return this.setSession(a, { event: n }), a;
1168
1283
  }
1169
1284
  /**
1170
1285
  * Запрос recovery email. Бэк всегда ok, чтобы не палить enumeration.
@@ -1223,10 +1338,8 @@ class dt {
1223
1338
  * когда сервер начнёт возвращать challenge_required в риск-сценариях,
1224
1339
  * SDK сможет передать proof-of-something обратно без breaking change.
1225
1340
  *
1226
- * `forceCaptcha: true` пропускает шаги 1-2 и сразу делает /signin (создаёт
1227
- * нового anon-юзера). Используется в switch-account flow. Имя поля исторически
1228
- * остаётся `forceCaptcha`, хотя капчи там больше нет — менять имя ломает
1229
- * host-сигнатуру; смысл «принудительно новая anon-сессия» сохранён.
1341
+ * `forceNewAnon: true` пропускает шаги 1-2 и сразу делает /signin (создаёт
1342
+ * нового anon-юзера). Используется в switch-account flow.
1230
1343
  *
1231
1344
  * Параллельные вызовы дедуплицируются через `inflightAnonSignin` — два
1232
1345
  * click'а на «Войти как гость» не создадут двух anon-юзеров (два /signup =
@@ -1235,9 +1348,9 @@ class dt {
1235
1348
  async signInAnonymously(t = {}) {
1236
1349
  if (this.inflightAnonSignin) return this.inflightAnonSignin;
1237
1350
  this.inflightAnonSignin = (async () => {
1238
- if (await this.hydrated, !t.forceCaptcha && this.session?.user.is_anonymous === !0)
1351
+ if (await this.hydrated, !t.forceNewAnon && this.session?.user.is_anonymous === !0)
1239
1352
  return this.session;
1240
- if (!t.forceCaptcha) {
1353
+ if (!t.forceNewAnon) {
1241
1354
  const o = await this.resumeAnonymous();
1242
1355
  if (o) return o;
1243
1356
  }
@@ -1251,12 +1364,12 @@ class dt {
1251
1364
  user_meta: t.userMeta
1252
1365
  })
1253
1366
  }
1254
- ), i = {
1367
+ ), a = {
1255
1368
  ...s.user,
1256
1369
  email: s.user.email ?? null,
1257
1370
  is_anonymous: !0
1258
- }, n = this.toSession(s, i);
1259
- return this.setSession(n), await this.writeAnonRefreshToken(n.refresh_token), n;
1371
+ }, n = this.toSession(s, a);
1372
+ return this.setSession(n, { event: "SIGNED_IN" }), await this.writeAnonRefreshToken(n.refresh_token), n;
1260
1373
  })();
1261
1374
  try {
1262
1375
  return await this.inflightAnonSignin;
@@ -1277,8 +1390,8 @@ class dt {
1277
1390
  const e = await this.api.request(
1278
1391
  `/api/v1/paywall/${this.paywallId}/auth/refresh`,
1279
1392
  { method: "POST", body: JSON.stringify({ refresh_token: t }) }
1280
- ), s = this.session?.user.is_anonymous === !0 ? this.session.user : { id: "", email: null, is_anonymous: !0 }, i = this.toSession(e, s);
1281
- return this.setSession(i), await this.writeAnonRefreshToken(i.refresh_token), i;
1393
+ ), s = this.session?.user.is_anonymous === !0 ? this.session.user : { id: "", email: null, is_anonymous: !0 }, a = this.toSession(e, s);
1394
+ return this.setSession(a, { event: "SIGNED_IN" }), await this.writeAnonRefreshToken(a.refresh_token), a;
1282
1395
  } catch (e) {
1283
1396
  if (e instanceof r && e.status === 401)
1284
1397
  return await this.clearAnonRefreshToken(), null;
@@ -1315,7 +1428,7 @@ class dt {
1315
1428
  Authorization: `Bearer ${e}`
1316
1429
  };
1317
1430
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1318
- const i = await this.api.request(
1431
+ const a = await this.api.request(
1319
1432
  `/api/v1/paywall/${this.paywallId}/auth/anonymous/upgrade`,
1320
1433
  {
1321
1434
  method: "POST",
@@ -1327,8 +1440,8 @@ class dt {
1327
1440
  })
1328
1441
  }
1329
1442
  );
1330
- if (i.status === "confirmation_required")
1331
- return { kind: "confirmation_required", email: i.email };
1443
+ if (a.status === "confirmation_required")
1444
+ return { kind: "confirmation_required", email: a.email };
1332
1445
  const n = this.session;
1333
1446
  if (!n)
1334
1447
  throw new r(
@@ -1337,11 +1450,11 @@ class dt {
1337
1450
  );
1338
1451
  const o = {
1339
1452
  ...n.user,
1340
- id: i.user.id,
1341
- email: i.user.email,
1342
- is_anonymous: i.user.is_anonymous ?? !1
1453
+ id: a.user.id,
1454
+ email: a.user.email,
1455
+ is_anonymous: a.user.is_anonymous ?? !1
1343
1456
  }, u = { ...n, user: o };
1344
- return this.setSession(u), await this.clearAnonRefreshToken(), { kind: "updated", session: u };
1457
+ return this.setSession(u, { event: "USER_UPDATED" }), await this.clearAnonRefreshToken(), { kind: "updated", session: u };
1345
1458
  }
1346
1459
  /**
1347
1460
  * OAuth signin через popup с PKCE. Жизненный цикл:
@@ -1370,14 +1483,14 @@ class dt {
1370
1483
  provider: t.provider,
1371
1484
  scopes: t.scopes,
1372
1485
  userMeta: t.userMeta
1373
- }), i = this.openPopup(e, `pw-oauth-${s}`);
1374
- if (!i)
1486
+ }), a = this.openPopup(e, `pw-oauth-${s}`);
1487
+ if (!a)
1375
1488
  throw this.oauthFlows.delete(s), new r(
1376
1489
  "popup_blocked",
1377
1490
  "browser blocked auth popup — call from a user gesture"
1378
1491
  );
1379
1492
  t.onPopupOpened?.();
1380
- const n = await ot(i, s);
1493
+ const n = await rt(a, s);
1381
1494
  if (this.destroyed)
1382
1495
  throw this.oauthFlows.delete(s), new r("aborted", "AuthClient destroyed mid-flow");
1383
1496
  return this.completeOAuthFlow({ state: s, code: n });
@@ -1398,7 +1511,7 @@ class dt {
1398
1511
  */
1399
1512
  async startOAuthFlow(t) {
1400
1513
  await this.hydrated, this.gcOAuthFlows();
1401
- const e = Z(), s = await tt(e), i = et(), n = {}, o = await this.getAccessToken().catch(() => null);
1514
+ const e = Z(), s = await tt(e), a = et(), n = {}, o = await this.getAccessToken().catch(() => null);
1402
1515
  o && (n.Authorization = `Bearer ${o}`);
1403
1516
  const { authorize_url: u } = await this.api.request(
1404
1517
  `/api/v1/paywall/${this.paywallId}/auth/oauth/init`,
@@ -1413,11 +1526,12 @@ class dt {
1413
1526
  })
1414
1527
  }
1415
1528
  );
1416
- return this.oauthFlows.set(i, {
1529
+ return this.oauthFlows.set(a, {
1417
1530
  verifier: e,
1418
1531
  userMeta: t.userMeta,
1532
+ provider: t.provider,
1419
1533
  startedAt: Date.now()
1420
- }), { authorize_url: u, state: i };
1534
+ }), this.recordLastLoginMethod(t.provider), { authorize_url: u, state: a };
1421
1535
  }
1422
1536
  /**
1423
1537
  * Шаг 2 OAuth split-API: обменивает code (полученный из popup) на session,
@@ -1437,7 +1551,7 @@ class dt {
1437
1551
  "OAuth flow not found — start with startOAuthFlow first or check TTL"
1438
1552
  );
1439
1553
  this.oauthFlows.delete(t.state);
1440
- const s = await this.readVisitorId(), i = await this.api.request(
1554
+ const s = await this.readVisitorId(), a = await this.api.request(
1441
1555
  `/api/v1/paywall/${this.paywallId}/auth/oauth/exchange`,
1442
1556
  {
1443
1557
  method: "POST",
@@ -1451,11 +1565,11 @@ class dt {
1451
1565
  );
1452
1566
  if (this.destroyed)
1453
1567
  throw new r("aborted", "AuthClient destroyed mid-flow");
1454
- const n = this.toSession(i, i.user);
1455
- return this.setSession(n), n;
1568
+ const n = this.toSession(a, a.user);
1569
+ return this.setSession(n, { event: "SIGNED_IN" }), n.user.email && this.recordLastLoginEmail(n.user.email), n;
1456
1570
  }
1457
1571
  gcOAuthFlows() {
1458
- const t = Date.now() - at;
1572
+ const t = Date.now() - it;
1459
1573
  for (const [e, s] of this.oauthFlows)
1460
1574
  s.startedAt < t && this.oauthFlows.delete(e);
1461
1575
  }
@@ -1480,11 +1594,11 @@ class dt {
1480
1594
  method: "POST",
1481
1595
  body: JSON.stringify({ refresh_token: t })
1482
1596
  }
1483
- ), i = this.toSession(s, e);
1484
- return this.setSession(i), e.is_anonymous === !0 && await this.writeAnonRefreshToken(i.refresh_token), i;
1597
+ ), a = this.toSession(s, e);
1598
+ return this.setSession(a, { event: "TOKEN_REFRESHED" }), e.is_anonymous === !0 && await this.writeAnonRefreshToken(a.refresh_token), a;
1485
1599
  } catch (s) {
1486
1600
  if (s instanceof r && s.status === 401)
1487
- return e.is_anonymous === !0 && await this.clearAnonRefreshToken(), this.setSession(null), null;
1601
+ return e.is_anonymous === !0 && await this.clearAnonRefreshToken(), this.setSession(null, { event: "SIGNED_OUT" }), null;
1488
1602
  throw s;
1489
1603
  } finally {
1490
1604
  this.inflightRefresh = null;
@@ -1517,7 +1631,7 @@ class dt {
1517
1631
  method: "POST",
1518
1632
  headers: { Authorization: `Bearer ${t}` }
1519
1633
  }
1520
- ), this.setSession(null);
1634
+ ), this.setSession(null, { event: "SIGNED_OUT" });
1521
1635
  }
1522
1636
  /**
1523
1637
  * Signout: чистит локальную session СРАЗУ (UX — мгновенный logout без
@@ -1537,7 +1651,7 @@ class dt {
1537
1651
  async signOut(t = {}) {
1538
1652
  await this.hydrated;
1539
1653
  const e = this.session?.access_token, s = this.session?.user.is_anonymous === !0;
1540
- if (this.setSession(null), t.forgetAnonymous && await this.clearAnonRefreshToken(), !!e && !(s && !t.forgetAnonymous))
1654
+ if (this.setSession(null, { event: "SIGNED_OUT" }), t.forgetAnonymous && await this.clearAnonRefreshToken(), !!e && !(s && !t.forgetAnonymous))
1541
1655
  try {
1542
1656
  await this.api.request(
1543
1657
  `/api/v1/paywall/${this.paywallId}/auth/signout`,
@@ -1551,22 +1665,35 @@ class dt {
1551
1665
  }
1552
1666
  /**
1553
1667
  * Подписка на изменения session: signin/signup/refresh/signOut/expired-401.
1554
- * Колбек вызывается с текущим snapshot через microtask (если session есть)
1555
- * + на каждое реальное изменение. Возвращает unsubscribe.
1668
+ *
1669
+ * Гарантированный контракт: ПЕРВЫЙ callback каждому subscriber'у — всегда
1670
+ * `event = 'INITIAL_SESSION'`, дёргается асинхронно после resolve hydrate'а
1671
+ * (даже если session=null — listener получает explicit «нет сессии», а не
1672
+ * молчание). Все последующие callback'и — реальные переходы с конкретным
1673
+ * event'ом (SIGNED_IN / SIGNED_OUT / TOKEN_REFRESHED / USER_UPDATED /
1674
+ * PASSWORD_RECOVERY).
1675
+ *
1676
+ * Это позволяет listener'у безопасно делать «only on real signin» побочные
1677
+ * эффекты (force refetch balances и т.п.) через `event === 'SIGNED_IN'`,
1678
+ * не путая их с восстановлением из storage.
1679
+ *
1680
+ * Возвращает unsubscribe.
1556
1681
  */
1557
1682
  onAuthChange(t) {
1558
- if (this.listeners.add(t), this.session) {
1683
+ return this.listeners.add(t), this.hydrated.then(() => {
1684
+ if (this.destroyed || !this.listeners.has(t)) return;
1559
1685
  const e = this.session;
1560
- queueMicrotask(() => {
1561
- this.listeners.has(t) && t(e);
1562
- });
1563
- }
1564
- return () => {
1686
+ try {
1687
+ t("INITIAL_SESSION", e);
1688
+ } catch (s) {
1689
+ console.warn("[paywall] onAuthChange INITIAL_SESSION threw", s);
1690
+ }
1691
+ }), () => {
1565
1692
  this.listeners.delete(t);
1566
1693
  };
1567
1694
  }
1568
1695
  isFresh(t) {
1569
- return t.expires_at - Date.now() > it;
1696
+ return t.expires_at - Date.now() > st;
1570
1697
  }
1571
1698
  toSession(t, e) {
1572
1699
  const s = t.expires_at != null ? t.expires_at * 1e3 : Date.now() + t.expires_in * 1e3;
@@ -1577,21 +1704,21 @@ class dt {
1577
1704
  user: e
1578
1705
  };
1579
1706
  }
1580
- setSession(t, e = {}) {
1707
+ setSession(t, e) {
1581
1708
  if (this.destroyed) return;
1582
1709
  const s = this.session;
1583
- this.session = t, e.skipPersist || this.persist(), ht(s, t) || this.emit();
1710
+ this.session = t, e.skipPersist || this.persist(), ht(s, t) || this.emit(e.event);
1584
1711
  }
1585
- emit() {
1586
- for (const t of this.listeners)
1712
+ emit(t) {
1713
+ for (const e of this.listeners)
1587
1714
  try {
1588
- t(this.session);
1589
- } catch (e) {
1590
- console.warn("[paywall] onAuthChange listener threw", e);
1715
+ e(t, this.session);
1716
+ } catch (s) {
1717
+ console.warn("[paywall] onAuthChange listener threw", s);
1591
1718
  }
1592
1719
  }
1593
1720
  storageKey() {
1594
- return y.authSession(this.paywallId);
1721
+ return l.authSession(this.paywallId);
1595
1722
  }
1596
1723
  async hydrate() {
1597
1724
  try {
@@ -1600,7 +1727,7 @@ class dt {
1600
1727
  const e = JSON.parse(t);
1601
1728
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1602
1729
  return;
1603
- this.session = e, this.emit();
1730
+ this.session = e;
1604
1731
  } catch {
1605
1732
  }
1606
1733
  }
@@ -1614,7 +1741,7 @@ class dt {
1614
1741
  const e = JSON.parse(t);
1615
1742
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1616
1743
  return;
1617
- this.setSession(e, { skipPersist: !0 });
1744
+ this.setSession(e, { skipPersist: !0, event: "SIGNED_IN" });
1618
1745
  } catch {
1619
1746
  }
1620
1747
  }
@@ -1645,7 +1772,7 @@ class dt {
1645
1772
  }
1646
1773
  async readAnonRefreshToken() {
1647
1774
  try {
1648
- const t = await this.storage.getItem(y.anonRefreshToken(this.paywallId));
1775
+ const t = await this.storage.getItem(l.anonRefreshToken(this.paywallId));
1649
1776
  return typeof t == "string" && t.length > 0 ? t : null;
1650
1777
  } catch {
1651
1778
  return null;
@@ -1654,7 +1781,7 @@ class dt {
1654
1781
  async writeAnonRefreshToken(t) {
1655
1782
  try {
1656
1783
  await this.storage.setItem(
1657
- y.anonRefreshToken(this.paywallId),
1784
+ l.anonRefreshToken(this.paywallId),
1658
1785
  t
1659
1786
  );
1660
1787
  } catch {
@@ -1663,11 +1790,41 @@ class dt {
1663
1790
  async clearAnonRefreshToken() {
1664
1791
  try {
1665
1792
  await this.storage.removeItem(
1666
- y.anonRefreshToken(this.paywallId)
1793
+ l.anonRefreshToken(this.paywallId)
1667
1794
  );
1668
1795
  } catch {
1669
1796
  }
1670
1797
  }
1798
+ /**
1799
+ * Last-used auth method + email — для UI бейджа "Last used" и pre-fill'а
1800
+ * email-инпута. Storage paywall-scoped, поэтому переключение между
1801
+ * пейволами на одном host'е не пересекает данные. Чтение всегда возвращает
1802
+ * объект — отсутствующие поля = null. */
1803
+ async getLastLogin() {
1804
+ try {
1805
+ const [t, e] = await Promise.all([
1806
+ this.storage.getItem(l.lastLoginMethod(this.paywallId)),
1807
+ this.storage.getItem(l.lastLoginEmail(this.paywallId))
1808
+ ]);
1809
+ return !t || !ot(t) ? null : { method: t, email: typeof e == "string" && e ? e : null };
1810
+ } catch {
1811
+ return null;
1812
+ }
1813
+ }
1814
+ /** Запись method и email атомарно (для email/password flows — оба известны
1815
+ * на момент signin/signup'а). OAuth-flows используют раздельные
1816
+ * recordLastLoginMethod (до popup) и recordLastLoginEmail (после exchange). */
1817
+ recordLastLogin(t, e) {
1818
+ this.recordLastLoginMethod(t), e && this.recordLastLoginEmail(e);
1819
+ }
1820
+ recordLastLoginMethod(t) {
1821
+ this.storage.setItem(l.lastLoginMethod(this.paywallId), t).catch(() => {
1822
+ });
1823
+ }
1824
+ recordLastLoginEmail(t) {
1825
+ this.storage.setItem(l.lastLoginEmail(this.paywallId), t).catch(() => {
1826
+ });
1827
+ }
1671
1828
  /**
1672
1829
  * Читает stable visitor_id из storage если он там уже есть. НЕ генерит:
1673
1830
  * AuthClient может быть инстанцирован раньше BillingClient, а синтетический
@@ -1677,34 +1834,34 @@ class dt {
1677
1834
  */
1678
1835
  async readVisitorId() {
1679
1836
  try {
1680
- const t = await this.storage.getItem(y.visitorId);
1837
+ const t = await this.storage.getItem(l.visitorId);
1681
1838
  return typeof t == "string" && t.length >= 16 ? t : void 0;
1682
1839
  } catch {
1683
1840
  return;
1684
1841
  }
1685
1842
  }
1686
1843
  }
1687
- const nt = 5 * 6e4, rt = 500;
1688
- function ot(a, t) {
1844
+ const at = 5 * 6e4, nt = 500;
1845
+ function rt(i, t) {
1689
1846
  return new Promise((e, s) => {
1690
- let i = !1;
1847
+ let a = !1;
1691
1848
  const n = () => {
1692
- i = !0, window.removeEventListener("message", o), clearInterval(u), clearTimeout(l);
1693
- }, o = (d) => {
1694
- if (i) return;
1695
- const h = d.data;
1849
+ a = !0, window.removeEventListener("message", o), clearInterval(u), clearTimeout(d);
1850
+ }, o = (f) => {
1851
+ if (a) return;
1852
+ const h = f.data;
1696
1853
  if (!(!h || h.type !== "pw-oauth") && h.messageId === t) {
1697
1854
  if (h.status === "success" && h.code) {
1698
1855
  n();
1699
1856
  try {
1700
- a.close();
1857
+ i.close();
1701
1858
  } catch {
1702
1859
  }
1703
1860
  e(h.code);
1704
1861
  } else if (h.status === "error") {
1705
1862
  n();
1706
1863
  try {
1707
- a.close();
1864
+ i.close();
1708
1865
  } catch {
1709
1866
  }
1710
1867
  s(
@@ -1716,32 +1873,35 @@ function ot(a, t) {
1716
1873
  }
1717
1874
  }
1718
1875
  }, u = setInterval(() => {
1719
- if (i) return;
1720
- let d;
1876
+ if (a) return;
1877
+ let f;
1721
1878
  try {
1722
- d = a.closed;
1879
+ f = i.closed;
1723
1880
  } catch {
1724
1881
  return;
1725
1882
  }
1726
- d && (n(), s(new r("oauth_cancelled", "auth popup was closed")));
1727
- }, rt), l = setTimeout(() => {
1728
- if (!i) {
1883
+ f && (n(), s(new r("oauth_cancelled", "auth popup was closed")));
1884
+ }, nt), d = setTimeout(() => {
1885
+ if (!a) {
1729
1886
  n();
1730
1887
  try {
1731
- a.close();
1888
+ i.close();
1732
1889
  } catch {
1733
1890
  }
1734
1891
  s(new r("oauth_timeout", "OAuth flow timed out"));
1735
1892
  }
1736
- }, nt);
1893
+ }, at);
1737
1894
  window.addEventListener("message", o);
1738
1895
  });
1739
1896
  }
1740
- function ht(a, t) {
1741
- return a === t ? !0 : !a || !t ? !1 : a.access_token === t.access_token && a.refresh_token === t.refresh_token && a.expires_at === t.expires_at && a.user.id === t.user.id && a.user.email === t.user.email;
1897
+ function ot(i) {
1898
+ return i === "google" || i === "apple" || i === "github" || i === "facebook" || i === "email";
1742
1899
  }
1743
- const ct = 1500, lt = 20, U = 200;
1744
- class ft {
1900
+ function ht(i, t) {
1901
+ return i === t ? !0 : !i || !t ? !1 : i.access_token === t.access_token && i.refresh_token === t.refresh_token && i.expires_at === t.expires_at && i.user.id === t.user.id && i.user.email === t.user.email;
1902
+ }
1903
+ const ct = 1500, lt = 20, k = 200;
1904
+ class gt {
1745
1905
  constructor(t) {
1746
1906
  this.buffer = [], this.flushTimer = null, this.destroyed = !1, this.unloadHandler = null, this.visibilityHandler = null, this.opts = t, this.isEnabled() && this.attachUnloadHandlers();
1747
1907
  }
@@ -1756,7 +1916,7 @@ class ft {
1756
1916
  this.flush();
1757
1917
  return;
1758
1918
  }
1759
- this.buffer.length > U && (this.buffer = this.buffer.slice(-U)), this.scheduleFlush();
1919
+ this.buffer.length > k && (this.buffer = this.buffer.slice(-k)), this.scheduleFlush();
1760
1920
  }
1761
1921
  scheduleFlush() {
1762
1922
  if (this.flushTimer || this.destroyed) return;
@@ -1771,7 +1931,7 @@ class ft {
1771
1931
  const t = this.buffer;
1772
1932
  this.buffer = [];
1773
1933
  try {
1774
- const e = await this.opts.getVisitorId(), s = this.opts.getUserId?.() ?? null, i = JSON.stringify({ events: t }), n = this.opts.fetch ?? (typeof fetch < "u" ? fetch : void 0);
1934
+ const e = await this.opts.getVisitorId(), s = this.opts.getUserId?.() ?? null, a = JSON.stringify({ events: t }), n = this.opts.fetch ?? (typeof fetch < "u" ? fetch : void 0);
1775
1935
  if (!n) return;
1776
1936
  await n(this.opts.endpoint, {
1777
1937
  method: "POST",
@@ -1779,7 +1939,7 @@ class ft {
1779
1939
  keepalive: !0,
1780
1940
  // если страница закроется в этот момент — браузер всё равно дотянет
1781
1941
  headers: this.buildHeaders(e, s),
1782
- body: i
1942
+ body: a
1783
1943
  });
1784
1944
  } catch {
1785
1945
  }
@@ -1799,12 +1959,12 @@ class ft {
1799
1959
  this.buffer.unshift(...t), this.flush();
1800
1960
  return;
1801
1961
  }
1802
- const i = JSON.stringify({
1962
+ const a = JSON.stringify({
1803
1963
  events: t,
1804
1964
  // body-level дубликаты для beacon-flow, читаются сервером как fallback.
1805
1965
  visitor_id: e,
1806
1966
  user_id: s,
1807
- sdk_version: g,
1967
+ sdk_version: m,
1808
1968
  paywall_id: this.opts.paywallId,
1809
1969
  capabilities: this.opts.capabilities?.join(",") ?? ""
1810
1970
  }), n = this.opts.sendBeacon ?? (typeof navigator < "u" && typeof navigator.sendBeacon == "function" ? navigator.sendBeacon.bind(navigator) : null);
@@ -1813,7 +1973,7 @@ class ft {
1813
1973
  return;
1814
1974
  }
1815
1975
  try {
1816
- n(this.opts.endpoint, i) || (this.buffer.unshift(...t), this.flush());
1976
+ n(this.opts.endpoint, a) || (this.buffer.unshift(...t), this.flush());
1817
1977
  } catch {
1818
1978
  this.buffer.unshift(...t), this.flush();
1819
1979
  }
@@ -1821,7 +1981,7 @@ class ft {
1821
1981
  buildHeaders(t, e) {
1822
1982
  const s = {
1823
1983
  "Content-Type": "application/json",
1824
- "X-SDK-Version": g,
1984
+ "X-SDK-Version": m,
1825
1985
  "X-Paywall-Id": this.opts.paywallId,
1826
1986
  "X-Visitor-Id": t
1827
1987
  };
@@ -1839,18 +1999,64 @@ class ft {
1839
1999
  this.destroyed || (this.destroyed = !0, this.flushTimer && (clearTimeout(this.flushTimer), this.flushTimer = null), this.flush(), this.detachUnloadHandlers());
1840
2000
  }
1841
2001
  }
2002
+ function ut(i) {
2003
+ return `pw-offer-${i}-start`;
2004
+ }
2005
+ function wt(i, t) {
2006
+ if (!i || i.length === 0) return null;
2007
+ const e = i.find(
2008
+ (a) => a.price_id === t && (a.discount_percent ?? 0) > 0
2009
+ );
2010
+ return e || (i.find(
2011
+ (a) => a.price_id == null && (a.discount_percent ?? 0) > 0
2012
+ ) ?? null);
2013
+ }
2014
+ function mt(i, t = {}) {
2015
+ const e = i.discount_percent ?? 0;
2016
+ if (e <= 0) return null;
2017
+ const s = t.now ?? Date.now(), a = dt(i, t.readStart), n = ft(i, a), o = a !== null ? Math.max(0, a - s) : null;
2018
+ return a !== null && a <= s ? null : { offer: i, discountPercent: e, remainingMs: o, totalMs: n, expiresAt: a };
2019
+ }
2020
+ function dt(i, t) {
2021
+ if (i.expires_at) {
2022
+ const e = Date.parse(i.expires_at);
2023
+ return Number.isFinite(e) ? e : null;
2024
+ }
2025
+ if (i.duration_minutes && i.duration_minutes > 0 && t) {
2026
+ const e = t(i.id);
2027
+ if (!e) return null;
2028
+ const s = Date.parse(e);
2029
+ return Number.isFinite(s) ? s + i.duration_minutes * 6e4 : null;
2030
+ }
2031
+ return null;
2032
+ }
2033
+ function ft(i, t) {
2034
+ return i.duration_minutes && i.duration_minutes > 0 ? i.duration_minutes * 6e4 : t !== null ? t - Date.now() : null;
2035
+ }
2036
+ function It(i) {
2037
+ if (typeof window > "u") return null;
2038
+ try {
2039
+ return window.localStorage.getItem(ut(i));
2040
+ } catch {
2041
+ return null;
2042
+ }
2043
+ }
1842
2044
  export {
1843
- k as ApiClient,
1844
- K as ApiGatewayClient,
1845
- dt as AuthClient,
1846
- ut as BillingClient,
1847
- ft as EventTracker,
2045
+ E as ApiClient,
2046
+ D as ApiGatewayClient,
2047
+ pt as AuthClient,
2048
+ yt as BillingClient,
2049
+ gt as EventTracker,
1848
2050
  r as PaywallError,
1849
2051
  R as QuotaExceededError,
1850
- g as SDK_VERSION,
1851
- y as STORAGE_KEYS,
1852
- O as createStorage,
1853
- I as ensureVisitorId,
1854
- C as generateVisitorId
2052
+ m as SDK_VERSION,
2053
+ l as STORAGE_KEYS,
2054
+ L as createStorage,
2055
+ b as ensureVisitorId,
2056
+ wt as findApplicableOffer,
2057
+ C as generateVisitorId,
2058
+ ut as offerStartStorageKey,
2059
+ It as readBrowserOfferStart,
2060
+ mt as resolveOffer
1855
2061
  };
1856
2062
  //# sourceMappingURL=core.js.map