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