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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 D extends r {
7
7
  constructor(t) {
8
8
  super("not_enough_queries", t.message ?? "Not enough queries", {
9
9
  status: 402
@@ -11,12 +11,12 @@ 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}`);
@@ -24,7 +24,7 @@ class O {
24
24
  e.body && !n.has("Content-Type") && !u && n.set("Content-Type", "application/json");
25
25
  let l;
26
26
  try {
27
- l = await i(s, {
27
+ l = await a(s, {
28
28
  ...e,
29
29
  headers: n,
30
30
  credentials: "omit"
@@ -40,12 +40,16 @@ class O {
40
40
  return f;
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
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,19 +59,19 @@ class K {
55
59
  this.apiOrigin
56
60
  );
57
61
  s.searchParams.set("paywall_id", this.paywallId);
58
- const i = new Headers(t.headers);
59
- i.set("X-SDK-Version", 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);
65
+ n ? a.set("Authorization", `Bearer ${n}`) : this.userId && a.set("X-User-ID", this.userId);
62
66
  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
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"));
68
+ t.body === void 0 || t.body === null ? h = void 0 : o || u || l || d ? h = t.body : (h = JSON.stringify(t.body), a.has("Content-Type") || a.set("Content-Type", "application/json"));
65
69
  const f = this.customFetch ?? fetch;
66
70
  let c;
67
71
  try {
68
72
  c = await f(s.toString(), {
69
73
  method: t.method ?? "POST",
70
- headers: i,
74
+ headers: a,
71
75
  body: h,
72
76
  signal: t.signal,
73
77
  credentials: "omit"
@@ -77,11 +81,11 @@ class K {
77
81
  throw new r("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());
88
+ const p = await $(c.clone());
85
89
  throw new r(
86
90
  p ?? `http_${c.status}`,
87
91
  c.statusText || "Gateway request failed",
@@ -92,192 +96,196 @@ class K {
92
96
  return this.onChargeSuccess?.(w), 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) => {
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
+ }, 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 C(i) {
199
+ return i || (F() ? M : typeof window < "u" && "localStorage" in window ? x : J);
196
200
  }
197
201
  const y = {
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 N() {
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(y.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 = N();
246
250
  try {
247
- await a.setItem(y.visitorId, t);
251
+ await i.setItem(y.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
259
  trial: null
256
260
  };
257
- function _(a) {
258
- return a && (a.email || a.userId || a.anonymousId) || "guest";
261
+ function B(i) {
262
+ return i && (i.email || i.userId || i.anonymousId) || "guest";
259
263
  }
260
- function X(a, t) {
261
- return a === t ? !0 : !a || !t ? !1 : JSON.stringify(a) === JSON.stringify(t);
264
+ function G(i, t) {
265
+ return i === t ? !0 : !i || !t ? !1 : JSON.stringify(i) === JSON.stringify(t);
262
266
  }
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;
267
+ const X = 5e3, A = 5 * 6e4, z = 3e4;
268
+ function Q(i, t) {
269
+ if (i === t) return !0;
270
+ if (!i || !t || i.length !== t.length) return !1;
271
+ for (let e = 0; e < i.length; e++)
272
+ if (i[e].type !== t[e].type || i[e].count !== t[e].count) return !1;
269
273
  return !0;
270
274
  }
271
- const W = "https://appbox.space";
272
- class ut {
275
+ class lt {
273
276
  constructor(t) {
274
277
  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
278
  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
+ if (!t.apiOrigin)
280
+ throw new r(
281
+ "invalid_config",
282
+ '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.'
283
+ );
284
+ this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin, this.capabilities = t.capabilities, this.auth = t.auth, this.previewMode = t.preview === !0;
277
285
  const e = t.auth?.getCachedUser();
278
286
  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
287
  "[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({
288
+ ), this.storage = C(t.storage), this.api = new E({
281
289
  apiOrigin: this.apiOrigin,
282
290
  paywallId: t.paywallId,
283
291
  capabilities: t.capabilities,
@@ -286,10 +294,10 @@ class ut {
286
294
  // делает lazy refresh, дедупит, на 401 возвращает null — тогда
287
295
  // Authorization-хедер просто не выставится.
288
296
  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));
297
+ }), t.auth && (this.authUnsubscribe = t.auth.onAuthChange((s, a) => {
298
+ const n = a ? T(a.user) : void 0;
299
+ W(this.identity, n) || this.setIdentity(n);
300
+ })), this.hydrateUserFromStorage(), this.hydrateBootstrapFromStorage(), this.subscribeBootstrapStorage(), this.hydrateBalancesFromStorage(), this.subscribeBalancesStorage(), this.visitorIdPromise = I(this.storage).then((s) => (this.visitorId = s, s));
293
301
  }
294
302
  /**
295
303
  * Stable visitor_id (UUID v4). Первый вызов awaitит первичный резолв из
@@ -297,7 +305,7 @@ class ut {
297
305
  * EventTracker'ом для атрибуции аналитики.
298
306
  */
299
307
  async getVisitorId() {
300
- return this.visitorId ? this.visitorId : (this.visitorIdPromise || (this.visitorIdPromise = S(this.storage).then((t) => (this.visitorId = t, t))), this.visitorIdPromise);
308
+ return this.visitorId ? this.visitorId : (this.visitorIdPromise || (this.visitorIdPromise = I(this.storage).then((t) => (this.visitorId = t, t))), this.visitorIdPromise);
301
309
  }
302
310
  /** Sync-доступ к visitor_id. null если ещё не зарезолвили (первые ms жизни). */
303
311
  getCachedVisitorId() {
@@ -333,9 +341,9 @@ class ut {
333
341
  "BillingClient in preview mode but cachedBootstrap is not seeded. Call setBootstrap(bootstrap) before open()."
334
342
  );
335
343
  }
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({
344
+ const s = Date.now(), a = this.cachedBootstrap && this.cachedBootstrapAt > 0 && s - this.cachedBootstrapAt < b;
345
+ return !e.force && a ? (s - this.cachedBootstrapAt > j && this.revalidateBootstrap(e.signal).catch(() => {
346
+ }), { ...this.cachedBootstrap, user: this.cachedUser ?? void 0 }) : this.inflightBootstrap ? this.inflightBootstrap : (this.inflightBootstrap = this.fetchBootstrap({
339
347
  ifVersion: e.force ? void 0 : this.cachedBootstrap?.version,
340
348
  signal: e.signal
341
349
  }).finally(() => {
@@ -384,9 +392,9 @@ class ut {
384
392
  version: `preview:${++this.previewVersionCounter}`
385
393
  };
386
394
  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
+ for (const a of this.bootstrapListeners)
388
396
  try {
389
- i(s);
397
+ a(s);
390
398
  } catch (n) {
391
399
  console.warn("[paywall] onBootstrapChange listener threw", n);
392
400
  }
@@ -398,14 +406,14 @@ class ut {
398
406
  async fetchBootstrap(t) {
399
407
  const e = {};
400
408
  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, {
409
+ 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
410
  ...Object.keys(e).length ? { headers: e } : {},
403
411
  signal: t.signal
404
412
  });
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;
413
+ if ("unchanged" in a && a.unchanged)
414
+ return this.cachedBootstrap ? (this.cachedBootstrapAt = Date.now(), a.user && this.applyUser(a.user), this.cachedBootstrap) : this.fetchBootstrap({ signal: t.signal });
415
+ const n = a;
416
+ return Y(n.settings.custom_domain, this.apiOrigin), n.layout || (n.layout = U(n.settings, n.prices)), g(n), this.applyBootstrap(n, { persist: !0 }), n.user && this.applyUser(n.user), n;
409
417
  }
410
418
  // Фоновый revalidate из stale-while-revalidate ветки. Дедуплицируется через
411
419
  // `inflightBootstrap`, чтобы параллельные revalidate'ы не пересекались.
@@ -425,9 +433,9 @@ class ut {
425
433
  applyBootstrap(t, { persist: e }) {
426
434
  const s = !this.cachedBootstrap || this.cachedBootstrap.version !== t.version;
427
435
  if (this.cachedBootstrap = t, this.cachedBootstrapAt = Date.now(), e && this.persistBootstrap(t), s)
428
- for (const i of this.bootstrapListeners)
436
+ for (const a of this.bootstrapListeners)
429
437
  try {
430
- i(t);
438
+ a(t);
431
439
  } catch (n) {
432
440
  console.warn("[paywall] onBootstrapChange listener threw", n);
433
441
  }
@@ -438,13 +446,13 @@ class ut {
438
446
  const t = await this.storage.getItem(y.bootstrap(this.paywallId));
439
447
  if (!t) return;
440
448
  const e = JSON.parse(t);
441
- if (!e?.bootstrap || Date.now() - e.at > I || this.cachedBootstrap) return;
449
+ if (!e?.bootstrap || Date.now() - e.at > b || this.cachedBootstrap) return;
442
450
  g(e.bootstrap), this.cachedBootstrap = e.bootstrap, this.cachedBootstrapAt = e.at;
443
451
  for (const s of this.bootstrapListeners)
444
452
  try {
445
453
  s(e.bootstrap);
446
- } catch (i) {
447
- console.warn("[paywall] onBootstrapChange listener threw", i);
454
+ } catch (a) {
455
+ console.warn("[paywall] onBootstrapChange listener threw", a);
448
456
  }
449
457
  } catch {
450
458
  }
@@ -537,7 +545,7 @@ class ut {
537
545
  return !t && this.cachedUser && Date.now() - this.cachedUserAt < H ? this.cachedUser : this.inflightUser ? this.inflightUser : (this.inflightUser = (async () => {
538
546
  try {
539
547
  if (!this.identity?.email)
540
- return this.applyUser(B), B;
548
+ return this.applyUser(_), _;
541
549
  const s = await this.api.request(
542
550
  `/api/v1/paywall/${this.paywallId}/user-state`,
543
551
  { headers: { "X-User-Email": this.identity.email }, signal: e }
@@ -569,16 +577,16 @@ class ut {
569
577
  this.userListeners.add(t);
570
578
  const s = e.immediate ?? "microtask";
571
579
  if (this.cachedUser && s !== "none") {
572
- const i = this.cachedUser;
580
+ const a = this.cachedUser;
573
581
  if (s === "sync")
574
582
  try {
575
- t(i);
583
+ t(a);
576
584
  } catch (n) {
577
585
  console.warn("[paywall] onUserChange initial sync threw", n);
578
586
  }
579
587
  else
580
588
  queueMicrotask(() => {
581
- this.userListeners.has(t) && t(i);
589
+ this.userListeners.has(t) && t(a);
582
590
  });
583
591
  }
584
592
  return () => {
@@ -590,19 +598,19 @@ class ut {
590
598
  return this.cachedUser;
591
599
  }
592
600
  applyUser(t) {
593
- const e = !X(this.cachedUser, t);
601
+ const e = !G(this.cachedUser, t);
594
602
  if (this.cachedUser = t, this.cachedUserAt = Date.now(), e) {
595
603
  this.persistUser(t);
596
604
  for (const s of this.userListeners)
597
605
  try {
598
606
  s(t);
599
- } catch (i) {
600
- console.warn("[paywall] onUserChange listener threw", i);
607
+ } catch (a) {
608
+ console.warn("[paywall] onUserChange listener threw", a);
601
609
  }
602
610
  }
603
611
  }
604
612
  storageKey() {
605
- return y.userState(this.paywallId, _(this.identity));
613
+ return y.userState(this.paywallId, B(this.identity));
606
614
  }
607
615
  async hydrateUserFromStorage() {
608
616
  if (!this.cachedUser)
@@ -638,8 +646,8 @@ class ut {
638
646
  * по `currentBalance` в QuotaExceededError или `balances.length`.
639
647
  */
640
648
  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(() => {
649
+ const s = Date.now(), a = this.cachedBalances ? s - this.cachedBalancesAt : 1 / 0;
650
+ return !t && this.cachedBalances && (a < X || a < z) ? this.cachedBalances : !t && this.cachedBalances && a < A ? (this.fetchBalances({ signal: e }).catch(() => {
643
651
  }), this.cachedBalances) : this.inflightBalances ? this.inflightBalances : this.fetchBalances({ signal: e });
644
652
  }
645
653
  // Network primitive — единая точка для force/stale-revalidate/cold-start.
@@ -670,16 +678,16 @@ class ut {
670
678
  this.balanceListeners.add(t);
671
679
  const s = e.immediate ?? "microtask";
672
680
  if (this.cachedBalances && s !== "none") {
673
- const i = this.cachedBalances;
681
+ const a = this.cachedBalances;
674
682
  if (s === "sync")
675
683
  try {
676
- t(i);
684
+ t(a);
677
685
  } catch (n) {
678
686
  console.warn("[paywall] onBalanceChange initial sync threw", n);
679
687
  }
680
688
  else
681
689
  queueMicrotask(() => {
682
- this.balanceListeners.has(t) && t(i);
690
+ this.balanceListeners.has(t) && t(a);
683
691
  });
684
692
  }
685
693
  return () => {
@@ -708,10 +716,10 @@ class ut {
708
716
  if (!this.cachedBalances) return;
709
717
  const e = this.cachedBalances.findIndex((n) => n.type === t);
710
718
  if (e < 0 || this.cachedBalances[e].count <= 0) return;
711
- const i = this.cachedBalances.map(
719
+ const a = this.cachedBalances.map(
712
720
  (n, o) => o === e ? { ...n, count: n.count - 1 } : n
713
721
  );
714
- this.applyBalances(i);
722
+ this.applyBalances(a);
715
723
  }
716
724
  /** Принудительный re-fetch — типичный вызов после QuotaExceededError, чтобы
717
725
  * UI получил актуальный balance=0 и нарисовал upgrade-prompt. */
@@ -730,7 +738,7 @@ class ut {
730
738
  */
731
739
  createApiGatewayClient(t = {}) {
732
740
  const e = t.onChargeSuccess, s = t.onQuotaExceeded;
733
- return new K({
741
+ return new q({
734
742
  paywallId: this.paywallId,
735
743
  apiOrigin: this.apiOrigin,
736
744
  auth: this.auth,
@@ -738,26 +746,26 @@ class ut {
738
746
  capabilities: this.capabilities,
739
747
  fetch: this.fetchImpl,
740
748
  ...t,
741
- onChargeSuccess: (i) => {
742
- this.decrementBalanceLocal(i), e?.(i);
749
+ onChargeSuccess: (a) => {
750
+ this.decrementBalanceLocal(a), e?.(a);
743
751
  },
744
- onQuotaExceeded: (i) => {
745
- this.refreshBalances(), s?.(i);
752
+ onQuotaExceeded: (a) => {
753
+ this.refreshBalances(), s?.(a);
746
754
  }
747
755
  });
748
756
  }
749
757
  applyBalances(t, { persist: e = !0 } = {}) {
750
758
  const s = !Q(this.cachedBalances, t);
751
759
  if (this.cachedBalances = t, this.cachedBalancesAt = Date.now(), e && this.persistBalances(t), s)
752
- for (const i of this.balanceListeners)
760
+ for (const a of this.balanceListeners)
753
761
  try {
754
- i(t);
762
+ a(t);
755
763
  } catch (n) {
756
764
  console.warn("[paywall] onBalanceChange listener threw", n);
757
765
  }
758
766
  }
759
767
  balancesStorageKey() {
760
- return y.balances(this.paywallId, _(this.identity));
768
+ return y.balances(this.paywallId, B(this.identity));
761
769
  }
762
770
  async hydrateBalancesFromStorage() {
763
771
  if (!this.cachedBalances)
@@ -770,8 +778,8 @@ class ut {
770
778
  for (const s of this.balanceListeners)
771
779
  try {
772
780
  s(e.balances);
773
- } catch (i) {
774
- console.warn("[paywall] onBalanceChange listener threw", i);
781
+ } catch (a) {
782
+ console.warn("[paywall] onBalanceChange listener threw", a);
775
783
  }
776
784
  } catch {
777
785
  }
@@ -811,7 +819,7 @@ class ut {
811
819
  const e = t.idempotencyKey ?? `auto:${t.priceId}`, s = this.inflightCheckouts.get(e);
812
820
  if (s) return s;
813
821
  const n = {
814
- "Idempotency-Key": t.idempotencyKey ?? E()
822
+ "Idempotency-Key": t.idempotencyKey ?? N()
815
823
  };
816
824
  this.apiKey && (n["X-Api-Key"] = this.apiKey);
817
825
  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`, {
@@ -955,80 +963,105 @@ class ut {
955
963
  });
956
964
  }
957
965
  }
958
- function T(a) {
959
- return { email: a.email, userId: a.id };
966
+ function T(i) {
967
+ return { email: i.email, userId: i.id };
968
+ }
969
+ function W(i, t) {
970
+ return i === t ? !0 : !i || !t ? !1 : i.email === t.email && i.userId === t.userId && i.anonymousId === t.anonymousId;
960
971
  }
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;
972
+ function O(i) {
973
+ if (!i) return null;
974
+ const t = i.trim();
975
+ if (!t) return null;
976
+ try {
977
+ return new URL(t.includes("://") ? t : `https://${t}`).origin;
978
+ } catch {
979
+ return null;
980
+ }
981
+ }
982
+ function Y(i, t) {
983
+ const e = O(i);
984
+ if (!(!e || O(t) === e))
985
+ throw new r(
986
+ "invalid_config",
987
+ `apiOrigin mismatch: SDK initialized with "${t}" but paywall is configured with custom_domain "${i}". Use the custom_domain from the platform paywall settings.`
988
+ );
963
989
  }
964
- function U(a, t) {
990
+ function U(i, t) {
965
991
  return {
966
992
  type: "modal",
967
993
  blocks: [
968
- { type: "heading", text: a.name || "Upgrade", level: 1 },
994
+ { type: "heading", text: i.name || "Upgrade", level: 1 },
969
995
  { type: "price_grid", priceIds: t.map((e) => e.id) },
970
- { type: "cta_button", label: "Continue", action: "checkout" }
996
+ { type: "cta_button", label: "Continue", action: "checkout" },
997
+ { type: "guarantee_badge" },
998
+ { type: "current_session" }
971
999
  ]
972
1000
  };
973
1001
  }
974
- function L(a) {
975
- const t = a.locales;
1002
+ function L(i) {
1003
+ const t = i.locales;
976
1004
  if (!t) return null;
977
1005
  const e = [];
978
1006
  if (typeof navigator < "u") {
979
1007
  navigator.language && e.push(navigator.language);
980
- const i = navigator.language?.split("-")[0];
981
- i && i !== navigator.language && e.push(i);
1008
+ const a = navigator.language?.split("-")[0];
1009
+ a && a !== navigator.language && e.push(a);
982
1010
  }
983
- const s = a.settings.locale_default;
1011
+ const s = i.settings.locale_default;
984
1012
  s && e.push(s);
985
- for (const i of e)
986
- if (i && Object.prototype.hasOwnProperty.call(t, i)) return i;
1013
+ for (const a of e)
1014
+ if (a && Object.prototype.hasOwnProperty.call(t, a)) return a;
987
1015
  return null;
988
1016
  }
989
- function g(a) {
990
- const t = L(a);
1017
+ function g(i) {
1018
+ const t = L(i);
991
1019
  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;
1020
+ const e = i.locales?.[t];
1021
+ e && (e.layout && (i.layout = e.layout), e.prices && (i.prices = i.prices.map((s) => {
1022
+ const a = e.prices?.[s.id];
1023
+ if (!a) return s;
996
1024
  const n = { ...s };
997
- return "label" in i && (n.label = i.label ?? null), "description" in i && (n.description = i.description ?? null), n;
1025
+ return "label" in a && (n.label = a.label ?? null), "description" in a && (n.description = a.description ?? null), n;
998
1026
  })));
999
1027
  }
1000
- function P(a) {
1001
- const t = new Uint8Array(a), e = typeof globalThis < "u" ? globalThis.crypto : void 0;
1028
+ function P(i) {
1029
+ const t = new Uint8Array(i), e = typeof globalThis < "u" ? globalThis.crypto : void 0;
1002
1030
  if (e && typeof e.getRandomValues == "function")
1003
1031
  e.getRandomValues(t);
1004
1032
  else
1005
- for (let s = 0; s < a; s++) t[s] = Math.floor(Math.random() * 256);
1033
+ for (let s = 0; s < i; s++) t[s] = Math.floor(Math.random() * 256);
1006
1034
  return t;
1007
1035
  }
1008
- function v(a) {
1036
+ function S(i) {
1009
1037
  let t = "";
1010
- for (let e = 0; e < a.length; e++) t += String.fromCharCode(a[e]);
1038
+ for (let e = 0; e < i.length; e++) t += String.fromCharCode(i[e]);
1011
1039
  return btoa(t).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1012
1040
  }
1013
1041
  function Z() {
1014
- return v(P(64));
1042
+ return S(P(64));
1015
1043
  }
1016
- async function tt(a) {
1017
- const t = new TextEncoder().encode(a), e = globalThis.crypto;
1044
+ async function tt(i) {
1045
+ const t = new TextEncoder().encode(i), e = globalThis.crypto;
1018
1046
  if (!e?.subtle?.digest)
1019
1047
  throw new Error("crypto.subtle is required for PKCE");
1020
1048
  const s = await e.subtle.digest("SHA-256", t);
1021
- return v(new Uint8Array(s));
1049
+ return S(new Uint8Array(s));
1022
1050
  }
1023
1051
  function et() {
1024
- return v(P(16));
1052
+ return S(P(16));
1025
1053
  }
1026
- const st = "https://appbox.space", it = 6e4, at = 600 * 1e3;
1027
- class dt {
1054
+ const st = 6e4, it = 600 * 1e3;
1055
+ class ut {
1028
1056
  constructor(t) {
1029
1057
  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
1058
  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({
1059
+ if (!t.apiOrigin)
1060
+ throw new r(
1061
+ "invalid_config",
1062
+ "apiOrigin is required. Pass the paywall custom_domain configured in the platform."
1063
+ );
1064
+ this.paywallId = t.paywallId, this.apiOrigin = t.apiOrigin, this.storage = C(t.storage), this.api = new E({
1032
1065
  apiOrigin: this.apiOrigin,
1033
1066
  paywallId: t.paywallId,
1034
1067
  fetch: t.fetch
@@ -1054,14 +1087,15 @@ class dt {
1054
1087
  async applyExternalSession(t) {
1055
1088
  if (!this.destroyed && (await this.hydrated, !this.destroyed)) {
1056
1089
  if (t == null) {
1057
- this.session && this.setSession(null, { skipPersist: !0 });
1090
+ this.session && this.setSession(null, { skipPersist: !0, event: "SIGNED_OUT" });
1058
1091
  return;
1059
1092
  }
1060
1093
  try {
1061
1094
  const e = JSON.parse(t);
1062
1095
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1063
1096
  return;
1064
- this.setSession(e, { skipPersist: !0 });
1097
+ const s = !this.session || this.session.user.id !== e.user.id ? "SIGNED_IN" : "TOKEN_REFRESHED";
1098
+ this.setSession(e, { skipPersist: !0, event: s });
1065
1099
  } catch {
1066
1100
  }
1067
1101
  }
@@ -1105,7 +1139,7 @@ class dt {
1105
1139
  await this.hydrated;
1106
1140
  const e = await this.readVisitorId(), s = {};
1107
1141
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1108
- const i = await this.api.request(
1142
+ const a = await this.api.request(
1109
1143
  `/api/v1/paywall/${this.paywallId}/auth/email/signin`,
1110
1144
  {
1111
1145
  method: "POST",
@@ -1117,8 +1151,8 @@ class dt {
1117
1151
  user_meta: t.userMeta
1118
1152
  })
1119
1153
  }
1120
- ), n = this.toSession(i, i.user);
1121
- return this.setSession(n), n;
1154
+ ), n = this.toSession(a, a.user);
1155
+ return this.setSession(n, { event: "SIGNED_IN" }), n;
1122
1156
  }
1123
1157
  /**
1124
1158
  * Signup. Если в Supabase включён email confirm — сервер возвращает
@@ -1130,7 +1164,7 @@ class dt {
1130
1164
  await this.hydrated;
1131
1165
  const e = await this.readVisitorId(), s = {};
1132
1166
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1133
- const i = await this.api.request(
1167
+ const a = await this.api.request(
1134
1168
  `/api/v1/paywall/${this.paywallId}/auth/email/signup`,
1135
1169
  {
1136
1170
  method: "POST",
@@ -1143,10 +1177,10 @@ class dt {
1143
1177
  })
1144
1178
  }
1145
1179
  );
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 };
1180
+ if (a.status === "confirmation_required")
1181
+ return { kind: "confirmation_required", user: a.user };
1182
+ const n = this.toSession(a, a.user);
1183
+ return this.setSession(n, { event: "SIGNED_IN" }), { kind: "signed_in", session: n };
1150
1184
  }
1151
1185
  /**
1152
1186
  * Повторная отправка confirmation-email после signUp с включённым
@@ -1209,8 +1243,8 @@ class dt {
1209
1243
  user_meta: t.userMeta
1210
1244
  })
1211
1245
  }
1212
- ), i = this.toSession(s, s.user);
1213
- return this.setSession(i), i;
1246
+ ), a = this.toSession(s, s.user), n = t.type === "recovery" ? "PASSWORD_RECOVERY" : "SIGNED_IN";
1247
+ return this.setSession(a, { event: n }), a;
1214
1248
  }
1215
1249
  /**
1216
1250
  * Запрос recovery email. Бэк всегда ok, чтобы не палить enumeration.
@@ -1297,12 +1331,12 @@ class dt {
1297
1331
  user_meta: t.userMeta
1298
1332
  })
1299
1333
  }
1300
- ), i = {
1334
+ ), a = {
1301
1335
  ...s.user,
1302
1336
  email: s.user.email ?? null,
1303
1337
  is_anonymous: !0
1304
- }, n = this.toSession(s, i);
1305
- return this.setSession(n), await this.writeAnonRefreshToken(n.refresh_token), n;
1338
+ }, n = this.toSession(s, a);
1339
+ return this.setSession(n, { event: "SIGNED_IN" }), await this.writeAnonRefreshToken(n.refresh_token), n;
1306
1340
  })();
1307
1341
  try {
1308
1342
  return await this.inflightAnonSignin;
@@ -1323,8 +1357,8 @@ class dt {
1323
1357
  const e = await this.api.request(
1324
1358
  `/api/v1/paywall/${this.paywallId}/auth/refresh`,
1325
1359
  { 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;
1360
+ ), s = this.session?.user.is_anonymous === !0 ? this.session.user : { id: "", email: null, is_anonymous: !0 }, a = this.toSession(e, s);
1361
+ return this.setSession(a, { event: "SIGNED_IN" }), await this.writeAnonRefreshToken(a.refresh_token), a;
1328
1362
  } catch (e) {
1329
1363
  if (e instanceof r && e.status === 401)
1330
1364
  return await this.clearAnonRefreshToken(), null;
@@ -1361,7 +1395,7 @@ class dt {
1361
1395
  Authorization: `Bearer ${e}`
1362
1396
  };
1363
1397
  t.idempotencyKey && (s["Idempotency-Key"] = t.idempotencyKey);
1364
- const i = await this.api.request(
1398
+ const a = await this.api.request(
1365
1399
  `/api/v1/paywall/${this.paywallId}/auth/anonymous/upgrade`,
1366
1400
  {
1367
1401
  method: "POST",
@@ -1373,8 +1407,8 @@ class dt {
1373
1407
  })
1374
1408
  }
1375
1409
  );
1376
- if (i.status === "confirmation_required")
1377
- return { kind: "confirmation_required", email: i.email };
1410
+ if (a.status === "confirmation_required")
1411
+ return { kind: "confirmation_required", email: a.email };
1378
1412
  const n = this.session;
1379
1413
  if (!n)
1380
1414
  throw new r(
@@ -1383,11 +1417,11 @@ class dt {
1383
1417
  );
1384
1418
  const o = {
1385
1419
  ...n.user,
1386
- id: i.user.id,
1387
- email: i.user.email,
1388
- is_anonymous: i.user.is_anonymous ?? !1
1420
+ id: a.user.id,
1421
+ email: a.user.email,
1422
+ is_anonymous: a.user.is_anonymous ?? !1
1389
1423
  }, u = { ...n, user: o };
1390
- return this.setSession(u), await this.clearAnonRefreshToken(), { kind: "updated", session: u };
1424
+ return this.setSession(u, { event: "USER_UPDATED" }), await this.clearAnonRefreshToken(), { kind: "updated", session: u };
1391
1425
  }
1392
1426
  /**
1393
1427
  * OAuth signin через popup с PKCE. Жизненный цикл:
@@ -1416,14 +1450,14 @@ class dt {
1416
1450
  provider: t.provider,
1417
1451
  scopes: t.scopes,
1418
1452
  userMeta: t.userMeta
1419
- }), i = this.openPopup(e, `pw-oauth-${s}`);
1420
- if (!i)
1453
+ }), a = this.openPopup(e, `pw-oauth-${s}`);
1454
+ if (!a)
1421
1455
  throw this.oauthFlows.delete(s), new r(
1422
1456
  "popup_blocked",
1423
1457
  "browser blocked auth popup — call from a user gesture"
1424
1458
  );
1425
1459
  t.onPopupOpened?.();
1426
- const n = await ot(i, s);
1460
+ const n = await rt(a, s);
1427
1461
  if (this.destroyed)
1428
1462
  throw this.oauthFlows.delete(s), new r("aborted", "AuthClient destroyed mid-flow");
1429
1463
  return this.completeOAuthFlow({ state: s, code: n });
@@ -1444,7 +1478,7 @@ class dt {
1444
1478
  */
1445
1479
  async startOAuthFlow(t) {
1446
1480
  await this.hydrated, this.gcOAuthFlows();
1447
- const e = Z(), s = await tt(e), i = et(), n = {}, o = await this.getAccessToken().catch(() => null);
1481
+ const e = Z(), s = await tt(e), a = et(), n = {}, o = await this.getAccessToken().catch(() => null);
1448
1482
  o && (n.Authorization = `Bearer ${o}`);
1449
1483
  const { authorize_url: u } = await this.api.request(
1450
1484
  `/api/v1/paywall/${this.paywallId}/auth/oauth/init`,
@@ -1459,11 +1493,11 @@ class dt {
1459
1493
  })
1460
1494
  }
1461
1495
  );
1462
- return this.oauthFlows.set(i, {
1496
+ return this.oauthFlows.set(a, {
1463
1497
  verifier: e,
1464
1498
  userMeta: t.userMeta,
1465
1499
  startedAt: Date.now()
1466
- }), { authorize_url: u, state: i };
1500
+ }), { authorize_url: u, state: a };
1467
1501
  }
1468
1502
  /**
1469
1503
  * Шаг 2 OAuth split-API: обменивает code (полученный из popup) на session,
@@ -1483,7 +1517,7 @@ class dt {
1483
1517
  "OAuth flow not found — start with startOAuthFlow first or check TTL"
1484
1518
  );
1485
1519
  this.oauthFlows.delete(t.state);
1486
- const s = await this.readVisitorId(), i = await this.api.request(
1520
+ const s = await this.readVisitorId(), a = await this.api.request(
1487
1521
  `/api/v1/paywall/${this.paywallId}/auth/oauth/exchange`,
1488
1522
  {
1489
1523
  method: "POST",
@@ -1497,11 +1531,11 @@ class dt {
1497
1531
  );
1498
1532
  if (this.destroyed)
1499
1533
  throw new r("aborted", "AuthClient destroyed mid-flow");
1500
- const n = this.toSession(i, i.user);
1501
- return this.setSession(n), n;
1534
+ const n = this.toSession(a, a.user);
1535
+ return this.setSession(n, { event: "SIGNED_IN" }), n;
1502
1536
  }
1503
1537
  gcOAuthFlows() {
1504
- const t = Date.now() - at;
1538
+ const t = Date.now() - it;
1505
1539
  for (const [e, s] of this.oauthFlows)
1506
1540
  s.startedAt < t && this.oauthFlows.delete(e);
1507
1541
  }
@@ -1526,11 +1560,11 @@ class dt {
1526
1560
  method: "POST",
1527
1561
  body: JSON.stringify({ refresh_token: t })
1528
1562
  }
1529
- ), i = this.toSession(s, e);
1530
- return this.setSession(i), e.is_anonymous === !0 && await this.writeAnonRefreshToken(i.refresh_token), i;
1563
+ ), a = this.toSession(s, e);
1564
+ return this.setSession(a, { event: "TOKEN_REFRESHED" }), e.is_anonymous === !0 && await this.writeAnonRefreshToken(a.refresh_token), a;
1531
1565
  } catch (s) {
1532
1566
  if (s instanceof r && s.status === 401)
1533
- return e.is_anonymous === !0 && await this.clearAnonRefreshToken(), this.setSession(null), null;
1567
+ return e.is_anonymous === !0 && await this.clearAnonRefreshToken(), this.setSession(null, { event: "SIGNED_OUT" }), null;
1534
1568
  throw s;
1535
1569
  } finally {
1536
1570
  this.inflightRefresh = null;
@@ -1563,7 +1597,7 @@ class dt {
1563
1597
  method: "POST",
1564
1598
  headers: { Authorization: `Bearer ${t}` }
1565
1599
  }
1566
- ), this.setSession(null);
1600
+ ), this.setSession(null, { event: "SIGNED_OUT" });
1567
1601
  }
1568
1602
  /**
1569
1603
  * Signout: чистит локальную session СРАЗУ (UX — мгновенный logout без
@@ -1583,7 +1617,7 @@ class dt {
1583
1617
  async signOut(t = {}) {
1584
1618
  await this.hydrated;
1585
1619
  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))
1620
+ if (this.setSession(null, { event: "SIGNED_OUT" }), t.forgetAnonymous && await this.clearAnonRefreshToken(), !!e && !(s && !t.forgetAnonymous))
1587
1621
  try {
1588
1622
  await this.api.request(
1589
1623
  `/api/v1/paywall/${this.paywallId}/auth/signout`,
@@ -1597,22 +1631,35 @@ class dt {
1597
1631
  }
1598
1632
  /**
1599
1633
  * Подписка на изменения session: signin/signup/refresh/signOut/expired-401.
1600
- * Колбек вызывается с текущим snapshot через microtask (если session есть)
1601
- * + на каждое реальное изменение. Возвращает unsubscribe.
1634
+ *
1635
+ * Гарантированный контракт: ПЕРВЫЙ callback каждому subscriber'у — всегда
1636
+ * `event = 'INITIAL_SESSION'`, дёргается асинхронно после resolve hydrate'а
1637
+ * (даже если session=null — listener получает explicit «нет сессии», а не
1638
+ * молчание). Все последующие callback'и — реальные переходы с конкретным
1639
+ * event'ом (SIGNED_IN / SIGNED_OUT / TOKEN_REFRESHED / USER_UPDATED /
1640
+ * PASSWORD_RECOVERY).
1641
+ *
1642
+ * Это позволяет listener'у безопасно делать «only on real signin» побочные
1643
+ * эффекты (force refetch balances и т.п.) через `event === 'SIGNED_IN'`,
1644
+ * не путая их с восстановлением из storage.
1645
+ *
1646
+ * Возвращает unsubscribe.
1602
1647
  */
1603
1648
  onAuthChange(t) {
1604
- if (this.listeners.add(t), this.session) {
1649
+ return this.listeners.add(t), this.hydrated.then(() => {
1650
+ if (this.destroyed || !this.listeners.has(t)) return;
1605
1651
  const e = this.session;
1606
- queueMicrotask(() => {
1607
- this.listeners.has(t) && t(e);
1608
- });
1609
- }
1610
- return () => {
1652
+ try {
1653
+ t("INITIAL_SESSION", e);
1654
+ } catch (s) {
1655
+ console.warn("[paywall] onAuthChange INITIAL_SESSION threw", s);
1656
+ }
1657
+ }), () => {
1611
1658
  this.listeners.delete(t);
1612
1659
  };
1613
1660
  }
1614
1661
  isFresh(t) {
1615
- return t.expires_at - Date.now() > it;
1662
+ return t.expires_at - Date.now() > st;
1616
1663
  }
1617
1664
  toSession(t, e) {
1618
1665
  const s = t.expires_at != null ? t.expires_at * 1e3 : Date.now() + t.expires_in * 1e3;
@@ -1623,17 +1670,17 @@ class dt {
1623
1670
  user: e
1624
1671
  };
1625
1672
  }
1626
- setSession(t, e = {}) {
1673
+ setSession(t, e) {
1627
1674
  if (this.destroyed) return;
1628
1675
  const s = this.session;
1629
- this.session = t, e.skipPersist || this.persist(), ht(s, t) || this.emit();
1676
+ this.session = t, e.skipPersist || this.persist(), ot(s, t) || this.emit(e.event);
1630
1677
  }
1631
- emit() {
1632
- for (const t of this.listeners)
1678
+ emit(t) {
1679
+ for (const e of this.listeners)
1633
1680
  try {
1634
- t(this.session);
1635
- } catch (e) {
1636
- console.warn("[paywall] onAuthChange listener threw", e);
1681
+ e(t, this.session);
1682
+ } catch (s) {
1683
+ console.warn("[paywall] onAuthChange listener threw", s);
1637
1684
  }
1638
1685
  }
1639
1686
  storageKey() {
@@ -1646,7 +1693,7 @@ class dt {
1646
1693
  const e = JSON.parse(t);
1647
1694
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1648
1695
  return;
1649
- this.session = e, this.emit();
1696
+ this.session = e;
1650
1697
  } catch {
1651
1698
  }
1652
1699
  }
@@ -1660,7 +1707,7 @@ class dt {
1660
1707
  const e = JSON.parse(t);
1661
1708
  if (!e || typeof e.access_token != "string" || typeof e.refresh_token != "string" || typeof e.expires_at != "number" || !e.user)
1662
1709
  return;
1663
- this.setSession(e, { skipPersist: !0 });
1710
+ this.setSession(e, { skipPersist: !0, event: "SIGNED_IN" });
1664
1711
  } catch {
1665
1712
  }
1666
1713
  }
@@ -1730,27 +1777,27 @@ class dt {
1730
1777
  }
1731
1778
  }
1732
1779
  }
1733
- const nt = 5 * 6e4, rt = 500;
1734
- function ot(a, t) {
1780
+ const at = 5 * 6e4, nt = 500;
1781
+ function rt(i, t) {
1735
1782
  return new Promise((e, s) => {
1736
- let i = !1;
1783
+ let a = !1;
1737
1784
  const n = () => {
1738
- i = !0, window.removeEventListener("message", o), clearInterval(u), clearTimeout(l);
1785
+ a = !0, window.removeEventListener("message", o), clearInterval(u), clearTimeout(l);
1739
1786
  }, o = (d) => {
1740
- if (i) return;
1787
+ if (a) return;
1741
1788
  const h = d.data;
1742
1789
  if (!(!h || h.type !== "pw-oauth") && h.messageId === t) {
1743
1790
  if (h.status === "success" && h.code) {
1744
1791
  n();
1745
1792
  try {
1746
- a.close();
1793
+ i.close();
1747
1794
  } catch {
1748
1795
  }
1749
1796
  e(h.code);
1750
1797
  } else if (h.status === "error") {
1751
1798
  n();
1752
1799
  try {
1753
- a.close();
1800
+ i.close();
1754
1801
  } catch {
1755
1802
  }
1756
1803
  s(
@@ -1762,32 +1809,32 @@ function ot(a, t) {
1762
1809
  }
1763
1810
  }
1764
1811
  }, u = setInterval(() => {
1765
- if (i) return;
1812
+ if (a) return;
1766
1813
  let d;
1767
1814
  try {
1768
- d = a.closed;
1815
+ d = i.closed;
1769
1816
  } catch {
1770
1817
  return;
1771
1818
  }
1772
1819
  d && (n(), s(new r("oauth_cancelled", "auth popup was closed")));
1773
- }, rt), l = setTimeout(() => {
1774
- if (!i) {
1820
+ }, nt), l = setTimeout(() => {
1821
+ if (!a) {
1775
1822
  n();
1776
1823
  try {
1777
- a.close();
1824
+ i.close();
1778
1825
  } catch {
1779
1826
  }
1780
1827
  s(new r("oauth_timeout", "OAuth flow timed out"));
1781
1828
  }
1782
- }, nt);
1829
+ }, at);
1783
1830
  window.addEventListener("message", o);
1784
1831
  });
1785
1832
  }
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;
1833
+ function ot(i, t) {
1834
+ 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
1835
  }
1789
- const ct = 1500, lt = 20, k = 200;
1790
- class ft {
1836
+ const ht = 1500, ct = 20, k = 200;
1837
+ class dt {
1791
1838
  constructor(t) {
1792
1839
  this.buffer = [], this.flushTimer = null, this.destroyed = !1, this.unloadHandler = null, this.visibilityHandler = null, this.opts = t, this.isEnabled() && this.attachUnloadHandlers();
1793
1840
  }
@@ -1797,7 +1844,7 @@ class ft {
1797
1844
  track(t, e) {
1798
1845
  if (this.destroyed || !this.isEnabled() || typeof t != "string" || t.length === 0) return;
1799
1846
  this.buffer.push({ type: t, ts: Date.now(), props: e });
1800
- const s = this.opts.maxBufferSize ?? lt;
1847
+ const s = this.opts.maxBufferSize ?? ct;
1801
1848
  if (this.buffer.length >= s) {
1802
1849
  this.flush();
1803
1850
  return;
@@ -1806,7 +1853,7 @@ class ft {
1806
1853
  }
1807
1854
  scheduleFlush() {
1808
1855
  if (this.flushTimer || this.destroyed) return;
1809
- const t = this.opts.flushIntervalMs ?? ct;
1856
+ const t = this.opts.flushIntervalMs ?? ht;
1810
1857
  this.flushTimer = setTimeout(() => {
1811
1858
  this.flushTimer = null, this.flush();
1812
1859
  }, t);
@@ -1817,7 +1864,7 @@ class ft {
1817
1864
  const t = this.buffer;
1818
1865
  this.buffer = [];
1819
1866
  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);
1867
+ 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
1868
  if (!n) return;
1822
1869
  await n(this.opts.endpoint, {
1823
1870
  method: "POST",
@@ -1825,7 +1872,7 @@ class ft {
1825
1872
  keepalive: !0,
1826
1873
  // если страница закроется в этот момент — браузер всё равно дотянет
1827
1874
  headers: this.buildHeaders(e, s),
1828
- body: i
1875
+ body: a
1829
1876
  });
1830
1877
  } catch {
1831
1878
  }
@@ -1845,7 +1892,7 @@ class ft {
1845
1892
  this.buffer.unshift(...t), this.flush();
1846
1893
  return;
1847
1894
  }
1848
- const i = JSON.stringify({
1895
+ const a = JSON.stringify({
1849
1896
  events: t,
1850
1897
  // body-level дубликаты для beacon-flow, читаются сервером как fallback.
1851
1898
  visitor_id: e,
@@ -1859,7 +1906,7 @@ class ft {
1859
1906
  return;
1860
1907
  }
1861
1908
  try {
1862
- n(this.opts.endpoint, i) || (this.buffer.unshift(...t), this.flush());
1909
+ n(this.opts.endpoint, a) || (this.buffer.unshift(...t), this.flush());
1863
1910
  } catch {
1864
1911
  this.buffer.unshift(...t), this.flush();
1865
1912
  }
@@ -1886,17 +1933,17 @@ class ft {
1886
1933
  }
1887
1934
  }
1888
1935
  export {
1889
- O as ApiClient,
1890
- K as ApiGatewayClient,
1891
- dt as AuthClient,
1892
- ut as BillingClient,
1893
- ft as EventTracker,
1936
+ E as ApiClient,
1937
+ q as ApiGatewayClient,
1938
+ ut as AuthClient,
1939
+ lt as BillingClient,
1940
+ dt as EventTracker,
1894
1941
  r as PaywallError,
1895
- q as QuotaExceededError,
1942
+ D as QuotaExceededError,
1896
1943
  m as SDK_VERSION,
1897
1944
  y as STORAGE_KEYS,
1898
1945
  C as createStorage,
1899
- S as ensureVisitorId,
1900
- E as generateVisitorId
1946
+ I as ensureVisitorId,
1947
+ N as generateVisitorId
1901
1948
  };
1902
1949
  //# sourceMappingURL=core.js.map