@monetize.software/sdk 3.0.0-alpha.3 → 3.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/chunks/PaywallUI-BD5hRY2P.js +26 -0
  2. package/dist/chunks/PaywallUI-BD5hRY2P.js.map +1 -0
  3. package/dist/chunks/PaywallUI-D7lp-bC5.js +3206 -0
  4. package/dist/chunks/PaywallUI-D7lp-bC5.js.map +1 -0
  5. package/dist/chunks/ar-BCHXVoE2.js +114 -0
  6. package/dist/chunks/ar-BCHXVoE2.js.map +1 -0
  7. package/dist/chunks/ar-CsJNZJSr.js +2 -0
  8. package/dist/chunks/ar-CsJNZJSr.js.map +1 -0
  9. package/dist/chunks/cs-B5NqpTW_.js +110 -0
  10. package/dist/chunks/cs-B5NqpTW_.js.map +1 -0
  11. package/dist/chunks/cs-BydWUC0e.js +2 -0
  12. package/dist/chunks/cs-BydWUC0e.js.map +1 -0
  13. package/dist/chunks/da-BJrGZ3LD.js +110 -0
  14. package/dist/chunks/da-BJrGZ3LD.js.map +1 -0
  15. package/dist/chunks/da-DNhiAQnh.js +2 -0
  16. package/dist/chunks/da-DNhiAQnh.js.map +1 -0
  17. package/dist/chunks/de-H8ztFOie.js +2 -0
  18. package/dist/chunks/de-H8ztFOie.js.map +1 -0
  19. package/dist/chunks/de-aepBKwsb.js +130 -0
  20. package/dist/chunks/de-aepBKwsb.js.map +1 -0
  21. package/dist/chunks/el-DRfoadtI.js +2 -0
  22. package/dist/chunks/el-DRfoadtI.js.map +1 -0
  23. package/dist/chunks/el-DTLQoX2D.js +114 -0
  24. package/dist/chunks/el-DTLQoX2D.js.map +1 -0
  25. package/dist/chunks/es-CLutF-D_.js +130 -0
  26. package/dist/chunks/es-CLutF-D_.js.map +1 -0
  27. package/dist/chunks/es-GlaYesNR.js +2 -0
  28. package/dist/chunks/es-GlaYesNR.js.map +1 -0
  29. package/dist/chunks/fi-BIHFyScH.js +2 -0
  30. package/dist/chunks/fi-BIHFyScH.js.map +1 -0
  31. package/dist/chunks/fi-DZ4csxqk.js +110 -0
  32. package/dist/chunks/fi-DZ4csxqk.js.map +1 -0
  33. package/dist/chunks/fr-BtZILUNZ.js +2 -0
  34. package/dist/chunks/fr-BtZILUNZ.js.map +1 -0
  35. package/dist/chunks/fr-jJU1SSpj.js +130 -0
  36. package/dist/chunks/fr-jJU1SSpj.js.map +1 -0
  37. package/dist/chunks/he-D9obGPNj.js +114 -0
  38. package/dist/chunks/he-D9obGPNj.js.map +1 -0
  39. package/dist/chunks/he-vSDRE4Nn.js +2 -0
  40. package/dist/chunks/he-vSDRE4Nn.js.map +1 -0
  41. package/dist/chunks/hi-B90FsnP6.js +2 -0
  42. package/dist/chunks/hi-B90FsnP6.js.map +1 -0
  43. package/dist/chunks/hi-pM8SQwZ3.js +114 -0
  44. package/dist/chunks/hi-pM8SQwZ3.js.map +1 -0
  45. package/dist/chunks/hu-DWVFODsS.js +2 -0
  46. package/dist/chunks/hu-DWVFODsS.js.map +1 -0
  47. package/dist/chunks/hu-E0m9WgbD.js +110 -0
  48. package/dist/chunks/hu-E0m9WgbD.js.map +1 -0
  49. package/dist/chunks/id-C6poPvby.js +110 -0
  50. package/dist/chunks/id-C6poPvby.js.map +1 -0
  51. package/dist/chunks/id-Ce2gzMVT.js +2 -0
  52. package/dist/chunks/id-Ce2gzMVT.js.map +1 -0
  53. package/dist/chunks/it-B2RSFyVd.js +130 -0
  54. package/dist/chunks/it-B2RSFyVd.js.map +1 -0
  55. package/dist/chunks/it-u-Gu44bl.js +2 -0
  56. package/dist/chunks/it-u-Gu44bl.js.map +1 -0
  57. package/dist/chunks/ja-CM-VgVG6.js +134 -0
  58. package/dist/chunks/ja-CM-VgVG6.js.map +1 -0
  59. package/dist/chunks/ja-CQy8RaRa.js +2 -0
  60. package/dist/chunks/ja-CQy8RaRa.js.map +1 -0
  61. package/dist/chunks/ko-BRnb7vJ7.js +2 -0
  62. package/dist/chunks/ko-BRnb7vJ7.js.map +1 -0
  63. package/dist/chunks/ko-C451fA21.js +134 -0
  64. package/dist/chunks/ko-C451fA21.js.map +1 -0
  65. package/dist/chunks/nl-CJelco6J.js +2 -0
  66. package/dist/chunks/nl-CJelco6J.js.map +1 -0
  67. package/dist/chunks/nl-DzQfJPo2.js +130 -0
  68. package/dist/chunks/nl-DzQfJPo2.js.map +1 -0
  69. package/dist/chunks/no-B51be8KT.js +110 -0
  70. package/dist/chunks/no-B51be8KT.js.map +1 -0
  71. package/dist/chunks/no-BwTjSZ4K.js +2 -0
  72. package/dist/chunks/no-BwTjSZ4K.js.map +1 -0
  73. package/dist/chunks/pl-5rTEkvfY.js +110 -0
  74. package/dist/chunks/pl-5rTEkvfY.js.map +1 -0
  75. package/dist/chunks/pl-kO82vcjb.js +2 -0
  76. package/dist/chunks/pl-kO82vcjb.js.map +1 -0
  77. package/dist/chunks/pt-CsJzaSjg.js +2 -0
  78. package/dist/chunks/pt-CsJzaSjg.js.map +1 -0
  79. package/dist/chunks/pt-JwqffZ9u.js +130 -0
  80. package/dist/chunks/pt-JwqffZ9u.js.map +1 -0
  81. package/dist/chunks/ro-BE_wJ1td.js +110 -0
  82. package/dist/chunks/ro-BE_wJ1td.js.map +1 -0
  83. package/dist/chunks/ro-ue15Ina4.js +2 -0
  84. package/dist/chunks/ro-ue15Ina4.js.map +1 -0
  85. package/dist/chunks/ru-B1iMOhX0.js +2 -0
  86. package/dist/chunks/ru-B1iMOhX0.js.map +1 -0
  87. package/dist/chunks/ru-BviATvLb.js +124 -0
  88. package/dist/chunks/ru-BviATvLb.js.map +1 -0
  89. package/dist/chunks/sv-CkNYpUVy.js +2 -0
  90. package/dist/chunks/sv-CkNYpUVy.js.map +1 -0
  91. package/dist/chunks/sv-DabGF9WL.js +110 -0
  92. package/dist/chunks/sv-DabGF9WL.js.map +1 -0
  93. package/dist/chunks/th-BiF-bNo0.js +114 -0
  94. package/dist/chunks/th-BiF-bNo0.js.map +1 -0
  95. package/dist/chunks/th-Cu80HK4y.js +2 -0
  96. package/dist/chunks/th-Cu80HK4y.js.map +1 -0
  97. package/dist/chunks/tr-B7c0afXV.js +2 -0
  98. package/dist/chunks/tr-B7c0afXV.js.map +1 -0
  99. package/dist/chunks/tr-xZuly8X8.js +110 -0
  100. package/dist/chunks/tr-xZuly8X8.js.map +1 -0
  101. package/dist/chunks/uk-BO106B0H.js +2 -0
  102. package/dist/chunks/uk-BO106B0H.js.map +1 -0
  103. package/dist/chunks/uk-KlkAaHuy.js +124 -0
  104. package/dist/chunks/uk-KlkAaHuy.js.map +1 -0
  105. package/dist/chunks/vi-BVCeumNE.js +110 -0
  106. package/dist/chunks/vi-BVCeumNE.js.map +1 -0
  107. package/dist/chunks/vi-CZ6ow40D.js +2 -0
  108. package/dist/chunks/vi-CZ6ow40D.js.map +1 -0
  109. package/dist/chunks/zh-BhP80WI1.js +2 -0
  110. package/dist/chunks/zh-BhP80WI1.js.map +1 -0
  111. package/dist/chunks/zh-C_ghwmqi.js +134 -0
  112. package/dist/chunks/zh-C_ghwmqi.js.map +1 -0
  113. package/dist/core.cjs +1 -1
  114. package/dist/core.cjs.map +1 -1
  115. package/dist/core.d.ts +152 -11
  116. package/dist/core.js +433 -348
  117. package/dist/core.js.map +1 -1
  118. package/dist/index.cjs +1 -1
  119. package/dist/index.d.ts +166 -22
  120. package/dist/index.js +1 -1
  121. package/dist/ui.cjs +1 -1
  122. package/dist/ui.d.ts +166 -22
  123. package/dist/ui.js +1 -1
  124. package/package.json +32 -31
  125. package/dist/chunks/PaywallUI-BWU_1hsh.js +0 -26
  126. package/dist/chunks/PaywallUI-BWU_1hsh.js.map +0 -1
  127. package/dist/chunks/PaywallUI-C3W0eKDo.js +0 -2233
  128. package/dist/chunks/PaywallUI-C3W0eKDo.js.map +0 -1
package/dist/core.js CHANGED
@@ -1,9 +1,9 @@
1
- class r extends Error {
1
+ class n extends Error {
2
2
  constructor(t, e, s = {}) {
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 D extends n {
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);
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(","));
19
+ const s = new URL(t, this.opts.apiOrigin).toString(), a = this.opts.fetch ?? fetch, r = new Headers(e.headers);
20
+ r.set("Accept", "application/json"), r.set("X-SDK-Version", m), r.set("X-Paywall-Id", this.opts.paywallId), this.opts.capabilities?.length && r.set("X-SDK-Capabilities", this.opts.capabilities.join(","));
21
21
  const o = await this.opts.getAuthToken?.();
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;
22
+ o && r.set("Authorization", `Bearer ${o}`);
23
+ const d = typeof FormData < "u" && e.body instanceof FormData;
24
+ e.body && !r.has("Content-Type") && !d && r.set("Content-Type", "application/json");
25
+ let u;
26
26
  try {
27
- l = await i(s, {
27
+ u = await a(s, {
28
28
  ...e,
29
- headers: n,
29
+ headers: r,
30
30
  credentials: "omit"
31
31
  });
32
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 });
33
+ throw (c && typeof c == "object" && "name" in c ? c.name : void 0) === "AbortError" ? new n("aborted", "Request aborted", { cause: c }) : new n("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 = (u.headers.get("content-type") ?? "").includes("application/json") ? await u.json().catch(() => null) : null;
36
+ if (!u.ok) {
37
+ const c = y && typeof y == "object" && "code" in y && String(y.code) || `http_${u.status}`, g = y && typeof y == "object" && "message" in y && String(y.message) || u.statusText || "Request failed";
38
+ throw new n(c, g, { status: u.status, cause: y });
39
39
  }
40
- return f;
40
+ return y;
41
41
  }
42
42
  }
43
- const $ = "https://appbox.space";
44
- class K {
43
+ class q {
45
44
  constructor(t) {
46
45
  if (!t.paywallId)
47
- 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(
46
+ throw new n("invalid_config", "paywallId is required");
47
+ if (!t.apiOrigin)
48
+ throw new n(
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(","));
60
- 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";
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(","));
64
+ const r = await this.auth?.getAccessToken();
65
+ r ? a.set("Authorization", `Bearer ${r}`) : 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, u = 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 || d || u || 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"
74
78
  });
75
79
  } catch (p) {
76
80
  const R = p instanceof Error ? p.message : String(p);
77
- throw new r("network_error", `Network request failed: ${R}`, { cause: p });
81
+ throw new n("network_error", `Network request failed: ${R}`, { cause: p });
78
82
  }
79
83
  if (c.status === 402) {
80
- const p = await F(c);
84
+ const p = await K(c);
81
85
  throw this.onQuotaExceeded?.(p), p;
82
86
  }
83
87
  if (!c.ok) {
84
- const p = await N(c.clone());
85
- throw new r(
88
+ const p = await $(c.clone());
89
+ throw new n(
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 F(a) {
99
+ async function K(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 D({
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) => {
149
- if (n !== "local") return;
150
- const o = i[a];
152
+ const s = (a, r) => {
153
+ if (r !== "local") return;
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
+ }, v = /* @__PURE__ */ new Map(), J = {
188
+ async getItem(i) {
189
+ return v.get(i) ?? null;
186
190
  },
187
- async setItem(a, t) {
188
- b.set(a, t);
191
+ async setItem(i, t) {
192
+ v.set(i, t);
189
193
  },
190
- async removeItem(a) {
191
- b.delete(a);
194
+ async removeItem(i) {
195
+ v.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 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
- 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 I(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
- const t = E();
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 H = 5e3, V = 30 * 6e4, I = 60 * 6e4, j = 5 * 6e4, B = {
256
+ const H = 5e3, V = 30 * 6e4, b = 60 * 6e4, j = 5 * 6e4, _ = {
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 G(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 X = 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
276
  class ut {
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
- 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;
279
+ throw new n("invalid_config", "paywallId is required");
280
+ if (!t.apiOrigin)
281
+ throw new n(
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 r = a ? T(a.user) : void 0;
300
+ W(this.identity, r) || this.setIdentity(r);
301
+ })), this.hydrateUserFromStorage(), this.hydrateBootstrapFromStorage(), this.subscribeBootstrapStorage(), this.hydrateBalancesFromStorage(), this.subscribeBalancesStorage(), this.visitorIdPromise = I(this.storage).then((s) => (this.visitorId = s, s));
293
302
  }
294
303
  /**
295
304
  * Stable visitor_id (UUID v4). Первый вызов awaitит первичный резолв из
@@ -297,7 +306,7 @@ 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 = I(this.storage).then((t) => (this.visitorId = t, t))), this.visitorIdPromise);
301
310
  }
302
311
  /** Sync-доступ к visitor_id. null если ещё не зарезолвили (первые ms жизни). */
303
312
  getCachedVisitorId() {
@@ -328,14 +337,14 @@ class ut {
328
337
  const e = typeof t == "boolean" ? { force: t } : t;
329
338
  if (this.previewMode) {
330
339
  if (this.cachedBootstrap) return this.cachedBootstrap;
331
- throw new r(
340
+ throw new n(
332
341
  "invalid_config",
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 < b;
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,12 +392,12 @@ 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 = U(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);
390
- } catch (n) {
391
- console.warn("[paywall] onBootstrapChange listener threw", n);
398
+ a(s);
399
+ } catch (r) {
400
+ console.warn("[paywall] onBootstrapChange listener threw", r);
392
401
  }
393
402
  }
394
403
  // Network primitive — единая точка для force-запроса, revalidate'а и
@@ -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 r = a;
417
+ return Y(r.settings.custom_domain, this.apiOrigin), r.layout || (r.layout = U(r.settings, r.prices)), w(r), this.applyBootstrap(r, { persist: !0 }), r.user && this.applyUser(r.user), r;
409
418
  }
410
419
  // Фоновый revalidate из stale-while-revalidate ветки. Дедуплицируется через
411
420
  // `inflightBootstrap`, чтобы параллельные revalidate'ы не пересекались.
@@ -425,26 +434,26 @@ 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);
431
- } catch (n) {
432
- console.warn("[paywall] onBootstrapChange listener threw", n);
439
+ a(t);
440
+ } catch (r) {
441
+ console.warn("[paywall] onBootstrapChange listener threw", r);
433
442
  }
434
443
  }
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(l.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 > b || 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
+ l.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
+ l.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
  }
@@ -522,7 +531,7 @@ class ut {
522
531
  * есть `navigator.language`.
523
532
  */
524
533
  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;
534
+ 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
535
  return { tag: s ?? t ?? e, applied: s, browserLanguage: t, countryLanguage: e };
527
536
  }
528
537
  /**
@@ -537,7 +546,7 @@ class ut {
537
546
  return !t && this.cachedUser && Date.now() - this.cachedUserAt < H ? this.cachedUser : this.inflightUser ? this.inflightUser : (this.inflightUser = (async () => {
538
547
  try {
539
548
  if (!this.identity?.email)
540
- return this.applyUser(B), B;
549
+ return this.applyUser(_), _;
541
550
  const s = await this.api.request(
542
551
  `/api/v1/paywall/${this.paywallId}/user-state`,
543
552
  { headers: { "X-User-Email": this.identity.email }, signal: e }
@@ -569,16 +578,16 @@ class ut {
569
578
  this.userListeners.add(t);
570
579
  const s = e.immediate ?? "microtask";
571
580
  if (this.cachedUser && s !== "none") {
572
- const i = this.cachedUser;
581
+ const a = this.cachedUser;
573
582
  if (s === "sync")
574
583
  try {
575
- t(i);
576
- } catch (n) {
577
- console.warn("[paywall] onUserChange initial sync threw", n);
584
+ t(a);
585
+ } catch (r) {
586
+ console.warn("[paywall] onUserChange initial sync threw", r);
578
587
  }
579
588
  else
580
589
  queueMicrotask(() => {
581
- this.userListeners.has(t) && t(i);
590
+ this.userListeners.has(t) && t(a);
582
591
  });
583
592
  }
584
593
  return () => {
@@ -590,19 +599,19 @@ class ut {
590
599
  return this.cachedUser;
591
600
  }
592
601
  applyUser(t) {
593
- const e = !X(this.cachedUser, t);
602
+ const e = !G(this.cachedUser, t);
594
603
  if (this.cachedUser = t, this.cachedUserAt = Date.now(), e) {
595
604
  this.persistUser(t);
596
605
  for (const s of this.userListeners)
597
606
  try {
598
607
  s(t);
599
- } catch (i) {
600
- console.warn("[paywall] onUserChange listener threw", i);
608
+ } catch (a) {
609
+ console.warn("[paywall] onUserChange listener threw", a);
601
610
  }
602
611
  }
603
612
  }
604
613
  storageKey() {
605
- return y.userState(this.paywallId, _(this.identity));
614
+ return l.userState(this.paywallId, B(this.identity));
606
615
  }
607
616
  async hydrateUserFromStorage() {
608
617
  if (!this.cachedUser)
@@ -638,8 +647,8 @@ class ut {
638
647
  * по `currentBalance` в QuotaExceededError или `balances.length`.
639
648
  */
640
649
  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(() => {
650
+ const s = Date.now(), a = this.cachedBalances ? s - this.cachedBalancesAt : 1 / 0;
651
+ return !t && this.cachedBalances && (a < X || a < z) ? this.cachedBalances : !t && this.cachedBalances && a < A ? (this.fetchBalances({ signal: e }).catch(() => {
643
652
  }), this.cachedBalances) : this.inflightBalances ? this.inflightBalances : this.fetchBalances({ signal: e });
644
653
  }
645
654
  // Network primitive — единая точка для force/stale-revalidate/cold-start.
@@ -670,16 +679,16 @@ class ut {
670
679
  this.balanceListeners.add(t);
671
680
  const s = e.immediate ?? "microtask";
672
681
  if (this.cachedBalances && s !== "none") {
673
- const i = this.cachedBalances;
682
+ const a = this.cachedBalances;
674
683
  if (s === "sync")
675
684
  try {
676
- t(i);
677
- } catch (n) {
678
- console.warn("[paywall] onBalanceChange initial sync threw", n);
685
+ t(a);
686
+ } catch (r) {
687
+ console.warn("[paywall] onBalanceChange initial sync threw", r);
679
688
  }
680
689
  else
681
690
  queueMicrotask(() => {
682
- this.balanceListeners.has(t) && t(i);
691
+ this.balanceListeners.has(t) && t(a);
683
692
  });
684
693
  }
685
694
  return () => {
@@ -706,12 +715,12 @@ class ut {
706
715
  return;
707
716
  }
708
717
  if (!this.cachedBalances) return;
709
- const e = this.cachedBalances.findIndex((n) => n.type === t);
718
+ const e = this.cachedBalances.findIndex((r) => r.type === t);
710
719
  if (e < 0 || this.cachedBalances[e].count <= 0) return;
711
- const i = this.cachedBalances.map(
712
- (n, o) => o === e ? { ...n, count: n.count - 1 } : n
720
+ const a = this.cachedBalances.map(
721
+ (r, o) => o === e ? { ...r, count: r.count - 1 } : r
713
722
  );
714
- this.applyBalances(i);
723
+ this.applyBalances(a);
715
724
  }
716
725
  /** Принудительный re-fetch — типичный вызов после QuotaExceededError, чтобы
717
726
  * UI получил актуальный balance=0 и нарисовал upgrade-prompt. */
@@ -730,7 +739,7 @@ class ut {
730
739
  */
731
740
  createApiGatewayClient(t = {}) {
732
741
  const e = t.onChargeSuccess, s = t.onQuotaExceeded;
733
- return new K({
742
+ return new q({
734
743
  paywallId: this.paywallId,
735
744
  apiOrigin: this.apiOrigin,
736
745
  auth: this.auth,
@@ -738,26 +747,26 @@ class ut {
738
747
  capabilities: this.capabilities,
739
748
  fetch: this.fetchImpl,
740
749
  ...t,
741
- onChargeSuccess: (i) => {
742
- this.decrementBalanceLocal(i), e?.(i);
750
+ onChargeSuccess: (a) => {
751
+ this.decrementBalanceLocal(a), e?.(a);
743
752
  },
744
- onQuotaExceeded: (i) => {
745
- this.refreshBalances(), s?.(i);
753
+ onQuotaExceeded: (a) => {
754
+ this.refreshBalances(), s?.(a);
746
755
  }
747
756
  });
748
757
  }
749
758
  applyBalances(t, { persist: e = !0 } = {}) {
750
759
  const s = !Q(this.cachedBalances, t);
751
760
  if (this.cachedBalances = t, this.cachedBalancesAt = Date.now(), e && this.persistBalances(t), s)
752
- for (const i of this.balanceListeners)
761
+ for (const a of this.balanceListeners)
753
762
  try {
754
- i(t);
755
- } catch (n) {
756
- console.warn("[paywall] onBalanceChange listener threw", n);
763
+ a(t);
764
+ } catch (r) {
765
+ console.warn("[paywall] onBalanceChange listener threw", r);
757
766
  }
758
767
  }
759
768
  balancesStorageKey() {
760
- return y.balances(this.paywallId, _(this.identity));
769
+ return l.balances(this.paywallId, B(this.identity));
761
770
  }
762
771
  async hydrateBalancesFromStorage() {
763
772
  if (!this.cachedBalances)
@@ -770,8 +779,8 @@ class ut {
770
779
  for (const s of this.balanceListeners)
771
780
  try {
772
781
  s(e.balances);
773
- } catch (i) {
774
- console.warn("[paywall] onBalanceChange listener threw", i);
782
+ } catch (a) {
783
+ console.warn("[paywall] onBalanceChange listener threw", a);
775
784
  }
776
785
  } catch {
777
786
  }
@@ -804,42 +813,42 @@ class ut {
804
813
  }
805
814
  async createCheckout(t) {
806
815
  if (!this.identity?.email)
807
- throw new r(
816
+ throw new n(
808
817
  "identity_required",
809
818
  "createCheckout requires identity with email"
810
819
  );
811
820
  const e = t.idempotencyKey ?? `auto:${t.priceId}`, s = this.inflightCheckouts.get(e);
812
821
  if (s) return s;
813
- const n = {
814
- "Idempotency-Key": t.idempotencyKey ?? E()
822
+ const r = {
823
+ "Idempotency-Key": t.idempotencyKey ?? C()
815
824
  };
816
- 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`, {
825
+ this.apiKey && (r["X-Api-Key"] = this.apiKey);
826
+ const o = this.cachedBootstrap?.settings, d = t.successUrl ?? o?.success_redirect_url ?? void 0, u = t.shopUrl ?? o?.checkout_shop_url ?? void 0, f = this.api.request(`/api/v1/paywall/${this.paywallId}/start-checkout`, {
818
827
  method: "POST",
819
- headers: n,
828
+ headers: r,
820
829
  signal: t.signal,
821
830
  body: JSON.stringify({
822
831
  email: this.identity.email,
823
832
  priceId: Number(t.priceId),
824
- successUrl: u,
833
+ successUrl: d,
825
834
  errorUrl: t.errorUrl,
826
- shopUrl: l,
835
+ shopUrl: u,
827
836
  productName: o?.checkout_product_name ?? void 0,
828
837
  trial_days: t.trialDays,
829
838
  ignoreActivePurchase: t.ignoreActivePurchase ? !0 : void 0,
830
839
  userMeta: this.identity.userId ? { userId: this.identity.userId } : void 0
831
840
  })
832
841
  }).then((h) => ({ url: h.checkoutUrl, acquiring: h.acquiring })).catch((h) => {
833
- throw h instanceof r && h.status === 409 && h.cause && typeof h.cause == "object" && h.cause.hasActivePurchase === !0 ? new r(
842
+ throw h instanceof n && h.status === 409 && h.cause && typeof h.cause == "object" && h.cause.hasActivePurchase === !0 ? new n(
834
843
  "already_purchased",
835
844
  "You already have an active subscription",
836
845
  { status: 409, cause: h.cause }
837
846
  ) : h;
838
847
  });
839
- return this.inflightCheckouts.set(e, d), d.finally(() => {
840
- this.inflightCheckouts.get(e) === d && this.inflightCheckouts.delete(e);
848
+ return this.inflightCheckouts.set(e, f), f.finally(() => {
849
+ this.inflightCheckouts.get(e) === f && this.inflightCheckouts.delete(e);
841
850
  }).catch(() => {
842
- }), d;
851
+ }), f;
843
852
  }
844
853
  /**
845
854
  * URL Stripe/Paddle/Chargebee customer portal — место, где залогиненный
@@ -859,7 +868,7 @@ class ut {
859
868
  */
860
869
  async getCustomerPortalUrl(t = {}) {
861
870
  if (!this.auth && !this.apiKey && !this.identity?.email)
862
- throw new r(
871
+ throw new n(
863
872
  "identity_required",
864
873
  "getCustomerPortalUrl requires auth, apiKey, or identity.email"
865
874
  );
@@ -891,7 +900,7 @@ class ut {
891
900
  */
892
901
  async listPurchases(t = {}) {
893
902
  if (!this.auth)
894
- throw new r(
903
+ throw new n(
895
904
  "auth_required",
896
905
  "listPurchases requires AuthClient (Bearer auth)"
897
906
  );
@@ -912,7 +921,7 @@ class ut {
912
921
  */
913
922
  async cancelSubscription(t) {
914
923
  if (!this.auth)
915
- throw new r(
924
+ throw new n(
916
925
  "auth_required",
917
926
  "cancelSubscription requires AuthClient (Bearer auth)"
918
927
  );
@@ -937,12 +946,12 @@ class ut {
937
946
  async createSupportTicket(t) {
938
947
  const e = t.email ?? this.identity?.email ?? null, s = `/api/v1/paywall/${this.paywallId}/support/ticket`;
939
948
  if (!!t.files && t.files.length > 0) {
940
- const n = new FormData();
941
- n.set("subject", t.subject), n.set("content", t.content), e && n.set("customer_email", e);
942
- for (const o of t.files) n.append("files", o);
949
+ const r = new FormData();
950
+ r.set("subject", t.subject), r.set("content", t.content), e && r.set("customer_email", e);
951
+ for (const o of t.files) r.append("files", o);
943
952
  return this.api.request(s, {
944
953
  method: "POST",
945
- body: n
954
+ body: r
946
955
  });
947
956
  }
948
957
  return this.api.request(s, {
@@ -955,80 +964,108 @@ class ut {
955
964
  });
956
965
  }
957
966
  }
958
- function T(a) {
959
- return { email: a.email, userId: a.id };
967
+ function T(i) {
968
+ return { email: i.email, userId: i.id };
969
+ }
970
+ function W(i, t) {
971
+ return i === t ? !0 : !i || !t ? !1 : i.email === t.email && i.userId === t.userId && i.anonymousId === t.anonymousId;
960
972
  }
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;
973
+ function O(i) {
974
+ if (!i) return null;
975
+ const t = i.trim();
976
+ if (!t) return null;
977
+ try {
978
+ return new URL(t.includes("://") ? t : `https://${t}`).origin;
979
+ } catch {
980
+ return null;
981
+ }
963
982
  }
964
- function U(a, t) {
983
+ function Y(i, t) {
984
+ const e = O(i);
985
+ if (!(!e || O(t) === e))
986
+ throw new n(
987
+ "invalid_config",
988
+ `apiOrigin mismatch: SDK initialized with "${t}" but paywall is configured with custom_domain "${i}". Use the custom_domain from the platform paywall settings.`
989
+ );
990
+ }
991
+ function U(i, t) {
965
992
  return {
966
993
  type: "modal",
967
994
  blocks: [
968
- { type: "heading", text: a.name || "Upgrade", level: 1 },
995
+ // offer_banner НЕ в default layout PaywallRoot рендерит его как
996
+ // top-tab над dialog'ом (rounded-top, negative margin), за пределами
997
+ // scrollable area. Блок остаётся в registry для opt-in inline-вариантa.
998
+ { type: "heading", text: i.name || "Upgrade", level: 1 },
969
999
  { type: "price_grid", priceIds: t.map((e) => e.id) },
970
- { type: "cta_button", label: "Continue", action: "checkout" }
1000
+ { type: "cta_button", action: "checkout" },
1001
+ { type: "guarantee_badge" },
1002
+ { type: "current_session" }
971
1003
  ]
972
1004
  };
973
1005
  }
974
- function L(a) {
975
- const t = a.locales;
1006
+ function N(i) {
1007
+ const t = i.locales;
976
1008
  if (!t) return null;
977
1009
  const e = [];
978
1010
  if (typeof navigator < "u") {
979
1011
  navigator.language && e.push(navigator.language);
980
- const i = navigator.language?.split("-")[0];
981
- i && i !== navigator.language && e.push(i);
1012
+ const a = navigator.language?.split("-")[0];
1013
+ a && a !== navigator.language && e.push(a);
982
1014
  }
983
- const s = a.settings.locale_default;
1015
+ const s = i.settings.locale_default;
984
1016
  s && e.push(s);
985
- for (const i of e)
986
- if (i && Object.prototype.hasOwnProperty.call(t, i)) return i;
1017
+ for (const a of e)
1018
+ if (a && Object.prototype.hasOwnProperty.call(t, a)) return a;
987
1019
  return null;
988
1020
  }
989
- function g(a) {
990
- const t = L(a);
1021
+ function w(i) {
1022
+ const t = N(i);
991
1023
  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;
996
- const n = { ...s };
997
- return "label" in i && (n.label = i.label ?? null), "description" in i && (n.description = i.description ?? null), n;
1024
+ const e = i.locales?.[t];
1025
+ e && (e.layout && (i.layout = e.layout), e.prices && (i.prices = i.prices.map((s) => {
1026
+ const a = e.prices?.[s.id];
1027
+ if (!a) return s;
1028
+ const r = { ...s };
1029
+ return "label" in a && (r.label = a.label ?? null), "description" in a && (r.description = a.description ?? null), r;
998
1030
  })));
999
1031
  }
1000
- function P(a) {
1001
- const t = new Uint8Array(a), e = typeof globalThis < "u" ? globalThis.crypto : void 0;
1032
+ function P(i) {
1033
+ const t = new Uint8Array(i), e = typeof globalThis < "u" ? globalThis.crypto : void 0;
1002
1034
  if (e && typeof e.getRandomValues == "function")
1003
1035
  e.getRandomValues(t);
1004
1036
  else
1005
- for (let s = 0; s < a; s++) t[s] = Math.floor(Math.random() * 256);
1037
+ for (let s = 0; s < i; s++) t[s] = Math.floor(Math.random() * 256);
1006
1038
  return t;
1007
1039
  }
1008
- function v(a) {
1040
+ function S(i) {
1009
1041
  let t = "";
1010
- for (let e = 0; e < a.length; e++) t += String.fromCharCode(a[e]);
1042
+ for (let e = 0; e < i.length; e++) t += String.fromCharCode(i[e]);
1011
1043
  return btoa(t).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1012
1044
  }
1013
1045
  function Z() {
1014
- return v(P(64));
1046
+ return S(P(64));
1015
1047
  }
1016
- async function tt(a) {
1017
- const t = new TextEncoder().encode(a), e = globalThis.crypto;
1048
+ async function tt(i) {
1049
+ const t = new TextEncoder().encode(i), e = globalThis.crypto;
1018
1050
  if (!e?.subtle?.digest)
1019
1051
  throw new Error("crypto.subtle is required for PKCE");
1020
1052
  const s = await e.subtle.digest("SHA-256", t);
1021
- return v(new Uint8Array(s));
1053
+ return S(new Uint8Array(s));
1022
1054
  }
1023
1055
  function et() {
1024
- return v(P(16));
1056
+ return S(P(16));
1025
1057
  }
1026
- const st = "https://appbox.space", it = 6e4, at = 600 * 1e3;
1058
+ const st = 6e4, it = 600 * 1e3;
1027
1059
  class dt {
1028
1060
  constructor(t) {
1029
1061
  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
- 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({
1062
+ throw new n("invalid_config", "paywallId is required");
1063
+ if (!t.apiOrigin)
1064
+ throw new n(
1065
+ "invalid_config",
1066
+ "apiOrigin is required. Pass the paywall custom_domain configured in the platform."
1067
+ );
1068
+ this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin, this.storage = L(t.storage), this.api = new E({
1032
1069
  apiOrigin: this.apiOrigin,
1033
1070
  paywallId: t.paywallId,
1034
1071
  fetch: t.fetch
@@ -1054,14 +1091,15 @@ class dt {
1054
1091
  async applyExternalSession(t) {
1055
1092
  if (!this.destroyed && (await this.hydrated, !this.destroyed)) {
1056
1093
  if (t == null) {
1057
- this.session && this.setSession(null, { skipPersist: !0 });
1094
+ this.session && this.setSession(null, { skipPersist: !0, event: "SIGNED_OUT" });
1058
1095
  return;
1059
1096
  }
1060
1097
  try {
1061
1098
  const e = JSON.parse(t);
1062
1099
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1063
1100
  return;
1064
- this.setSession(e, { skipPersist: !0 });
1101
+ const s = !this.session || this.session.user.id !== e.user.id ? "SIGNED_IN" : "TOKEN_REFRESHED";
1102
+ this.setSession(e, { skipPersist: !0, event: s });
1065
1103
  } catch {
1066
1104
  }
1067
1105
  }
@@ -1105,7 +1143,7 @@ class dt {
1105
1143
  await this.hydrated;
1106
1144
  const e = await this.readVisitorId(), s = {};
1107
1145
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1108
- const i = await this.api.request(
1146
+ const a = await this.api.request(
1109
1147
  `/api/v1/paywall/${this.paywallId}/auth/email/signin`,
1110
1148
  {
1111
1149
  method: "POST",
@@ -1117,8 +1155,8 @@ class dt {
1117
1155
  user_meta: t.userMeta
1118
1156
  })
1119
1157
  }
1120
- ), n = this.toSession(i, i.user);
1121
- return this.setSession(n), n;
1158
+ ), r = this.toSession(a, a.user);
1159
+ return this.setSession(r, { event: "SIGNED_IN" }), this.recordLastLogin("email", t.email), r;
1122
1160
  }
1123
1161
  /**
1124
1162
  * Signup. Если в Supabase включён email confirm — сервер возвращает
@@ -1130,7 +1168,7 @@ class dt {
1130
1168
  await this.hydrated;
1131
1169
  const e = await this.readVisitorId(), s = {};
1132
1170
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1133
- const i = await this.api.request(
1171
+ const a = await this.api.request(
1134
1172
  `/api/v1/paywall/${this.paywallId}/auth/email/signup`,
1135
1173
  {
1136
1174
  method: "POST",
@@ -1143,10 +1181,10 @@ class dt {
1143
1181
  })
1144
1182
  }
1145
1183
  );
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 };
1184
+ if (a.status === "confirmation_required")
1185
+ return this.recordLastLogin("email", t.email), { kind: "confirmation_required", user: a.user };
1186
+ const r = this.toSession(a, a.user);
1187
+ return this.setSession(r, { event: "SIGNED_IN" }), this.recordLastLogin("email", t.email), { kind: "signed_in", session: r };
1150
1188
  }
1151
1189
  /**
1152
1190
  * Повторная отправка confirmation-email после signUp с включённым
@@ -1209,8 +1247,8 @@ class dt {
1209
1247
  user_meta: t.userMeta
1210
1248
  })
1211
1249
  }
1212
- ), i = this.toSession(s, s.user);
1213
- return this.setSession(i), i;
1250
+ ), a = this.toSession(s, s.user), r = t.type === "recovery" ? "PASSWORD_RECOVERY" : "SIGNED_IN";
1251
+ return this.setSession(a, { event: r }), a;
1214
1252
  }
1215
1253
  /**
1216
1254
  * Запрос recovery email. Бэк всегда ok, чтобы не палить enumeration.
@@ -1236,7 +1274,7 @@ class dt {
1236
1274
  await this.hydrated;
1237
1275
  const e = await this.getAccessToken();
1238
1276
  if (!e)
1239
- throw new r("not_authenticated", "no active session");
1277
+ throw new n("not_authenticated", "no active session");
1240
1278
  await this.api.request(
1241
1279
  `/api/v1/paywall/${this.paywallId}/auth/password/update`,
1242
1280
  {
@@ -1297,12 +1335,12 @@ class dt {
1297
1335
  user_meta: t.userMeta
1298
1336
  })
1299
1337
  }
1300
- ), i = {
1338
+ ), a = {
1301
1339
  ...s.user,
1302
1340
  email: s.user.email ?? null,
1303
1341
  is_anonymous: !0
1304
- }, n = this.toSession(s, i);
1305
- return this.setSession(n), await this.writeAnonRefreshToken(n.refresh_token), n;
1342
+ }, r = this.toSession(s, a);
1343
+ return this.setSession(r, { event: "SIGNED_IN" }), await this.writeAnonRefreshToken(r.refresh_token), r;
1306
1344
  })();
1307
1345
  try {
1308
1346
  return await this.inflightAnonSignin;
@@ -1323,10 +1361,10 @@ class dt {
1323
1361
  const e = await this.api.request(
1324
1362
  `/api/v1/paywall/${this.paywallId}/auth/refresh`,
1325
1363
  { 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;
1364
+ ), s = this.session?.user.is_anonymous === !0 ? this.session.user : { id: "", email: null, is_anonymous: !0 }, a = this.toSession(e, s);
1365
+ return this.setSession(a, { event: "SIGNED_IN" }), await this.writeAnonRefreshToken(a.refresh_token), a;
1328
1366
  } catch (e) {
1329
- if (e instanceof r && e.status === 401)
1367
+ if (e instanceof n && e.status === 401)
1330
1368
  return await this.clearAnonRefreshToken(), null;
1331
1369
  throw e;
1332
1370
  }
@@ -1356,12 +1394,12 @@ class dt {
1356
1394
  await this.hydrated;
1357
1395
  const e = await this.getAccessToken();
1358
1396
  if (!e)
1359
- throw new r("not_authenticated", "no active session");
1397
+ throw new n("not_authenticated", "no active session");
1360
1398
  const s = {
1361
1399
  Authorization: `Bearer ${e}`
1362
1400
  };
1363
1401
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1364
- const i = await this.api.request(
1402
+ const a = await this.api.request(
1365
1403
  `/api/v1/paywall/${this.paywallId}/auth/anonymous/upgrade`,
1366
1404
  {
1367
1405
  method: "POST",
@@ -1373,21 +1411,21 @@ class dt {
1373
1411
  })
1374
1412
  }
1375
1413
  );
1376
- if (i.status === "confirmation_required")
1377
- return { kind: "confirmation_required", email: i.email };
1378
- const n = this.session;
1379
- if (!n)
1380
- throw new r(
1414
+ if (a.status === "confirmation_required")
1415
+ return { kind: "confirmation_required", email: a.email };
1416
+ const r = this.session;
1417
+ if (!r)
1418
+ throw new n(
1381
1419
  "not_authenticated",
1382
1420
  "session disappeared during upgrade"
1383
1421
  );
1384
1422
  const o = {
1385
- ...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 };
1423
+ ...r.user,
1424
+ id: a.user.id,
1425
+ email: a.user.email,
1426
+ is_anonymous: a.user.is_anonymous ?? !1
1427
+ }, d = { ...r, user: o };
1428
+ return this.setSession(d, { event: "USER_UPDATED" }), await this.clearAnonRefreshToken(), { kind: "updated", session: d };
1391
1429
  }
1392
1430
  /**
1393
1431
  * OAuth signin через popup с PKCE. Жизненный цикл:
@@ -1411,22 +1449,22 @@ class dt {
1411
1449
  */
1412
1450
  async signInWithOAuth(t) {
1413
1451
  if (typeof window > "u")
1414
- throw new r("oauth_unavailable", "window is required for OAuth");
1452
+ throw new n("oauth_unavailable", "window is required for OAuth");
1415
1453
  const { authorize_url: e, state: s } = await this.startOAuthFlow({
1416
1454
  provider: t.provider,
1417
1455
  scopes: t.scopes,
1418
1456
  userMeta: t.userMeta
1419
- }), i = this.openPopup(e, `pw-oauth-${s}`);
1420
- if (!i)
1421
- throw this.oauthFlows.delete(s), new r(
1457
+ }), a = this.openPopup(e, `pw-oauth-${s}`);
1458
+ if (!a)
1459
+ throw this.oauthFlows.delete(s), new n(
1422
1460
  "popup_blocked",
1423
1461
  "browser blocked auth popup — call from a user gesture"
1424
1462
  );
1425
1463
  t.onPopupOpened?.();
1426
- const n = await ot(i, s);
1464
+ const r = await nt(a, s);
1427
1465
  if (this.destroyed)
1428
- throw this.oauthFlows.delete(s), new r("aborted", "AuthClient destroyed mid-flow");
1429
- return this.completeOAuthFlow({ state: s, code: n });
1466
+ throw this.oauthFlows.delete(s), new n("aborted", "AuthClient destroyed mid-flow");
1467
+ return this.completeOAuthFlow({ state: s, code: r });
1430
1468
  }
1431
1469
  /**
1432
1470
  * Шаг 1 OAuth split-API: инициирует flow на бэке, генерит PKCE verifier
@@ -1444,13 +1482,13 @@ class dt {
1444
1482
  */
1445
1483
  async startOAuthFlow(t) {
1446
1484
  await this.hydrated, this.gcOAuthFlows();
1447
- const e = Z(), s = await tt(e), i = et(), n = {}, o = await this.getAccessToken().catch(() => null);
1448
- o && (n.Authorization = `Bearer ${o}`);
1449
- const { authorize_url: u } = await this.api.request(
1485
+ const e = Z(), s = await tt(e), a = et(), r = {}, o = await this.getAccessToken().catch(() => null);
1486
+ o && (r.Authorization = `Bearer ${o}`);
1487
+ const { authorize_url: d } = await this.api.request(
1450
1488
  `/api/v1/paywall/${this.paywallId}/auth/oauth/init`,
1451
1489
  {
1452
1490
  method: "POST",
1453
- headers: Object.keys(n).length ? n : void 0,
1491
+ headers: Object.keys(r).length ? r : void 0,
1454
1492
  body: JSON.stringify({
1455
1493
  provider: t.provider,
1456
1494
  code_challenge: s,
@@ -1459,11 +1497,12 @@ class dt {
1459
1497
  })
1460
1498
  }
1461
1499
  );
1462
- return this.oauthFlows.set(i, {
1500
+ return this.oauthFlows.set(a, {
1463
1501
  verifier: e,
1464
1502
  userMeta: t.userMeta,
1503
+ provider: t.provider,
1465
1504
  startedAt: Date.now()
1466
- }), { authorize_url: u, state: i };
1505
+ }), this.recordLastLoginMethod(t.provider), { authorize_url: d, state: a };
1467
1506
  }
1468
1507
  /**
1469
1508
  * Шаг 2 OAuth split-API: обменивает code (полученный из popup) на session,
@@ -1478,12 +1517,12 @@ class dt {
1478
1517
  await this.hydrated;
1479
1518
  const e = this.oauthFlows.get(t.state);
1480
1519
  if (!e)
1481
- throw new r(
1520
+ throw new n(
1482
1521
  "oauth_invalid_state",
1483
1522
  "OAuth flow not found — start with startOAuthFlow first or check TTL"
1484
1523
  );
1485
1524
  this.oauthFlows.delete(t.state);
1486
- const s = await this.readVisitorId(), i = await this.api.request(
1525
+ const s = await this.readVisitorId(), a = await this.api.request(
1487
1526
  `/api/v1/paywall/${this.paywallId}/auth/oauth/exchange`,
1488
1527
  {
1489
1528
  method: "POST",
@@ -1496,12 +1535,12 @@ class dt {
1496
1535
  }
1497
1536
  );
1498
1537
  if (this.destroyed)
1499
- throw new r("aborted", "AuthClient destroyed mid-flow");
1500
- const n = this.toSession(i, i.user);
1501
- return this.setSession(n), n;
1538
+ throw new n("aborted", "AuthClient destroyed mid-flow");
1539
+ const r = this.toSession(a, a.user);
1540
+ return this.setSession(r, { event: "SIGNED_IN" }), r.user.email && this.recordLastLoginEmail(r.user.email), r;
1502
1541
  }
1503
1542
  gcOAuthFlows() {
1504
- const t = Date.now() - at;
1543
+ const t = Date.now() - it;
1505
1544
  for (const [e, s] of this.oauthFlows)
1506
1545
  s.startedAt < t && this.oauthFlows.delete(e);
1507
1546
  }
@@ -1526,11 +1565,11 @@ class dt {
1526
1565
  method: "POST",
1527
1566
  body: JSON.stringify({ refresh_token: t })
1528
1567
  }
1529
- ), i = this.toSession(s, e);
1530
- return this.setSession(i), e.is_anonymous === !0 && await this.writeAnonRefreshToken(i.refresh_token), i;
1568
+ ), a = this.toSession(s, e);
1569
+ return this.setSession(a, { event: "TOKEN_REFRESHED" }), e.is_anonymous === !0 && await this.writeAnonRefreshToken(a.refresh_token), a;
1531
1570
  } catch (s) {
1532
- if (s instanceof r && s.status === 401)
1533
- return e.is_anonymous === !0 && await this.clearAnonRefreshToken(), this.setSession(null), null;
1571
+ if (s instanceof n && s.status === 401)
1572
+ return e.is_anonymous === !0 && await this.clearAnonRefreshToken(), this.setSession(null, { event: "SIGNED_OUT" }), null;
1534
1573
  throw s;
1535
1574
  } finally {
1536
1575
  this.inflightRefresh = null;
@@ -1556,14 +1595,14 @@ class dt {
1556
1595
  await this.hydrated;
1557
1596
  const t = this.session?.access_token;
1558
1597
  if (!t)
1559
- throw new r("not_authenticated", "no active session");
1598
+ throw new n("not_authenticated", "no active session");
1560
1599
  await this.api.request(
1561
1600
  `/api/v1/paywall/${this.paywallId}/auth/revoke-all`,
1562
1601
  {
1563
1602
  method: "POST",
1564
1603
  headers: { Authorization: `Bearer ${t}` }
1565
1604
  }
1566
- ), this.setSession(null);
1605
+ ), this.setSession(null, { event: "SIGNED_OUT" });
1567
1606
  }
1568
1607
  /**
1569
1608
  * Signout: чистит локальную session СРАЗУ (UX — мгновенный logout без
@@ -1583,7 +1622,7 @@ class dt {
1583
1622
  async signOut(t = {}) {
1584
1623
  await this.hydrated;
1585
1624
  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))
1625
+ if (this.setSession(null, { event: "SIGNED_OUT" }), t.forgetAnonymous && await this.clearAnonRefreshToken(), !!e && !(s && !t.forgetAnonymous))
1587
1626
  try {
1588
1627
  await this.api.request(
1589
1628
  `/api/v1/paywall/${this.paywallId}/auth/signout`,
@@ -1597,22 +1636,35 @@ class dt {
1597
1636
  }
1598
1637
  /**
1599
1638
  * Подписка на изменения session: signin/signup/refresh/signOut/expired-401.
1600
- * Колбек вызывается с текущим snapshot через microtask (если session есть)
1601
- * + на каждое реальное изменение. Возвращает unsubscribe.
1639
+ *
1640
+ * Гарантированный контракт: ПЕРВЫЙ callback каждому subscriber'у — всегда
1641
+ * `event = 'INITIAL_SESSION'`, дёргается асинхронно после resolve hydrate'а
1642
+ * (даже если session=null — listener получает explicit «нет сессии», а не
1643
+ * молчание). Все последующие callback'и — реальные переходы с конкретным
1644
+ * event'ом (SIGNED_IN / SIGNED_OUT / TOKEN_REFRESHED / USER_UPDATED /
1645
+ * PASSWORD_RECOVERY).
1646
+ *
1647
+ * Это позволяет listener'у безопасно делать «only on real signin» побочные
1648
+ * эффекты (force refetch balances и т.п.) через `event === 'SIGNED_IN'`,
1649
+ * не путая их с восстановлением из storage.
1650
+ *
1651
+ * Возвращает unsubscribe.
1602
1652
  */
1603
1653
  onAuthChange(t) {
1604
- if (this.listeners.add(t), this.session) {
1654
+ return this.listeners.add(t), this.hydrated.then(() => {
1655
+ if (this.destroyed || !this.listeners.has(t)) return;
1605
1656
  const e = this.session;
1606
- queueMicrotask(() => {
1607
- this.listeners.has(t) && t(e);
1608
- });
1609
- }
1610
- return () => {
1657
+ try {
1658
+ t("INITIAL_SESSION", e);
1659
+ } catch (s) {
1660
+ console.warn("[paywall] onAuthChange INITIAL_SESSION threw", s);
1661
+ }
1662
+ }), () => {
1611
1663
  this.listeners.delete(t);
1612
1664
  };
1613
1665
  }
1614
1666
  isFresh(t) {
1615
- return t.expires_at - Date.now() > it;
1667
+ return t.expires_at - Date.now() > st;
1616
1668
  }
1617
1669
  toSession(t, e) {
1618
1670
  const s = t.expires_at != null ? t.expires_at * 1e3 : Date.now() + t.expires_in * 1e3;
@@ -1623,21 +1675,21 @@ class dt {
1623
1675
  user: e
1624
1676
  };
1625
1677
  }
1626
- setSession(t, e = {}) {
1678
+ setSession(t, e) {
1627
1679
  if (this.destroyed) return;
1628
1680
  const s = this.session;
1629
- this.session = t, e.skipPersist || this.persist(), ht(s, t) || this.emit();
1681
+ this.session = t, e.skipPersist || this.persist(), ht(s, t) || this.emit(e.event);
1630
1682
  }
1631
- emit() {
1632
- for (const t of this.listeners)
1683
+ emit(t) {
1684
+ for (const e of this.listeners)
1633
1685
  try {
1634
- t(this.session);
1635
- } catch (e) {
1636
- console.warn("[paywall] onAuthChange listener threw", e);
1686
+ e(t, this.session);
1687
+ } catch (s) {
1688
+ console.warn("[paywall] onAuthChange listener threw", s);
1637
1689
  }
1638
1690
  }
1639
1691
  storageKey() {
1640
- return y.authSession(this.paywallId);
1692
+ return l.authSession(this.paywallId);
1641
1693
  }
1642
1694
  async hydrate() {
1643
1695
  try {
@@ -1646,7 +1698,7 @@ class dt {
1646
1698
  const e = JSON.parse(t);
1647
1699
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1648
1700
  return;
1649
- this.session = e, this.emit();
1701
+ this.session = e;
1650
1702
  } catch {
1651
1703
  }
1652
1704
  }
@@ -1660,7 +1712,7 @@ class dt {
1660
1712
  const e = JSON.parse(t);
1661
1713
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1662
1714
  return;
1663
- this.setSession(e, { skipPersist: !0 });
1715
+ this.setSession(e, { skipPersist: !0, event: "SIGNED_IN" });
1664
1716
  } catch {
1665
1717
  }
1666
1718
  }
@@ -1691,7 +1743,7 @@ class dt {
1691
1743
  }
1692
1744
  async readAnonRefreshToken() {
1693
1745
  try {
1694
- const t = await this.storage.getItem(y.anonRefreshToken(this.paywallId));
1746
+ const t = await this.storage.getItem(l.anonRefreshToken(this.paywallId));
1695
1747
  return typeof t == "string" && t.length > 0 ? t : null;
1696
1748
  } catch {
1697
1749
  return null;
@@ -1700,7 +1752,7 @@ class dt {
1700
1752
  async writeAnonRefreshToken(t) {
1701
1753
  try {
1702
1754
  await this.storage.setItem(
1703
- y.anonRefreshToken(this.paywallId),
1755
+ l.anonRefreshToken(this.paywallId),
1704
1756
  t
1705
1757
  );
1706
1758
  } catch {
@@ -1709,11 +1761,41 @@ class dt {
1709
1761
  async clearAnonRefreshToken() {
1710
1762
  try {
1711
1763
  await this.storage.removeItem(
1712
- y.anonRefreshToken(this.paywallId)
1764
+ l.anonRefreshToken(this.paywallId)
1713
1765
  );
1714
1766
  } catch {
1715
1767
  }
1716
1768
  }
1769
+ /**
1770
+ * Last-used auth method + email — для UI бейджа "Last used" и pre-fill'а
1771
+ * email-инпута. Storage paywall-scoped, поэтому переключение между
1772
+ * пейволами на одном host'е не пересекает данные. Чтение всегда возвращает
1773
+ * объект — отсутствующие поля = null. */
1774
+ async getLastLogin() {
1775
+ try {
1776
+ const [t, e] = await Promise.all([
1777
+ this.storage.getItem(l.lastLoginMethod(this.paywallId)),
1778
+ this.storage.getItem(l.lastLoginEmail(this.paywallId))
1779
+ ]);
1780
+ return !t || !ot(t) ? null : { method: t, email: typeof e == "string" && e ? e : null };
1781
+ } catch {
1782
+ return null;
1783
+ }
1784
+ }
1785
+ /** Запись method и email атомарно (для email/password flows — оба известны
1786
+ * на момент signin/signup'а). OAuth-flows используют раздельные
1787
+ * recordLastLoginMethod (до popup) и recordLastLoginEmail (после exchange). */
1788
+ recordLastLogin(t, e) {
1789
+ this.recordLastLoginMethod(t), e && this.recordLastLoginEmail(e);
1790
+ }
1791
+ recordLastLoginMethod(t) {
1792
+ this.storage.setItem(l.lastLoginMethod(this.paywallId), t).catch(() => {
1793
+ });
1794
+ }
1795
+ recordLastLoginEmail(t) {
1796
+ this.storage.setItem(l.lastLoginEmail(this.paywallId), t).catch(() => {
1797
+ });
1798
+ }
1717
1799
  /**
1718
1800
  * Читает stable visitor_id из storage если он там уже есть. НЕ генерит:
1719
1801
  * AuthClient может быть инстанцирован раньше BillingClient, а синтетический
@@ -1723,68 +1805,71 @@ class dt {
1723
1805
  */
1724
1806
  async readVisitorId() {
1725
1807
  try {
1726
- const t = await this.storage.getItem(y.visitorId);
1808
+ const t = await this.storage.getItem(l.visitorId);
1727
1809
  return typeof t == "string" && t.length >= 16 ? t : void 0;
1728
1810
  } catch {
1729
1811
  return;
1730
1812
  }
1731
1813
  }
1732
1814
  }
1733
- const nt = 5 * 6e4, rt = 500;
1734
- function ot(a, t) {
1815
+ const at = 5 * 6e4, rt = 500;
1816
+ function nt(i, t) {
1735
1817
  return new Promise((e, s) => {
1736
- let i = !1;
1737
- 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;
1818
+ let a = !1;
1819
+ const r = () => {
1820
+ a = !0, window.removeEventListener("message", o), clearInterval(d), clearTimeout(u);
1821
+ }, o = (f) => {
1822
+ if (a) return;
1823
+ const h = f.data;
1742
1824
  if (!(!h || h.type !== "pw-oauth") && h.messageId === t) {
1743
1825
  if (h.status === "success" && h.code) {
1744
- n();
1826
+ r();
1745
1827
  try {
1746
- a.close();
1828
+ i.close();
1747
1829
  } catch {
1748
1830
  }
1749
1831
  e(h.code);
1750
1832
  } else if (h.status === "error") {
1751
- n();
1833
+ r();
1752
1834
  try {
1753
- a.close();
1835
+ i.close();
1754
1836
  } catch {
1755
1837
  }
1756
1838
  s(
1757
- new r(
1839
+ new n(
1758
1840
  "oauth_failed",
1759
1841
  h.description || h.error || "OAuth provider returned error"
1760
1842
  )
1761
1843
  );
1762
1844
  }
1763
1845
  }
1764
- }, u = setInterval(() => {
1765
- if (i) return;
1766
- let d;
1846
+ }, d = setInterval(() => {
1847
+ if (a) return;
1848
+ let f;
1767
1849
  try {
1768
- d = a.closed;
1850
+ f = i.closed;
1769
1851
  } catch {
1770
1852
  return;
1771
1853
  }
1772
- d && (n(), s(new r("oauth_cancelled", "auth popup was closed")));
1773
- }, rt), l = setTimeout(() => {
1774
- if (!i) {
1775
- n();
1854
+ f && (r(), s(new n("oauth_cancelled", "auth popup was closed")));
1855
+ }, rt), u = setTimeout(() => {
1856
+ if (!a) {
1857
+ r();
1776
1858
  try {
1777
- a.close();
1859
+ i.close();
1778
1860
  } catch {
1779
1861
  }
1780
- s(new r("oauth_timeout", "OAuth flow timed out"));
1862
+ s(new n("oauth_timeout", "OAuth flow timed out"));
1781
1863
  }
1782
- }, nt);
1864
+ }, at);
1783
1865
  window.addEventListener("message", o);
1784
1866
  });
1785
1867
  }
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;
1868
+ function ot(i) {
1869
+ return i === "google" || i === "apple" || i === "github" || i === "facebook" || i === "email";
1870
+ }
1871
+ function ht(i, t) {
1872
+ 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
1873
  }
1789
1874
  const ct = 1500, lt = 20, k = 200;
1790
1875
  class ft {
@@ -1817,15 +1902,15 @@ class ft {
1817
1902
  const t = this.buffer;
1818
1903
  this.buffer = [];
1819
1904
  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);
1821
- if (!n) return;
1822
- await n(this.opts.endpoint, {
1905
+ const e = await this.opts.getVisitorId(), s = this.opts.getUserId?.() ?? null, a = JSON.stringify({ events: t }), r = this.opts.fetch ?? (typeof fetch < "u" ? fetch : void 0);
1906
+ if (!r) return;
1907
+ await r(this.opts.endpoint, {
1823
1908
  method: "POST",
1824
1909
  credentials: "omit",
1825
1910
  keepalive: !0,
1826
1911
  // если страница закроется в этот момент — браузер всё равно дотянет
1827
1912
  headers: this.buildHeaders(e, s),
1828
- body: i
1913
+ body: a
1829
1914
  });
1830
1915
  } catch {
1831
1916
  }
@@ -1845,7 +1930,7 @@ class ft {
1845
1930
  this.buffer.unshift(...t), this.flush();
1846
1931
  return;
1847
1932
  }
1848
- const i = JSON.stringify({
1933
+ const a = JSON.stringify({
1849
1934
  events: t,
1850
1935
  // body-level дубликаты для beacon-flow, читаются сервером как fallback.
1851
1936
  visitor_id: e,
@@ -1853,13 +1938,13 @@ class ft {
1853
1938
  sdk_version: m,
1854
1939
  paywall_id: this.opts.paywallId,
1855
1940
  capabilities: this.opts.capabilities?.join(",") ?? ""
1856
- }), n = this.opts.sendBeacon ?? (typeof navigator < "u" && typeof navigator.sendBeacon == "function" ? navigator.sendBeacon.bind(navigator) : null);
1857
- if (!n) {
1941
+ }), r = this.opts.sendBeacon ?? (typeof navigator < "u" && typeof navigator.sendBeacon == "function" ? navigator.sendBeacon.bind(navigator) : null);
1942
+ if (!r) {
1858
1943
  this.buffer.unshift(...t), this.flush();
1859
1944
  return;
1860
1945
  }
1861
1946
  try {
1862
- n(this.opts.endpoint, i) || (this.buffer.unshift(...t), this.flush());
1947
+ r(this.opts.endpoint, a) || (this.buffer.unshift(...t), this.flush());
1863
1948
  } catch {
1864
1949
  this.buffer.unshift(...t), this.flush();
1865
1950
  }
@@ -1886,17 +1971,17 @@ class ft {
1886
1971
  }
1887
1972
  }
1888
1973
  export {
1889
- O as ApiClient,
1890
- K as ApiGatewayClient,
1974
+ E as ApiClient,
1975
+ q as ApiGatewayClient,
1891
1976
  dt as AuthClient,
1892
1977
  ut as BillingClient,
1893
1978
  ft as EventTracker,
1894
- r as PaywallError,
1895
- q as QuotaExceededError,
1979
+ n as PaywallError,
1980
+ D as QuotaExceededError,
1896
1981
  m as SDK_VERSION,
1897
- y as STORAGE_KEYS,
1898
- C as createStorage,
1899
- S as ensureVisitorId,
1900
- E as generateVisitorId
1982
+ l as STORAGE_KEYS,
1983
+ L as createStorage,
1984
+ I as ensureVisitorId,
1985
+ C as generateVisitorId
1901
1986
  };
1902
1987
  //# sourceMappingURL=core.js.map