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