@monetize.software/sdk 3.0.0-alpha.2 → 3.0.0-alpha.20

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-BXEOgPTK.js +3555 -0
  3. package/dist/chunks/PaywallUI-BXEOgPTK.js.map +1 -0
  4. package/dist/chunks/PaywallUI-Cybo6wdO.js +26 -0
  5. package/dist/chunks/PaywallUI-Cybo6wdO.js.map +1 -0
  6. package/dist/chunks/ar-OnxZkqWR.js +2 -0
  7. package/dist/chunks/ar-OnxZkqWR.js.map +1 -0
  8. package/dist/chunks/ar-rSKgwKvp.js +129 -0
  9. package/dist/chunks/ar-rSKgwKvp.js.map +1 -0
  10. package/dist/chunks/cs-Cb2KZ70r.js +2 -0
  11. package/dist/chunks/cs-Cb2KZ70r.js.map +1 -0
  12. package/dist/chunks/cs-DIWkcge_.js +125 -0
  13. package/dist/chunks/cs-DIWkcge_.js.map +1 -0
  14. package/dist/chunks/da-DdMW98j3.js +125 -0
  15. package/dist/chunks/da-DdMW98j3.js.map +1 -0
  16. package/dist/chunks/da-DyQC12xy.js +2 -0
  17. package/dist/chunks/da-DyQC12xy.js.map +1 -0
  18. package/dist/chunks/de-CWM13wnK.js +2 -0
  19. package/dist/chunks/de-CWM13wnK.js.map +1 -0
  20. package/dist/chunks/de-D1bSmD_-.js +144 -0
  21. package/dist/chunks/de-D1bSmD_-.js.map +1 -0
  22. package/dist/chunks/el-BtKuORsc.js +2 -0
  23. package/dist/chunks/el-BtKuORsc.js.map +1 -0
  24. package/dist/chunks/el-C4LtWpfP.js +129 -0
  25. package/dist/chunks/el-C4LtWpfP.js.map +1 -0
  26. package/dist/chunks/es-Bhx7w85J.js +144 -0
  27. package/dist/chunks/es-Bhx7w85J.js.map +1 -0
  28. package/dist/chunks/es-qLcKnBft.js +2 -0
  29. package/dist/chunks/es-qLcKnBft.js.map +1 -0
  30. package/dist/chunks/fi-C34Oc6rg.js +125 -0
  31. package/dist/chunks/fi-C34Oc6rg.js.map +1 -0
  32. package/dist/chunks/fi-kGtbK51C.js +2 -0
  33. package/dist/chunks/fi-kGtbK51C.js.map +1 -0
  34. package/dist/chunks/fr-BrWtqej3.js +144 -0
  35. package/dist/chunks/fr-BrWtqej3.js.map +1 -0
  36. package/dist/chunks/fr-CScwFVNj.js +2 -0
  37. package/dist/chunks/fr-CScwFVNj.js.map +1 -0
  38. package/dist/chunks/he-Byr2r07x.js +129 -0
  39. package/dist/chunks/he-Byr2r07x.js.map +1 -0
  40. package/dist/chunks/he-ChFVbP_S.js +2 -0
  41. package/dist/chunks/he-ChFVbP_S.js.map +1 -0
  42. package/dist/chunks/hi-BIswPYL2.js +2 -0
  43. package/dist/chunks/hi-BIswPYL2.js.map +1 -0
  44. package/dist/chunks/hi-CABVgpKU.js +129 -0
  45. package/dist/chunks/hi-CABVgpKU.js.map +1 -0
  46. package/dist/chunks/hu-CSQ9avfJ.js +125 -0
  47. package/dist/chunks/hu-CSQ9avfJ.js.map +1 -0
  48. package/dist/chunks/hu-CT_jwL0k.js +2 -0
  49. package/dist/chunks/hu-CT_jwL0k.js.map +1 -0
  50. package/dist/chunks/id-2lYf7ogC.js +2 -0
  51. package/dist/chunks/id-2lYf7ogC.js.map +1 -0
  52. package/dist/chunks/id-BJ5w6RSU.js +125 -0
  53. package/dist/chunks/id-BJ5w6RSU.js.map +1 -0
  54. package/dist/chunks/it-CEMhCvXU.js +2 -0
  55. package/dist/chunks/it-CEMhCvXU.js.map +1 -0
  56. package/dist/chunks/it-Df8ChmTK.js +144 -0
  57. package/dist/chunks/it-Df8ChmTK.js.map +1 -0
  58. package/dist/chunks/ja-CkpO3n78.js +2 -0
  59. package/dist/chunks/ja-CkpO3n78.js.map +1 -0
  60. package/dist/chunks/ja-a53E5b2s.js +148 -0
  61. package/dist/chunks/ja-a53E5b2s.js.map +1 -0
  62. package/dist/chunks/ko-AZ8GrmXu.js +148 -0
  63. package/dist/chunks/ko-AZ8GrmXu.js.map +1 -0
  64. package/dist/chunks/ko-BKdzk0jX.js +2 -0
  65. package/dist/chunks/ko-BKdzk0jX.js.map +1 -0
  66. package/dist/chunks/nl-Bek7IiHL.js +2 -0
  67. package/dist/chunks/nl-Bek7IiHL.js.map +1 -0
  68. package/dist/chunks/nl-sF6ms5FU.js +144 -0
  69. package/dist/chunks/nl-sF6ms5FU.js.map +1 -0
  70. package/dist/chunks/no-BztcQKh8.js +2 -0
  71. package/dist/chunks/no-BztcQKh8.js.map +1 -0
  72. package/dist/chunks/no-DGf5PuW5.js +125 -0
  73. package/dist/chunks/no-DGf5PuW5.js.map +1 -0
  74. package/dist/chunks/pl-CMF2KerQ.js +2 -0
  75. package/dist/chunks/pl-CMF2KerQ.js.map +1 -0
  76. package/dist/chunks/pl-Dd-Ze6wn.js +125 -0
  77. package/dist/chunks/pl-Dd-Ze6wn.js.map +1 -0
  78. package/dist/chunks/pt-BL9X8Du2.js +144 -0
  79. package/dist/chunks/pt-BL9X8Du2.js.map +1 -0
  80. package/dist/chunks/pt-DF9cd_iW.js +2 -0
  81. package/dist/chunks/pt-DF9cd_iW.js.map +1 -0
  82. package/dist/chunks/ro-CGYmtR8q.js +125 -0
  83. package/dist/chunks/ro-CGYmtR8q.js.map +1 -0
  84. package/dist/chunks/ro-DpPc1UhJ.js +2 -0
  85. package/dist/chunks/ro-DpPc1UhJ.js.map +1 -0
  86. package/dist/chunks/ru-gt3-clOi.js +2 -0
  87. package/dist/chunks/ru-gt3-clOi.js.map +1 -0
  88. package/dist/chunks/ru-oPoQtUxk.js +147 -0
  89. package/dist/chunks/ru-oPoQtUxk.js.map +1 -0
  90. package/dist/chunks/sv-Cg7O9Uh3.js +2 -0
  91. package/dist/chunks/sv-Cg7O9Uh3.js.map +1 -0
  92. package/dist/chunks/sv-kXHP1Ct3.js +125 -0
  93. package/dist/chunks/sv-kXHP1Ct3.js.map +1 -0
  94. package/dist/chunks/th-DMcmb36d.js +129 -0
  95. package/dist/chunks/th-DMcmb36d.js.map +1 -0
  96. package/dist/chunks/th-pvtT9u-U.js +2 -0
  97. package/dist/chunks/th-pvtT9u-U.js.map +1 -0
  98. package/dist/chunks/tr-gAn3KCul.js +2 -0
  99. package/dist/chunks/tr-gAn3KCul.js.map +1 -0
  100. package/dist/chunks/tr-zjLbddlL.js +125 -0
  101. package/dist/chunks/tr-zjLbddlL.js.map +1 -0
  102. package/dist/chunks/uk-BYSiM14V.js +147 -0
  103. package/dist/chunks/uk-BYSiM14V.js.map +1 -0
  104. package/dist/chunks/uk-HIaOETe4.js +2 -0
  105. package/dist/chunks/uk-HIaOETe4.js.map +1 -0
  106. package/dist/chunks/vi-B7DVCjxx.js +2 -0
  107. package/dist/chunks/vi-B7DVCjxx.js.map +1 -0
  108. package/dist/chunks/vi-FbVRwy9D.js +125 -0
  109. package/dist/chunks/vi-FbVRwy9D.js.map +1 -0
  110. package/dist/chunks/zh-007yK7rl.js +2 -0
  111. package/dist/chunks/zh-007yK7rl.js.map +1 -0
  112. package/dist/chunks/zh-Cv0Yw4qR.js +148 -0
  113. package/dist/chunks/zh-Cv0Yw4qR.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 +260 -26
  117. package/dist/core.js +505 -338
  118. package/dist/core.js.map +1 -1
  119. package/dist/index.cjs +1 -1
  120. package/dist/index.d.ts +411 -45
  121. package/dist/index.js +14 -10
  122. package/dist/ui.cjs +1 -1
  123. package/dist/ui.d.ts +376 -44
  124. package/dist/ui.js +1 -1
  125. package/package.json +32 -31
  126. package/dist/chunks/PaywallUI-CRTEPjJm.js +0 -2229
  127. package/dist/chunks/PaywallUI-CRTEPjJm.js.map +0 -1
  128. package/dist/chunks/PaywallUI-CbbcfXXZ.js +0 -26
  129. package/dist/chunks/PaywallUI-CbbcfXXZ.js.map +0 -1
package/dist/core.js CHANGED
@@ -3,7 +3,7 @@ class r extends Error {
3
3
  super(e), this.name = "PaywallError", this.code = t, this.status = s.status, this.cause = s.cause;
4
4
  }
5
5
  }
6
- class q extends r {
6
+ class R extends r {
7
7
  constructor(t) {
8
8
  super("not_enough_queries", t.message ?? "Not enough queries", {
9
9
  status: 402
@@ -11,41 +11,45 @@ class q extends r {
11
11
  }
12
12
  }
13
13
  const m = "3.0.0-alpha.0";
14
- class O {
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);
19
+ const s = new URL(t, this.opts.apiOrigin).toString(), a = this.opts.fetch ?? fetch, n = new Headers(e.headers);
20
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
- const u = typeof FormData < "u" && e.body instanceof FormData;
24
- e.body && !n.has("Content-Type") && !u && n.set("Content-Type", "application/json");
25
- let l;
23
+ const d = typeof FormData < "u" && e.body instanceof FormData;
24
+ e.body && !n.has("Content-Type") && !d && n.set("Content-Type", "application/json");
25
+ let f;
26
26
  try {
27
- l = await i(s, {
27
+ f = await a(s, {
28
28
  ...e,
29
29
  headers: n,
30
30
  credentials: "omit"
31
31
  });
32
- } catch (c) {
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 });
32
+ } catch (h) {
33
+ throw (h && typeof h == "object" && "name" in h ? h.name : void 0) === "AbortError" ? new r("aborted", "Request aborted", { cause: h }) : new r("network_error", "Network request failed", { cause: h });
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 l = (f.headers.get("content-type") ?? "").includes("application/json") ? await f.json().catch(() => null) : null;
36
+ if (!f.ok) {
37
+ const h = l && typeof l == "object" && "code" in l && String(l.code) || `http_${f.status}`, g = l && typeof l == "object" && "message" in l && String(l.message) || f.statusText || "Request failed";
38
+ throw new r(h, g, { status: f.status, cause: l });
39
39
  }
40
- return f;
40
+ return l;
41
41
  }
42
42
  }
43
- const $ = "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 ?? $, 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,229 +59,234 @@ 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", m), 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";
63
- 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;
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, d = typeof Blob < "u" && t.body instanceof Blob, f = typeof ReadableStream < "u" && t.body instanceof ReadableStream, p = typeof t.body == "string";
66
67
  let c;
68
+ t.body === void 0 || t.body === null ? c = void 0 : o || d || f || p ? c = t.body : (c = JSON.stringify(t.body), a.has("Content-Type") || a.set("Content-Type", "application/json"));
69
+ const l = this.customFetch ?? fetch;
70
+ let h;
67
71
  try {
68
- c = await f(s.toString(), {
72
+ h = await l(s.toString(), {
69
73
  method: t.method ?? "POST",
70
- headers: i,
71
- body: h,
74
+ headers: a,
75
+ body: c,
72
76
  signal: t.signal,
73
77
  credentials: "omit"
74
78
  });
75
- } catch (p) {
76
- const R = p instanceof Error ? p.message : String(p);
77
- throw new r("network_error", `Network request failed: ${R}`, { cause: p });
79
+ } catch (y) {
80
+ const P = y instanceof Error ? y.message : String(y);
81
+ throw new r("network_error", `Network request failed: ${P}`, { cause: y });
78
82
  }
79
- if (c.status === 402) {
80
- const p = await F(c);
81
- throw this.onQuotaExceeded?.(p), p;
83
+ if (h.status === 402) {
84
+ const y = await q(h);
85
+ throw this.onQuotaExceeded?.(y), y;
82
86
  }
83
- if (!c.ok) {
84
- const p = await N(c.clone());
87
+ if (!h.ok) {
88
+ const y = await $(h.clone());
85
89
  throw new r(
86
- p ?? `http_${c.status}`,
87
- c.statusText || "Gateway request failed",
88
- { status: c.status }
90
+ y ?? `http_${h.status}`,
91
+ h.statusText || "Gateway request failed",
92
+ { status: h.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 = h.headers.get("X-Query-Type") ?? void 0;
96
+ return this.onChargeSuccess?.(g), h;
93
97
  }
94
98
  }
95
- async function F(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
- return new q({
111
+ return new R({
108
112
  balances: s,
109
113
  queryType: t.details?.queryType ?? "",
110
114
  currentBalance: t.details?.currentBalance ?? null
111
115
  });
112
116
  }
113
- async function N(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 D() {
126
+ function F() {
123
127
  return typeof chrome < "u" && !!chrome?.storage?.local && !!chrome?.runtime?.id;
124
128
  }
125
129
  const M = {
126
- getItem(a) {
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
- }, b = /* @__PURE__ */ new Map(), J = {
184
- async getItem(a) {
185
- return b.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
- b.set(a, t);
191
+ async setItem(i, t) {
192
+ I.set(i, t);
189
193
  },
190
- async removeItem(a) {
191
- b.delete(a);
194
+ async removeItem(i) {
195
+ I.delete(i);
192
196
  }
193
197
  };
194
- function C(a) {
195
- return a || (D() ? M : typeof window < "u" && "localStorage" in window ? x : J);
198
+ function L(i) {
199
+ return i || (F() ? M : typeof window < "u" && "localStorage" in window ? x : J);
196
200
  }
197
- const y = {
201
+ const u = {
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
- function E() {
228
- const a = typeof globalThis < "u" ? globalThis.crypto : void 0;
229
- if (a && typeof a.randomUUID == "function") return a.randomUUID();
231
+ function C() {
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 S(a) {
243
+ async function b(i) {
240
244
  try {
241
- const e = await a.getItem(y.visitorId);
245
+ const e = await i.getItem(u.visitorId);
242
246
  if (e && typeof e == "string" && e.length >= 16) return e;
243
247
  } catch {
244
248
  }
245
- const t = E();
249
+ const t = C();
246
250
  try {
247
- await a.setItem(y.visitorId, t);
251
+ await i.setItem(u.visitorId, t);
248
252
  } catch {
249
253
  }
250
254
  return t;
251
255
  }
252
- const H = 5e3, V = 30 * 6e4, I = 60 * 6e4, j = 5 * 6e4, B = {
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 _(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 X(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 z = 5e3, A = 5 * 6e4, G = 3e4;
264
- function Q(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 W = "https://appbox.space";
272
- class ut {
276
+ class yt {
273
277
  constructor(t) {
274
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 ?? W, this.capabilities = t.capabilities, this.auth = t.auth, this.previewMode = t.preview === !0;
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 = C(t.storage), this.api = new O({
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
- Y(this.identity, i) || this.setIdentity(i);
292
- })), this.hydrateUserFromStorage(), this.hydrateBootstrapFromStorage(), this.subscribeBootstrapStorage(), this.hydrateBalancesFromStorage(), this.subscribeBalancesStorage(), this.visitorIdPromise = S(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 = S(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'ы. Вызывать когда
@@ -333,9 +342,9 @@ class ut {
333
342
  "BillingClient in preview mode but cachedBootstrap is not seeded. Call setBootstrap(bootstrap) before open()."
334
343
  );
335
344
  }
336
- const s = Date.now(), i = this.cachedBootstrap && this.cachedBootstrapAt > 0 && s - this.cachedBootstrapAt < I;
337
- return !e.force && i ? (s - this.cachedBootstrapAt > j && this.revalidateBootstrap(e.signal).catch(() => {
338
- }), this.cachedBootstrap) : this.inflightBootstrap ? this.inflightBootstrap : (this.inflightBootstrap = this.fetchBootstrap({
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({
339
348
  ifVersion: e.force ? void 0 : this.cachedBootstrap?.version,
340
349
  signal: e.signal
341
350
  }).finally(() => {
@@ -383,10 +392,10 @@ class ut {
383
392
  offers: t.offers !== void 0 ? t.offers : e.offers,
384
393
  version: `preview:${++this.previewVersionCounter}`
385
394
  };
386
- s.layout || (s.layout = U(s.settings, s.prices)), g(s), this.cachedBootstrap = s, this.cachedBootstrapAt = Date.now();
387
- for (const i of this.bootstrapListeners)
395
+ s.layout || (s.layout = O(s.settings, s.prices)), w(s), this.cachedBootstrap = s, this.cachedBootstrapAt = Date.now();
396
+ for (const a of this.bootstrapListeners)
388
397
  try {
389
- i(s);
398
+ a(s);
390
399
  } catch (n) {
391
400
  console.warn("[paywall] onBootstrapChange listener threw", n);
392
401
  }
@@ -398,14 +407,14 @@ class ut {
398
407
  async fetchBootstrap(t) {
399
408
  const e = {};
400
409
  this.identity?.email && (e["X-User-Email"] = this.identity.email);
401
- 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, {
402
411
  ...Object.keys(e).length ? { headers: e } : {},
403
412
  signal: t.signal
404
413
  });
405
- if ("unchanged" in i && i.unchanged)
406
- return this.cachedBootstrap ? (this.cachedBootstrapAt = Date.now(), i.user && this.applyUser(i.user), this.cachedBootstrap) : this.fetchBootstrap({ signal: t.signal });
407
- const n = i;
408
- return n.layout || (n.layout = U(n.settings, n.prices)), g(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 = O(n.settings, n.prices)), w(n), this.applyBootstrap(n, { persist: !0 }), n.user && this.applyUser(n.user), n;
409
418
  }
410
419
  // Фоновый revalidate из stale-while-revalidate ветки. Дедуплицируется через
411
420
  // `inflightBootstrap`, чтобы параллельные revalidate'ы не пересекались.
@@ -425,9 +434,9 @@ class ut {
425
434
  applyBootstrap(t, { persist: e }) {
426
435
  const s = !this.cachedBootstrap || this.cachedBootstrap.version !== t.version;
427
436
  if (this.cachedBootstrap = t, this.cachedBootstrapAt = Date.now(), e && this.persistBootstrap(t), s)
428
- for (const i of this.bootstrapListeners)
437
+ for (const a of this.bootstrapListeners)
429
438
  try {
430
- i(t);
439
+ a(t);
431
440
  } catch (n) {
432
441
  console.warn("[paywall] onBootstrapChange listener threw", n);
433
442
  }
@@ -435,16 +444,16 @@ class ut {
435
444
  async hydrateBootstrapFromStorage() {
436
445
  if (!this.cachedBootstrap)
437
446
  try {
438
- const t = await this.storage.getItem(y.bootstrap(this.paywallId));
447
+ const t = await this.storage.getItem(u.bootstrap(this.paywallId));
439
448
  if (!t) return;
440
449
  const e = JSON.parse(t);
441
- if (!e?.bootstrap || Date.now() - e.at > I || this.cachedBootstrap) return;
442
- g(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;
443
452
  for (const s of this.bootstrapListeners)
444
453
  try {
445
454
  s(e.bootstrap);
446
- } catch (i) {
447
- console.warn("[paywall] onBootstrapChange listener threw", i);
455
+ } catch (a) {
456
+ console.warn("[paywall] onBootstrapChange listener threw", a);
448
457
  }
449
458
  } catch {
450
459
  }
@@ -454,7 +463,7 @@ class ut {
454
463
  try {
455
464
  const { user: e, ...s } = t;
456
465
  await this.storage.setItem(
457
- y.bootstrap(this.paywallId),
466
+ u.bootstrap(this.paywallId),
458
467
  JSON.stringify({ at: Date.now(), bootstrap: s })
459
468
  );
460
469
  } catch {
@@ -465,7 +474,7 @@ class ut {
465
474
  // no-op, всё работает как раньше через сеть.
466
475
  subscribeBootstrapStorage() {
467
476
  typeof this.storage.watch == "function" && (this.bootstrapStorageUnwatch = this.storage.watch(
468
- y.bootstrap(this.paywallId),
477
+ u.bootstrap(this.paywallId),
469
478
  (t) => {
470
479
  if (t)
471
480
  try {
@@ -475,7 +484,7 @@ class ut {
475
484
  this.cachedBootstrapAt = e.at;
476
485
  return;
477
486
  }
478
- g(e.bootstrap), this.applyBootstrap(e.bootstrap, { persist: !1 });
487
+ w(e.bootstrap), this.applyBootstrap(e.bootstrap, { persist: !1 });
479
488
  } catch {
480
489
  }
481
490
  }
@@ -503,6 +512,15 @@ class ut {
503
512
  getCachedPrices() {
504
513
  return this.cachedBootstrap?.prices ?? null;
505
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
+ }
506
524
  /**
507
525
  * Снимок того, какой язык SDK сейчас считает «языком юзера». Полезно для
508
526
  * синхронизации i18n хоста с тем, что фактически показывает пейвол — чтобы
@@ -522,7 +540,7 @@ class ut {
522
540
  * есть `navigator.language`.
523
541
  */
524
542
  getUserLanguage() {
525
- const t = typeof navigator < "u" && navigator.language ? navigator.language : null, e = this.cachedBootstrap?.settings.locale_default ?? null, s = this.cachedBootstrap ? L(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;
526
544
  return { tag: s ?? t ?? e, applied: s, browserLanguage: t, countryLanguage: e };
527
545
  }
528
546
  /**
@@ -537,7 +555,7 @@ class ut {
537
555
  return !t && this.cachedUser && Date.now() - this.cachedUserAt < H ? this.cachedUser : this.inflightUser ? this.inflightUser : (this.inflightUser = (async () => {
538
556
  try {
539
557
  if (!this.identity?.email)
540
- return this.applyUser(B), B;
558
+ return this.applyUser(v), v;
541
559
  const s = await this.api.request(
542
560
  `/api/v1/paywall/${this.paywallId}/user-state`,
543
561
  { headers: { "X-User-Email": this.identity.email }, signal: e }
@@ -569,16 +587,16 @@ class ut {
569
587
  this.userListeners.add(t);
570
588
  const s = e.immediate ?? "microtask";
571
589
  if (this.cachedUser && s !== "none") {
572
- const i = this.cachedUser;
590
+ const a = this.cachedUser;
573
591
  if (s === "sync")
574
592
  try {
575
- t(i);
593
+ t(a);
576
594
  } catch (n) {
577
595
  console.warn("[paywall] onUserChange initial sync threw", n);
578
596
  }
579
597
  else
580
598
  queueMicrotask(() => {
581
- this.userListeners.has(t) && t(i);
599
+ this.userListeners.has(t) && t(a);
582
600
  });
583
601
  }
584
602
  return () => {
@@ -596,13 +614,13 @@ class ut {
596
614
  for (const s of this.userListeners)
597
615
  try {
598
616
  s(t);
599
- } catch (i) {
600
- console.warn("[paywall] onUserChange listener threw", i);
617
+ } catch (a) {
618
+ console.warn("[paywall] onUserChange listener threw", a);
601
619
  }
602
620
  }
603
621
  }
604
622
  storageKey() {
605
- return y.userState(this.paywallId, _(this.identity));
623
+ return u.userState(this.paywallId, B(this.identity));
606
624
  }
607
625
  async hydrateUserFromStorage() {
608
626
  if (!this.cachedUser)
@@ -638,8 +656,8 @@ class ut {
638
656
  * по `currentBalance` в QuotaExceededError или `balances.length`.
639
657
  */
640
658
  async getBalances({ force: t = !1, signal: e } = {}) {
641
- const s = Date.now(), i = this.cachedBalances ? s - this.cachedBalancesAt : 1 / 0;
642
- return !t && this.cachedBalances && (i < z || i < G) ? 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(() => {
643
661
  }), this.cachedBalances) : this.inflightBalances ? this.inflightBalances : this.fetchBalances({ signal: e });
644
662
  }
645
663
  // Network primitive — единая точка для force/stale-revalidate/cold-start.
@@ -670,16 +688,16 @@ class ut {
670
688
  this.balanceListeners.add(t);
671
689
  const s = e.immediate ?? "microtask";
672
690
  if (this.cachedBalances && s !== "none") {
673
- const i = this.cachedBalances;
691
+ const a = this.cachedBalances;
674
692
  if (s === "sync")
675
693
  try {
676
- t(i);
694
+ t(a);
677
695
  } catch (n) {
678
696
  console.warn("[paywall] onBalanceChange initial sync threw", n);
679
697
  }
680
698
  else
681
699
  queueMicrotask(() => {
682
- this.balanceListeners.has(t) && t(i);
700
+ this.balanceListeners.has(t) && t(a);
683
701
  });
684
702
  }
685
703
  return () => {
@@ -708,10 +726,10 @@ class ut {
708
726
  if (!this.cachedBalances) return;
709
727
  const e = this.cachedBalances.findIndex((n) => n.type === t);
710
728
  if (e < 0 || this.cachedBalances[e].count <= 0) return;
711
- const i = this.cachedBalances.map(
729
+ const a = this.cachedBalances.map(
712
730
  (n, o) => o === e ? { ...n, count: n.count - 1 } : n
713
731
  );
714
- this.applyBalances(i);
732
+ this.applyBalances(a);
715
733
  }
716
734
  /** Принудительный re-fetch — типичный вызов после QuotaExceededError, чтобы
717
735
  * UI получил актуальный balance=0 и нарисовал upgrade-prompt. */
@@ -730,7 +748,7 @@ class ut {
730
748
  */
731
749
  createApiGatewayClient(t = {}) {
732
750
  const e = t.onChargeSuccess, s = t.onQuotaExceeded;
733
- return new K({
751
+ return new D({
734
752
  paywallId: this.paywallId,
735
753
  apiOrigin: this.apiOrigin,
736
754
  auth: this.auth,
@@ -738,26 +756,26 @@ class ut {
738
756
  capabilities: this.capabilities,
739
757
  fetch: this.fetchImpl,
740
758
  ...t,
741
- onChargeSuccess: (i) => {
742
- this.decrementBalanceLocal(i), e?.(i);
759
+ onChargeSuccess: (a) => {
760
+ this.decrementBalanceLocal(a), e?.(a);
743
761
  },
744
- onQuotaExceeded: (i) => {
745
- this.refreshBalances(), s?.(i);
762
+ onQuotaExceeded: (a) => {
763
+ this.refreshBalances(), s?.(a);
746
764
  }
747
765
  });
748
766
  }
749
767
  applyBalances(t, { persist: e = !0 } = {}) {
750
768
  const s = !Q(this.cachedBalances, t);
751
769
  if (this.cachedBalances = t, this.cachedBalancesAt = Date.now(), e && this.persistBalances(t), s)
752
- for (const i of this.balanceListeners)
770
+ for (const a of this.balanceListeners)
753
771
  try {
754
- i(t);
772
+ a(t);
755
773
  } catch (n) {
756
774
  console.warn("[paywall] onBalanceChange listener threw", n);
757
775
  }
758
776
  }
759
777
  balancesStorageKey() {
760
- return y.balances(this.paywallId, _(this.identity));
778
+ return u.balances(this.paywallId, B(this.identity));
761
779
  }
762
780
  async hydrateBalancesFromStorage() {
763
781
  if (!this.cachedBalances)
@@ -770,8 +788,8 @@ class ut {
770
788
  for (const s of this.balanceListeners)
771
789
  try {
772
790
  s(e.balances);
773
- } catch (i) {
774
- console.warn("[paywall] onBalanceChange listener threw", i);
791
+ } catch (a) {
792
+ console.warn("[paywall] onBalanceChange listener threw", a);
775
793
  }
776
794
  } catch {
777
795
  }
@@ -811,23 +829,27 @@ class ut {
811
829
  const e = t.idempotencyKey ?? `auto:${t.priceId}`, s = this.inflightCheckouts.get(e);
812
830
  if (s) return s;
813
831
  const n = {
814
- "Idempotency-Key": t.idempotencyKey ?? E()
832
+ "Idempotency-Key": t.idempotencyKey ?? C()
815
833
  };
816
834
  this.apiKey && (n["X-Api-Key"] = this.apiKey);
817
- 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, d = t.successUrl ?? o?.success_redirect_url ?? void 0, f = t.shopUrl ?? o?.checkout_shop_url ?? void 0, c = this.cachedBootstrap?.prices.find(
836
+ (h) => h.id === t.priceId
837
+ )?.local?.currency ?? void 0, l = this.api.request(`/api/v1/paywall/${this.paywallId}/start-checkout`, {
818
838
  method: "POST",
819
839
  headers: n,
820
840
  signal: t.signal,
821
841
  body: JSON.stringify({
822
842
  email: this.identity.email,
823
843
  priceId: Number(t.priceId),
824
- successUrl: u,
844
+ offerId: t.offerId,
845
+ successUrl: d,
825
846
  errorUrl: t.errorUrl,
826
- shopUrl: l,
847
+ shopUrl: f,
827
848
  productName: o?.checkout_product_name ?? void 0,
828
849
  trial_days: t.trialDays,
829
850
  ignoreActivePurchase: t.ignoreActivePurchase ? !0 : void 0,
830
- userMeta: this.identity.userId ? { userId: this.identity.userId } : void 0
851
+ userMeta: this.identity.userId ? { userId: this.identity.userId } : void 0,
852
+ localCurrency: c
831
853
  })
832
854
  }).then((h) => ({ url: h.checkoutUrl, acquiring: h.acquiring })).catch((h) => {
833
855
  throw h instanceof r && h.status === 409 && h.cause && typeof h.cause == "object" && h.cause.hasActivePurchase === !0 ? new r(
@@ -836,10 +858,10 @@ class ut {
836
858
  { status: 409, cause: h.cause }
837
859
  ) : h;
838
860
  });
839
- return this.inflightCheckouts.set(e, d), d.finally(() => {
840
- this.inflightCheckouts.get(e) === d && this.inflightCheckouts.delete(e);
861
+ return this.inflightCheckouts.set(e, l), l.finally(() => {
862
+ this.inflightCheckouts.get(e) === l && this.inflightCheckouts.delete(e);
841
863
  }).catch(() => {
842
- }), d;
864
+ }), l;
843
865
  }
844
866
  /**
845
867
  * URL Stripe/Paddle/Chargebee customer portal — место, где залогиненный
@@ -847,7 +869,9 @@ class ut {
847
869
  * инвойсы). Опен-флоу управляется host'ом:
848
870
  *
849
871
  * ```ts
850
- * const { url } = await billing.getCustomerPortalUrl();
872
+ * const { url } = await billing.getCustomerPortalUrl({
873
+ * returnUrl: 'https://your-app.com/account'
874
+ * });
851
875
  * window.open(url, '_blank');
852
876
  * ```
853
877
  *
@@ -865,9 +889,10 @@ class ut {
865
889
  );
866
890
  const e = {};
867
891
  this.apiKey && (e["X-Api-Key"] = this.apiKey);
868
- const s = this.auth && this.auth.getCachedSession() ? {} : {
892
+ const s = this.auth && this.auth.getCachedSession() ? { returnUrl: t.returnUrl } : {
869
893
  email: this.identity?.email,
870
- userMeta: this.identity?.userId ? { userId: this.identity.userId } : void 0
894
+ userMeta: this.identity?.userId ? { userId: this.identity.userId } : void 0,
895
+ returnUrl: t.returnUrl
871
896
  };
872
897
  return { url: (await this.api.request(
873
898
  `/api/v1/paywall/${this.paywallId}/get-customer-portal`,
@@ -886,43 +911,65 @@ class ut {
886
911
  * `/api/v1/paywall/[id]/user` без unstable_cache, потому что list для UI
887
912
  * должен быть свежим после cancel-а.
888
913
  *
889
- * Auth: Bearer обязателен (через AuthClient). Без Bearer — 401 от бэка,
890
- * пробрасываем как PaywallError('http_401'). Гость пустой список.
914
+ * Auth (два пути):
915
+ * - Bearer (через AuthClient) — user.id резолвится из сессии, identity
916
+ * в query игнорируется.
917
+ * - `apiKey` + `identity.email`/`identity.userId` — server-SDK путь для
918
+ * интеграций со своей авторизацией. Бэк проверяет, что identity линкована
919
+ * к этому пейволу (защита от cross-paywall lookup).
920
+ * Без auth и без apiKey+identity — `identity_required`.
891
921
  */
892
922
  async listPurchases(t = {}) {
893
- if (!this.auth)
923
+ const e = !!(this.identity?.email || this.identity?.userId);
924
+ if (!this.auth && !(this.apiKey && e))
894
925
  throw new r(
895
- "auth_required",
896
- "listPurchases requires AuthClient (Bearer auth)"
926
+ "identity_required",
927
+ "listPurchases requires AuthClient (Bearer) or apiKey + identity.email/userId"
897
928
  );
898
- return (await this.api.request(`/api/v1/paywall/${this.paywallId}/user`, {
929
+ const s = {};
930
+ this.apiKey && (s["X-Api-Key"] = this.apiKey);
931
+ const a = new URLSearchParams();
932
+ this.apiKey && this.identity?.email && a.set("email", this.identity.email), this.apiKey && this.identity?.userId && a.set("user_id", this.identity.userId);
933
+ const n = a.toString(), o = n ? `/api/v1/paywall/${this.paywallId}/user?${n}` : `/api/v1/paywall/${this.paywallId}/user`;
934
+ return (await this.api.request(o, {
899
935
  method: "GET",
936
+ headers: s,
900
937
  signal: t.signal
901
938
  })).purchases ?? [];
902
939
  }
903
940
  /**
904
- * Отменить подписку. Бэк проверит что subscription принадлежит auth-юзеру
905
- * и сделает cancel у acquiring'а (Stripe/Paddle/Chargebee). По умолчанию
906
- * cancel в конце текущего периода — юзер сохраняет access до renewal date'ы.
941
+ * Отменить подписку. Бэк проверит, что subscription принадлежит юзеру
942
+ * (Bearer-путь из сессии; apiKey-путь — из identity), и сделает cancel у
943
+ * acquiring'а (Stripe/Paddle/Chargebee/Overpay). По умолчанию cancel в
944
+ * конце текущего периода — юзер сохраняет access до renewal date'ы.
907
945
  *
908
- * `reason` обязательна (валидация на бэке). Удобно собрать через select
909
- * причин в host-UI, как в legacy customer portal'е.
946
+ * `reason` обязательна (валидация на бэке).
910
947
  *
911
- * Auth: Bearer обязателен.
948
+ * Auth (два пути):
949
+ * - Bearer (через AuthClient) — стандартный путь для UI customer-portal'a.
950
+ * - `apiKey` + `identity.email`/`identity.userId` — для self-service UI на
951
+ * бэке клиента со своей авторизацией. Бэк дополнительно фильтрует
952
+ * subscription по paywall_id, чтобы owner пейвола A не отменил подписку
953
+ * пейвола B.
912
954
  */
913
955
  async cancelSubscription(t) {
914
- if (!this.auth)
956
+ const e = !!(this.identity?.email || this.identity?.userId);
957
+ if (!this.auth && !(this.apiKey && e))
915
958
  throw new r(
916
- "auth_required",
917
- "cancelSubscription requires AuthClient (Bearer auth)"
959
+ "identity_required",
960
+ "cancelSubscription requires AuthClient (Bearer) or apiKey + identity.email/userId"
918
961
  );
919
- return this.api.request("/api/paywall/cancel-subscription", {
962
+ const s = {};
963
+ this.apiKey && (s["X-Api-Key"] = this.apiKey);
964
+ const a = {
965
+ subscriptionId: t.subscriptionId,
966
+ paywallId: this.paywallId,
967
+ cancellationReason: t.reason
968
+ };
969
+ 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", {
920
970
  method: "POST",
921
- body: JSON.stringify({
922
- subscriptionId: t.subscriptionId,
923
- paywallId: this.paywallId,
924
- cancellationReason: t.reason
925
- }),
971
+ headers: s,
972
+ body: JSON.stringify(a),
926
973
  signal: t.signal
927
974
  });
928
975
  }
@@ -955,80 +1002,108 @@ class ut {
955
1002
  });
956
1003
  }
957
1004
  }
958
- function T(a) {
959
- return { email: a.email, userId: a.id };
1005
+ function T(i) {
1006
+ return { email: i.email, userId: i.id };
1007
+ }
1008
+ function W(i, t) {
1009
+ return i === t ? !0 : !i || !t ? !1 : i.email === t.email && i.userId === t.userId && i.anonymousId === t.anonymousId;
960
1010
  }
961
- function Y(a, t) {
962
- return a === t ? !0 : !a || !t ? !1 : a.email === t.email && a.userId === t.userId && a.anonymousId === t.anonymousId;
1011
+ function U(i) {
1012
+ if (!i) return null;
1013
+ const t = i.trim();
1014
+ if (!t) return null;
1015
+ try {
1016
+ return new URL(t.includes("://") ? t : `https://${t}`).origin;
1017
+ } catch {
1018
+ return null;
1019
+ }
1020
+ }
1021
+ function Y(i, t) {
1022
+ const e = U(i);
1023
+ if (!(!e || U(t) === e))
1024
+ throw new r(
1025
+ "invalid_config",
1026
+ `apiOrigin mismatch: SDK initialized with "${t}" but paywall is configured with custom_domain "${i}". Use the custom_domain from the platform paywall settings.`
1027
+ );
963
1028
  }
964
- function U(a, t) {
1029
+ function O(i, t) {
965
1030
  return {
966
1031
  type: "modal",
967
1032
  blocks: [
968
- { type: "heading", text: a.name || "Upgrade", level: 1 },
1033
+ // offer_banner НЕ в default layout PaywallRoot рендерит его как
1034
+ // top-tab над dialog'ом (rounded-top, negative margin), за пределами
1035
+ // scrollable area. Блок остаётся в registry для opt-in inline-вариантa.
1036
+ { type: "heading", text: i.name || "Upgrade", level: 1 },
969
1037
  { type: "price_grid", priceIds: t.map((e) => e.id) },
970
- { type: "cta_button", label: "Continue", action: "checkout" }
1038
+ { type: "cta_button", action: "checkout" },
1039
+ { type: "guarantee_badge" },
1040
+ { type: "current_session" }
971
1041
  ]
972
1042
  };
973
1043
  }
974
- function L(a) {
975
- const t = a.locales;
1044
+ function N(i) {
1045
+ const t = i.locales;
976
1046
  if (!t) return null;
977
1047
  const e = [];
978
1048
  if (typeof navigator < "u") {
979
1049
  navigator.language && e.push(navigator.language);
980
- const i = navigator.language?.split("-")[0];
981
- i && i !== navigator.language && e.push(i);
1050
+ const a = navigator.language?.split("-")[0];
1051
+ a && a !== navigator.language && e.push(a);
982
1052
  }
983
- const s = a.settings.locale_default;
1053
+ const s = i.settings.locale_default;
984
1054
  s && e.push(s);
985
- for (const i of e)
986
- if (i && Object.prototype.hasOwnProperty.call(t, i)) return i;
1055
+ for (const a of e)
1056
+ if (a && Object.prototype.hasOwnProperty.call(t, a)) return a;
987
1057
  return null;
988
1058
  }
989
- function g(a) {
990
- const t = L(a);
1059
+ function w(i) {
1060
+ const t = N(i);
991
1061
  if (!t) return;
992
- const e = a.locales?.[t];
993
- e && (e.layout && (a.layout = e.layout), e.prices && (a.prices = a.prices.map((s) => {
994
- const i = e.prices?.[s.id];
995
- if (!i) return s;
1062
+ const e = i.locales?.[t];
1063
+ e && (e.layout && (i.layout = e.layout), e.prices && (i.prices = i.prices.map((s) => {
1064
+ const a = e.prices?.[s.id];
1065
+ if (!a) return s;
996
1066
  const n = { ...s };
997
- return "label" in i && (n.label = i.label ?? null), "description" in i && (n.description = i.description ?? null), n;
1067
+ return "label" in a && (n.label = a.label ?? null), "description" in a && (n.description = a.description ?? null), n;
998
1068
  })));
999
1069
  }
1000
- function P(a) {
1001
- const t = new Uint8Array(a), e = typeof globalThis < "u" ? globalThis.crypto : void 0;
1070
+ function K(i) {
1071
+ const t = new Uint8Array(i), e = typeof globalThis < "u" ? globalThis.crypto : void 0;
1002
1072
  if (e && typeof e.getRandomValues == "function")
1003
1073
  e.getRandomValues(t);
1004
1074
  else
1005
- for (let s = 0; s < a; s++) t[s] = Math.floor(Math.random() * 256);
1075
+ for (let s = 0; s < i; s++) t[s] = Math.floor(Math.random() * 256);
1006
1076
  return t;
1007
1077
  }
1008
- function v(a) {
1078
+ function S(i) {
1009
1079
  let t = "";
1010
- for (let e = 0; e < a.length; e++) t += String.fromCharCode(a[e]);
1080
+ for (let e = 0; e < i.length; e++) t += String.fromCharCode(i[e]);
1011
1081
  return btoa(t).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1012
1082
  }
1013
1083
  function Z() {
1014
- return v(P(64));
1084
+ return S(K(64));
1015
1085
  }
1016
- async function tt(a) {
1017
- const t = new TextEncoder().encode(a), e = globalThis.crypto;
1086
+ async function tt(i) {
1087
+ const t = new TextEncoder().encode(i), e = globalThis.crypto;
1018
1088
  if (!e?.subtle?.digest)
1019
1089
  throw new Error("crypto.subtle is required for PKCE");
1020
1090
  const s = await e.subtle.digest("SHA-256", t);
1021
- return v(new Uint8Array(s));
1091
+ return S(new Uint8Array(s));
1022
1092
  }
1023
1093
  function et() {
1024
- return v(P(16));
1094
+ return S(K(16));
1025
1095
  }
1026
- const st = "https://appbox.space", it = 6e4, at = 600 * 1e3;
1027
- class dt {
1096
+ const st = 6e4, it = 600 * 1e3;
1097
+ class pt {
1028
1098
  constructor(t) {
1029
1099
  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)
1030
1100
  throw new r("invalid_config", "paywallId is required");
1031
- this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin ?? st, this.storage = C(t.storage), this.api = new O({
1101
+ if (!t.apiOrigin)
1102
+ throw new r(
1103
+ "invalid_config",
1104
+ "apiOrigin is required. Pass the paywall custom_domain configured in the platform."
1105
+ );
1106
+ this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin, this.storage = L(t.storage), this.api = new E({
1032
1107
  apiOrigin: this.apiOrigin,
1033
1108
  paywallId: t.paywallId,
1034
1109
  fetch: t.fetch
@@ -1054,14 +1129,15 @@ class dt {
1054
1129
  async applyExternalSession(t) {
1055
1130
  if (!this.destroyed && (await this.hydrated, !this.destroyed)) {
1056
1131
  if (t == null) {
1057
- this.session && this.setSession(null, { skipPersist: !0 });
1132
+ this.session && this.setSession(null, { skipPersist: !0, event: "SIGNED_OUT" });
1058
1133
  return;
1059
1134
  }
1060
1135
  try {
1061
1136
  const e = JSON.parse(t);
1062
1137
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1063
1138
  return;
1064
- this.setSession(e, { skipPersist: !0 });
1139
+ const s = !this.session || this.session.user.id !== e.user.id ? "SIGNED_IN" : "TOKEN_REFRESHED";
1140
+ this.setSession(e, { skipPersist: !0, event: s });
1065
1141
  } catch {
1066
1142
  }
1067
1143
  }
@@ -1105,7 +1181,7 @@ class dt {
1105
1181
  await this.hydrated;
1106
1182
  const e = await this.readVisitorId(), s = {};
1107
1183
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1108
- const i = await this.api.request(
1184
+ const a = await this.api.request(
1109
1185
  `/api/v1/paywall/${this.paywallId}/auth/email/signin`,
1110
1186
  {
1111
1187
  method: "POST",
@@ -1117,8 +1193,8 @@ class dt {
1117
1193
  user_meta: t.userMeta
1118
1194
  })
1119
1195
  }
1120
- ), n = this.toSession(i, i.user);
1121
- return this.setSession(n), n;
1196
+ ), n = this.toSession(a, a.user);
1197
+ return this.setSession(n, { event: "SIGNED_IN" }), this.recordLastLogin("email", t.email), n;
1122
1198
  }
1123
1199
  /**
1124
1200
  * Signup. Если в Supabase включён email confirm — сервер возвращает
@@ -1130,7 +1206,7 @@ class dt {
1130
1206
  await this.hydrated;
1131
1207
  const e = await this.readVisitorId(), s = {};
1132
1208
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1133
- const i = await this.api.request(
1209
+ const a = await this.api.request(
1134
1210
  `/api/v1/paywall/${this.paywallId}/auth/email/signup`,
1135
1211
  {
1136
1212
  method: "POST",
@@ -1143,10 +1219,10 @@ class dt {
1143
1219
  })
1144
1220
  }
1145
1221
  );
1146
- if (i.status === "confirmation_required")
1147
- return { kind: "confirmation_required", user: i.user };
1148
- const n = this.toSession(i, i.user);
1149
- return this.setSession(n), { kind: "signed_in", session: n };
1222
+ if (a.status === "confirmation_required")
1223
+ return this.recordLastLogin("email", t.email), { kind: "confirmation_required", user: a.user };
1224
+ const n = this.toSession(a, a.user);
1225
+ return this.setSession(n, { event: "SIGNED_IN" }), this.recordLastLogin("email", t.email), { kind: "signed_in", session: n };
1150
1226
  }
1151
1227
  /**
1152
1228
  * Повторная отправка confirmation-email после signUp с включённым
@@ -1209,8 +1285,8 @@ class dt {
1209
1285
  user_meta: t.userMeta
1210
1286
  })
1211
1287
  }
1212
- ), i = this.toSession(s, s.user);
1213
- return this.setSession(i), i;
1288
+ ), a = this.toSession(s, s.user), n = t.type === "recovery" ? "PASSWORD_RECOVERY" : "SIGNED_IN";
1289
+ return this.setSession(a, { event: n }), a;
1214
1290
  }
1215
1291
  /**
1216
1292
  * Запрос recovery email. Бэк всегда ok, чтобы не палить enumeration.
@@ -1269,10 +1345,8 @@ class dt {
1269
1345
  * когда сервер начнёт возвращать challenge_required в риск-сценариях,
1270
1346
  * SDK сможет передать proof-of-something обратно без breaking change.
1271
1347
  *
1272
- * `forceCaptcha: true` пропускает шаги 1-2 и сразу делает /signin (создаёт
1273
- * нового anon-юзера). Используется в switch-account flow. Имя поля исторически
1274
- * остаётся `forceCaptcha`, хотя капчи там больше нет — менять имя ломает
1275
- * host-сигнатуру; смысл «принудительно новая anon-сессия» сохранён.
1348
+ * `forceNewAnon: true` пропускает шаги 1-2 и сразу делает /signin (создаёт
1349
+ * нового anon-юзера). Используется в switch-account flow.
1276
1350
  *
1277
1351
  * Параллельные вызовы дедуплицируются через `inflightAnonSignin` — два
1278
1352
  * click'а на «Войти как гость» не создадут двух anon-юзеров (два /signup =
@@ -1281,9 +1355,9 @@ class dt {
1281
1355
  async signInAnonymously(t = {}) {
1282
1356
  if (this.inflightAnonSignin) return this.inflightAnonSignin;
1283
1357
  this.inflightAnonSignin = (async () => {
1284
- if (await this.hydrated, !t.forceCaptcha && this.session?.user.is_anonymous === !0)
1358
+ if (await this.hydrated, !t.forceNewAnon && this.session?.user.is_anonymous === !0)
1285
1359
  return this.session;
1286
- if (!t.forceCaptcha) {
1360
+ if (!t.forceNewAnon) {
1287
1361
  const o = await this.resumeAnonymous();
1288
1362
  if (o) return o;
1289
1363
  }
@@ -1297,12 +1371,12 @@ class dt {
1297
1371
  user_meta: t.userMeta
1298
1372
  })
1299
1373
  }
1300
- ), i = {
1374
+ ), a = {
1301
1375
  ...s.user,
1302
1376
  email: s.user.email ?? null,
1303
1377
  is_anonymous: !0
1304
- }, n = this.toSession(s, i);
1305
- return this.setSession(n), await this.writeAnonRefreshToken(n.refresh_token), n;
1378
+ }, n = this.toSession(s, a);
1379
+ return this.setSession(n, { event: "SIGNED_IN" }), await this.writeAnonRefreshToken(n.refresh_token), n;
1306
1380
  })();
1307
1381
  try {
1308
1382
  return await this.inflightAnonSignin;
@@ -1323,8 +1397,8 @@ class dt {
1323
1397
  const e = await this.api.request(
1324
1398
  `/api/v1/paywall/${this.paywallId}/auth/refresh`,
1325
1399
  { method: "POST", body: JSON.stringify({ refresh_token: t }) }
1326
- ), s = this.session?.user.is_anonymous === !0 ? this.session.user : { id: "", email: null, is_anonymous: !0 }, i = this.toSession(e, s);
1327
- return this.setSession(i), await this.writeAnonRefreshToken(i.refresh_token), i;
1400
+ ), s = this.session?.user.is_anonymous === !0 ? this.session.user : { id: "", email: null, is_anonymous: !0 }, a = this.toSession(e, s);
1401
+ return this.setSession(a, { event: "SIGNED_IN" }), await this.writeAnonRefreshToken(a.refresh_token), a;
1328
1402
  } catch (e) {
1329
1403
  if (e instanceof r && e.status === 401)
1330
1404
  return await this.clearAnonRefreshToken(), null;
@@ -1361,7 +1435,7 @@ class dt {
1361
1435
  Authorization: `Bearer ${e}`
1362
1436
  };
1363
1437
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1364
- const i = await this.api.request(
1438
+ const a = await this.api.request(
1365
1439
  `/api/v1/paywall/${this.paywallId}/auth/anonymous/upgrade`,
1366
1440
  {
1367
1441
  method: "POST",
@@ -1373,8 +1447,8 @@ class dt {
1373
1447
  })
1374
1448
  }
1375
1449
  );
1376
- if (i.status === "confirmation_required")
1377
- return { kind: "confirmation_required", email: i.email };
1450
+ if (a.status === "confirmation_required")
1451
+ return { kind: "confirmation_required", email: a.email };
1378
1452
  const n = this.session;
1379
1453
  if (!n)
1380
1454
  throw new r(
@@ -1383,11 +1457,11 @@ class dt {
1383
1457
  );
1384
1458
  const o = {
1385
1459
  ...n.user,
1386
- id: i.user.id,
1387
- email: i.user.email,
1388
- is_anonymous: i.user.is_anonymous ?? !1
1389
- }, u = { ...n, user: o };
1390
- return this.setSession(u), await this.clearAnonRefreshToken(), { kind: "updated", session: u };
1460
+ id: a.user.id,
1461
+ email: a.user.email,
1462
+ is_anonymous: a.user.is_anonymous ?? !1
1463
+ }, d = { ...n, user: o };
1464
+ return this.setSession(d, { event: "USER_UPDATED" }), await this.clearAnonRefreshToken(), { kind: "updated", session: d };
1391
1465
  }
1392
1466
  /**
1393
1467
  * OAuth signin через popup с PKCE. Жизненный цикл:
@@ -1416,14 +1490,14 @@ class dt {
1416
1490
  provider: t.provider,
1417
1491
  scopes: t.scopes,
1418
1492
  userMeta: t.userMeta
1419
- }), i = this.openPopup(e, `pw-oauth-${s}`);
1420
- if (!i)
1493
+ }), a = this.openPopup(e, `pw-oauth-${s}`);
1494
+ if (!a)
1421
1495
  throw this.oauthFlows.delete(s), new r(
1422
1496
  "popup_blocked",
1423
1497
  "browser blocked auth popup — call from a user gesture"
1424
1498
  );
1425
1499
  t.onPopupOpened?.();
1426
- const n = await ot(i, s);
1500
+ const n = await rt(a, s);
1427
1501
  if (this.destroyed)
1428
1502
  throw this.oauthFlows.delete(s), new r("aborted", "AuthClient destroyed mid-flow");
1429
1503
  return this.completeOAuthFlow({ state: s, code: n });
@@ -1444,9 +1518,9 @@ class dt {
1444
1518
  */
1445
1519
  async startOAuthFlow(t) {
1446
1520
  await this.hydrated, this.gcOAuthFlows();
1447
- const e = Z(), s = await tt(e), i = et(), n = {}, o = await this.getAccessToken().catch(() => null);
1521
+ const e = Z(), s = await tt(e), a = et(), n = {}, o = await this.getAccessToken().catch(() => null);
1448
1522
  o && (n.Authorization = `Bearer ${o}`);
1449
- const { authorize_url: u } = await this.api.request(
1523
+ const { authorize_url: d } = await this.api.request(
1450
1524
  `/api/v1/paywall/${this.paywallId}/auth/oauth/init`,
1451
1525
  {
1452
1526
  method: "POST",
@@ -1459,11 +1533,12 @@ class dt {
1459
1533
  })
1460
1534
  }
1461
1535
  );
1462
- return this.oauthFlows.set(i, {
1536
+ return this.oauthFlows.set(a, {
1463
1537
  verifier: e,
1464
1538
  userMeta: t.userMeta,
1539
+ provider: t.provider,
1465
1540
  startedAt: Date.now()
1466
- }), { authorize_url: u, state: i };
1541
+ }), this.recordLastLoginMethod(t.provider), { authorize_url: d, state: a };
1467
1542
  }
1468
1543
  /**
1469
1544
  * Шаг 2 OAuth split-API: обменивает code (полученный из popup) на session,
@@ -1483,7 +1558,7 @@ class dt {
1483
1558
  "OAuth flow not found — start with startOAuthFlow first or check TTL"
1484
1559
  );
1485
1560
  this.oauthFlows.delete(t.state);
1486
- const s = await this.readVisitorId(), i = await this.api.request(
1561
+ const s = await this.readVisitorId(), a = await this.api.request(
1487
1562
  `/api/v1/paywall/${this.paywallId}/auth/oauth/exchange`,
1488
1563
  {
1489
1564
  method: "POST",
@@ -1497,11 +1572,11 @@ class dt {
1497
1572
  );
1498
1573
  if (this.destroyed)
1499
1574
  throw new r("aborted", "AuthClient destroyed mid-flow");
1500
- const n = this.toSession(i, i.user);
1501
- return this.setSession(n), n;
1575
+ const n = this.toSession(a, a.user);
1576
+ return this.setSession(n, { event: "SIGNED_IN" }), n.user.email && this.recordLastLoginEmail(n.user.email), n;
1502
1577
  }
1503
1578
  gcOAuthFlows() {
1504
- const t = Date.now() - at;
1579
+ const t = Date.now() - it;
1505
1580
  for (const [e, s] of this.oauthFlows)
1506
1581
  s.startedAt < t && this.oauthFlows.delete(e);
1507
1582
  }
@@ -1526,11 +1601,11 @@ class dt {
1526
1601
  method: "POST",
1527
1602
  body: JSON.stringify({ refresh_token: t })
1528
1603
  }
1529
- ), i = this.toSession(s, e);
1530
- return this.setSession(i), e.is_anonymous === !0 && await this.writeAnonRefreshToken(i.refresh_token), i;
1604
+ ), a = this.toSession(s, e);
1605
+ return this.setSession(a, { event: "TOKEN_REFRESHED" }), e.is_anonymous === !0 && await this.writeAnonRefreshToken(a.refresh_token), a;
1531
1606
  } catch (s) {
1532
1607
  if (s instanceof r && s.status === 401)
1533
- return e.is_anonymous === !0 && await this.clearAnonRefreshToken(), this.setSession(null), null;
1608
+ return e.is_anonymous === !0 && await this.clearAnonRefreshToken(), this.setSession(null, { event: "SIGNED_OUT" }), null;
1534
1609
  throw s;
1535
1610
  } finally {
1536
1611
  this.inflightRefresh = null;
@@ -1563,7 +1638,7 @@ class dt {
1563
1638
  method: "POST",
1564
1639
  headers: { Authorization: `Bearer ${t}` }
1565
1640
  }
1566
- ), this.setSession(null);
1641
+ ), this.setSession(null, { event: "SIGNED_OUT" });
1567
1642
  }
1568
1643
  /**
1569
1644
  * Signout: чистит локальную session СРАЗУ (UX — мгновенный logout без
@@ -1583,7 +1658,7 @@ class dt {
1583
1658
  async signOut(t = {}) {
1584
1659
  await this.hydrated;
1585
1660
  const e = this.session?.access_token, s = this.session?.user.is_anonymous === !0;
1586
- if (this.setSession(null), t.forgetAnonymous && await this.clearAnonRefreshToken(), !!e && !(s && !t.forgetAnonymous))
1661
+ if (this.setSession(null, { event: "SIGNED_OUT" }), t.forgetAnonymous && await this.clearAnonRefreshToken(), !!e && !(s && !t.forgetAnonymous))
1587
1662
  try {
1588
1663
  await this.api.request(
1589
1664
  `/api/v1/paywall/${this.paywallId}/auth/signout`,
@@ -1597,22 +1672,35 @@ class dt {
1597
1672
  }
1598
1673
  /**
1599
1674
  * Подписка на изменения session: signin/signup/refresh/signOut/expired-401.
1600
- * Колбек вызывается с текущим snapshot через microtask (если session есть)
1601
- * + на каждое реальное изменение. Возвращает unsubscribe.
1675
+ *
1676
+ * Гарантированный контракт: ПЕРВЫЙ callback каждому subscriber'у — всегда
1677
+ * `event = 'INITIAL_SESSION'`, дёргается асинхронно после resolve hydrate'а
1678
+ * (даже если session=null — listener получает explicit «нет сессии», а не
1679
+ * молчание). Все последующие callback'и — реальные переходы с конкретным
1680
+ * event'ом (SIGNED_IN / SIGNED_OUT / TOKEN_REFRESHED / USER_UPDATED /
1681
+ * PASSWORD_RECOVERY).
1682
+ *
1683
+ * Это позволяет listener'у безопасно делать «only on real signin» побочные
1684
+ * эффекты (force refetch balances и т.п.) через `event === 'SIGNED_IN'`,
1685
+ * не путая их с восстановлением из storage.
1686
+ *
1687
+ * Возвращает unsubscribe.
1602
1688
  */
1603
1689
  onAuthChange(t) {
1604
- if (this.listeners.add(t), this.session) {
1690
+ return this.listeners.add(t), this.hydrated.then(() => {
1691
+ if (this.destroyed || !this.listeners.has(t)) return;
1605
1692
  const e = this.session;
1606
- queueMicrotask(() => {
1607
- this.listeners.has(t) && t(e);
1608
- });
1609
- }
1610
- return () => {
1693
+ try {
1694
+ t("INITIAL_SESSION", e);
1695
+ } catch (s) {
1696
+ console.warn("[paywall] onAuthChange INITIAL_SESSION threw", s);
1697
+ }
1698
+ }), () => {
1611
1699
  this.listeners.delete(t);
1612
1700
  };
1613
1701
  }
1614
1702
  isFresh(t) {
1615
- return t.expires_at - Date.now() > it;
1703
+ return t.expires_at - Date.now() > st;
1616
1704
  }
1617
1705
  toSession(t, e) {
1618
1706
  const s = t.expires_at != null ? t.expires_at * 1e3 : Date.now() + t.expires_in * 1e3;
@@ -1623,21 +1711,21 @@ class dt {
1623
1711
  user: e
1624
1712
  };
1625
1713
  }
1626
- setSession(t, e = {}) {
1714
+ setSession(t, e) {
1627
1715
  if (this.destroyed) return;
1628
1716
  const s = this.session;
1629
- this.session = t, e.skipPersist || this.persist(), ht(s, t) || this.emit();
1717
+ this.session = t, e.skipPersist || this.persist(), ht(s, t) || this.emit(e.event);
1630
1718
  }
1631
- emit() {
1632
- for (const t of this.listeners)
1719
+ emit(t) {
1720
+ for (const e of this.listeners)
1633
1721
  try {
1634
- t(this.session);
1635
- } catch (e) {
1636
- console.warn("[paywall] onAuthChange listener threw", e);
1722
+ e(t, this.session);
1723
+ } catch (s) {
1724
+ console.warn("[paywall] onAuthChange listener threw", s);
1637
1725
  }
1638
1726
  }
1639
1727
  storageKey() {
1640
- return y.authSession(this.paywallId);
1728
+ return u.authSession(this.paywallId);
1641
1729
  }
1642
1730
  async hydrate() {
1643
1731
  try {
@@ -1646,7 +1734,7 @@ class dt {
1646
1734
  const e = JSON.parse(t);
1647
1735
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1648
1736
  return;
1649
- this.session = e, this.emit();
1737
+ this.session = e;
1650
1738
  } catch {
1651
1739
  }
1652
1740
  }
@@ -1660,7 +1748,7 @@ class dt {
1660
1748
  const e = JSON.parse(t);
1661
1749
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1662
1750
  return;
1663
- this.setSession(e, { skipPersist: !0 });
1751
+ this.setSession(e, { skipPersist: !0, event: "SIGNED_IN" });
1664
1752
  } catch {
1665
1753
  }
1666
1754
  }
@@ -1691,7 +1779,7 @@ class dt {
1691
1779
  }
1692
1780
  async readAnonRefreshToken() {
1693
1781
  try {
1694
- const t = await this.storage.getItem(y.anonRefreshToken(this.paywallId));
1782
+ const t = await this.storage.getItem(u.anonRefreshToken(this.paywallId));
1695
1783
  return typeof t == "string" && t.length > 0 ? t : null;
1696
1784
  } catch {
1697
1785
  return null;
@@ -1700,7 +1788,7 @@ class dt {
1700
1788
  async writeAnonRefreshToken(t) {
1701
1789
  try {
1702
1790
  await this.storage.setItem(
1703
- y.anonRefreshToken(this.paywallId),
1791
+ u.anonRefreshToken(this.paywallId),
1704
1792
  t
1705
1793
  );
1706
1794
  } catch {
@@ -1709,11 +1797,41 @@ class dt {
1709
1797
  async clearAnonRefreshToken() {
1710
1798
  try {
1711
1799
  await this.storage.removeItem(
1712
- y.anonRefreshToken(this.paywallId)
1800
+ u.anonRefreshToken(this.paywallId)
1713
1801
  );
1714
1802
  } catch {
1715
1803
  }
1716
1804
  }
1805
+ /**
1806
+ * Last-used auth method + email — для UI бейджа "Last used" и pre-fill'а
1807
+ * email-инпута. Storage paywall-scoped, поэтому переключение между
1808
+ * пейволами на одном host'е не пересекает данные. Чтение всегда возвращает
1809
+ * объект — отсутствующие поля = null. */
1810
+ async getLastLogin() {
1811
+ try {
1812
+ const [t, e] = await Promise.all([
1813
+ this.storage.getItem(u.lastLoginMethod(this.paywallId)),
1814
+ this.storage.getItem(u.lastLoginEmail(this.paywallId))
1815
+ ]);
1816
+ return !t || !ot(t) ? null : { method: t, email: typeof e == "string" && e ? e : null };
1817
+ } catch {
1818
+ return null;
1819
+ }
1820
+ }
1821
+ /** Запись method и email атомарно (для email/password flows — оба известны
1822
+ * на момент signin/signup'а). OAuth-flows используют раздельные
1823
+ * recordLastLoginMethod (до popup) и recordLastLoginEmail (после exchange). */
1824
+ recordLastLogin(t, e) {
1825
+ this.recordLastLoginMethod(t), e && this.recordLastLoginEmail(e);
1826
+ }
1827
+ recordLastLoginMethod(t) {
1828
+ this.storage.setItem(u.lastLoginMethod(this.paywallId), t).catch(() => {
1829
+ });
1830
+ }
1831
+ recordLastLoginEmail(t) {
1832
+ this.storage.setItem(u.lastLoginEmail(this.paywallId), t).catch(() => {
1833
+ });
1834
+ }
1717
1835
  /**
1718
1836
  * Читает stable visitor_id из storage если он там уже есть. НЕ генерит:
1719
1837
  * AuthClient может быть инстанцирован раньше BillingClient, а синтетический
@@ -1723,71 +1841,74 @@ class dt {
1723
1841
  */
1724
1842
  async readVisitorId() {
1725
1843
  try {
1726
- const t = await this.storage.getItem(y.visitorId);
1844
+ const t = await this.storage.getItem(u.visitorId);
1727
1845
  return typeof t == "string" && t.length >= 16 ? t : void 0;
1728
1846
  } catch {
1729
1847
  return;
1730
1848
  }
1731
1849
  }
1732
1850
  }
1733
- const nt = 5 * 6e4, rt = 500;
1734
- function ot(a, t) {
1851
+ const at = 5 * 6e4, nt = 500;
1852
+ function rt(i, t) {
1735
1853
  return new Promise((e, s) => {
1736
- let i = !1;
1854
+ let a = !1;
1737
1855
  const n = () => {
1738
- i = !0, window.removeEventListener("message", o), clearInterval(u), clearTimeout(l);
1739
- }, o = (d) => {
1740
- if (i) return;
1741
- const h = d.data;
1742
- if (!(!h || h.type !== "pw-oauth") && h.messageId === t) {
1743
- if (h.status === "success" && h.code) {
1856
+ a = !0, window.removeEventListener("message", o), clearInterval(d), clearTimeout(f);
1857
+ }, o = (p) => {
1858
+ if (a) return;
1859
+ const c = p.data;
1860
+ if (!(!c || c.type !== "pw-oauth") && c.messageId === t) {
1861
+ if (c.status === "success" && c.code) {
1744
1862
  n();
1745
1863
  try {
1746
- a.close();
1864
+ i.close();
1747
1865
  } catch {
1748
1866
  }
1749
- e(h.code);
1750
- } else if (h.status === "error") {
1867
+ e(c.code);
1868
+ } else if (c.status === "error") {
1751
1869
  n();
1752
1870
  try {
1753
- a.close();
1871
+ i.close();
1754
1872
  } catch {
1755
1873
  }
1756
1874
  s(
1757
1875
  new r(
1758
1876
  "oauth_failed",
1759
- h.description || h.error || "OAuth provider returned error"
1877
+ c.description || c.error || "OAuth provider returned error"
1760
1878
  )
1761
1879
  );
1762
1880
  }
1763
1881
  }
1764
- }, u = setInterval(() => {
1765
- if (i) return;
1766
- let d;
1882
+ }, d = setInterval(() => {
1883
+ if (a) return;
1884
+ let p;
1767
1885
  try {
1768
- d = a.closed;
1886
+ p = i.closed;
1769
1887
  } catch {
1770
1888
  return;
1771
1889
  }
1772
- d && (n(), s(new r("oauth_cancelled", "auth popup was closed")));
1773
- }, rt), l = setTimeout(() => {
1774
- if (!i) {
1890
+ p && (n(), s(new r("oauth_cancelled", "auth popup was closed")));
1891
+ }, nt), f = setTimeout(() => {
1892
+ if (!a) {
1775
1893
  n();
1776
1894
  try {
1777
- a.close();
1895
+ i.close();
1778
1896
  } catch {
1779
1897
  }
1780
1898
  s(new r("oauth_timeout", "OAuth flow timed out"));
1781
1899
  }
1782
- }, nt);
1900
+ }, at);
1783
1901
  window.addEventListener("message", o);
1784
1902
  });
1785
1903
  }
1786
- function ht(a, t) {
1787
- 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;
1904
+ function ot(i) {
1905
+ return i === "google" || i === "apple" || i === "github" || i === "facebook" || i === "email";
1906
+ }
1907
+ function ht(i, t) {
1908
+ 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;
1788
1909
  }
1789
1910
  const ct = 1500, lt = 20, k = 200;
1790
- class ft {
1911
+ class gt {
1791
1912
  constructor(t) {
1792
1913
  this.buffer = [], this.flushTimer = null, this.destroyed = !1, this.unloadHandler = null, this.visibilityHandler = null, this.opts = t, this.isEnabled() && this.attachUnloadHandlers();
1793
1914
  }
@@ -1817,7 +1938,7 @@ class ft {
1817
1938
  const t = this.buffer;
1818
1939
  this.buffer = [];
1819
1940
  try {
1820
- 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);
1941
+ 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);
1821
1942
  if (!n) return;
1822
1943
  await n(this.opts.endpoint, {
1823
1944
  method: "POST",
@@ -1825,7 +1946,7 @@ class ft {
1825
1946
  keepalive: !0,
1826
1947
  // если страница закроется в этот момент — браузер всё равно дотянет
1827
1948
  headers: this.buildHeaders(e, s),
1828
- body: i
1949
+ body: a
1829
1950
  });
1830
1951
  } catch {
1831
1952
  }
@@ -1845,7 +1966,7 @@ class ft {
1845
1966
  this.buffer.unshift(...t), this.flush();
1846
1967
  return;
1847
1968
  }
1848
- const i = JSON.stringify({
1969
+ const a = JSON.stringify({
1849
1970
  events: t,
1850
1971
  // body-level дубликаты для beacon-flow, читаются сервером как fallback.
1851
1972
  visitor_id: e,
@@ -1859,7 +1980,7 @@ class ft {
1859
1980
  return;
1860
1981
  }
1861
1982
  try {
1862
- n(this.opts.endpoint, i) || (this.buffer.unshift(...t), this.flush());
1983
+ n(this.opts.endpoint, a) || (this.buffer.unshift(...t), this.flush());
1863
1984
  } catch {
1864
1985
  this.buffer.unshift(...t), this.flush();
1865
1986
  }
@@ -1885,18 +2006,64 @@ class ft {
1885
2006
  this.destroyed || (this.destroyed = !0, this.flushTimer && (clearTimeout(this.flushTimer), this.flushTimer = null), this.flush(), this.detachUnloadHandlers());
1886
2007
  }
1887
2008
  }
2009
+ function ut(i) {
2010
+ return `pw-offer-${i}-start`;
2011
+ }
2012
+ function wt(i, t) {
2013
+ if (!i || i.length === 0) return null;
2014
+ const e = i.find(
2015
+ (a) => a.price_id === t && (a.discount_percent ?? 0) > 0
2016
+ );
2017
+ return e || (i.find(
2018
+ (a) => a.price_id == null && (a.discount_percent ?? 0) > 0
2019
+ ) ?? null);
2020
+ }
2021
+ function mt(i, t = {}) {
2022
+ const e = i.discount_percent ?? 0;
2023
+ if (e <= 0) return null;
2024
+ const s = t.now ?? Date.now(), a = dt(i, t.readStart), n = ft(i, a), o = a !== null ? Math.max(0, a - s) : null;
2025
+ return a !== null && a <= s ? null : { offer: i, discountPercent: e, remainingMs: o, totalMs: n, expiresAt: a };
2026
+ }
2027
+ function dt(i, t) {
2028
+ if (i.expires_at) {
2029
+ const e = Date.parse(i.expires_at);
2030
+ return Number.isFinite(e) ? e : null;
2031
+ }
2032
+ if (i.duration_minutes && i.duration_minutes > 0 && t) {
2033
+ const e = t(i.id);
2034
+ if (!e) return null;
2035
+ const s = Date.parse(e);
2036
+ return Number.isFinite(s) ? s + i.duration_minutes * 6e4 : null;
2037
+ }
2038
+ return null;
2039
+ }
2040
+ function ft(i, t) {
2041
+ return i.duration_minutes && i.duration_minutes > 0 ? i.duration_minutes * 6e4 : t !== null ? t - Date.now() : null;
2042
+ }
2043
+ function It(i) {
2044
+ if (typeof window > "u") return null;
2045
+ try {
2046
+ return window.localStorage.getItem(ut(i));
2047
+ } catch {
2048
+ return null;
2049
+ }
2050
+ }
1888
2051
  export {
1889
- O as ApiClient,
1890
- K as ApiGatewayClient,
1891
- dt as AuthClient,
1892
- ut as BillingClient,
1893
- ft as EventTracker,
2052
+ E as ApiClient,
2053
+ D as ApiGatewayClient,
2054
+ pt as AuthClient,
2055
+ yt as BillingClient,
2056
+ gt as EventTracker,
1894
2057
  r as PaywallError,
1895
- q as QuotaExceededError,
2058
+ R as QuotaExceededError,
1896
2059
  m as SDK_VERSION,
1897
- y as STORAGE_KEYS,
1898
- C as createStorage,
1899
- S as ensureVisitorId,
1900
- E as generateVisitorId
2060
+ u as STORAGE_KEYS,
2061
+ L as createStorage,
2062
+ b as ensureVisitorId,
2063
+ wt as findApplicableOffer,
2064
+ C as generateVisitorId,
2065
+ ut as offerStartStorageKey,
2066
+ It as readBrowserOfferStart,
2067
+ mt as resolveOffer
1901
2068
  };
1902
2069
  //# sourceMappingURL=core.js.map