@pollar/core 0.6.0 → 0.7.1
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 +276 -42
- package/dist/adapters/expo-secure-store.d.mts +26 -0
- package/dist/adapters/expo-secure-store.d.ts +26 -0
- package/dist/adapters/expo-secure-store.js +53 -0
- package/dist/adapters/expo-secure-store.js.map +1 -0
- package/dist/adapters/expo-secure-store.mjs +50 -0
- package/dist/adapters/expo-secure-store.mjs.map +1 -0
- package/dist/adapters/react-native-keychain.d.mts +25 -0
- package/dist/adapters/react-native-keychain.d.ts +25 -0
- package/dist/adapters/react-native-keychain.js +60 -0
- package/dist/adapters/react-native-keychain.js.map +1 -0
- package/dist/adapters/react-native-keychain.mjs +57 -0
- package/dist/adapters/react-native-keychain.mjs.map +1 -0
- package/dist/index.d.mts +1101 -88
- package/dist/index.d.ts +1101 -88
- package/dist/index.js +871 -128
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +861 -129
- package/dist/index.mjs.map +1 -1
- package/dist/index.rn.d.mts +30 -0
- package/dist/index.rn.d.ts +30 -0
- package/dist/index.rn.js +2675 -0
- package/dist/index.rn.js.map +1 -0
- package/dist/index.rn.mjs +2645 -0
- package/dist/index.rn.mjs.map +1 -0
- package/dist/types-DqgJIJBl.d.mts +19 -0
- package/dist/types-DqgJIJBl.d.ts +19 -0
- package/package.json +54 -2
package/dist/index.mjs
CHANGED
|
@@ -169,6 +169,250 @@ var require_index_min = __commonJS({
|
|
|
169
169
|
}
|
|
170
170
|
});
|
|
171
171
|
|
|
172
|
+
// src/keys/factory.ts
|
|
173
|
+
var _factory = null;
|
|
174
|
+
function _setDefaultKeyManagerFactory(factory) {
|
|
175
|
+
_factory = factory;
|
|
176
|
+
}
|
|
177
|
+
function defaultKeyManager(storage, apiKey) {
|
|
178
|
+
if (!_factory) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
'[PollarClient] No default KeyManager factory registered. Did you import from "@pollar/core" via a non-standard path?'
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return _factory(storage, apiKey);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/lib/sha256.ts
|
|
187
|
+
async function sha256(data) {
|
|
188
|
+
const buf = await crypto.subtle.digest("SHA-256", data);
|
|
189
|
+
return new Uint8Array(buf);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/lib/api-key-hash.ts
|
|
193
|
+
async function hashApiKey(apiKey) {
|
|
194
|
+
const digest = await sha256(new TextEncoder().encode(apiKey));
|
|
195
|
+
let hex = "";
|
|
196
|
+
for (let i = 0; i < 4; i++) hex += digest[i].toString(16).padStart(2, "0");
|
|
197
|
+
return hex;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/lib/base64url.ts
|
|
201
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
202
|
+
(() => {
|
|
203
|
+
const m = /* @__PURE__ */ new Map();
|
|
204
|
+
for (let i = 0; i < ALPHABET.length; i++) m.set(ALPHABET[i], i);
|
|
205
|
+
return m;
|
|
206
|
+
})();
|
|
207
|
+
function base64urlEncode(bytes) {
|
|
208
|
+
let result = "";
|
|
209
|
+
let i = 0;
|
|
210
|
+
for (; i + 2 < bytes.length; i += 3) {
|
|
211
|
+
const b1 = bytes[i];
|
|
212
|
+
const b2 = bytes[i + 1];
|
|
213
|
+
const b3 = bytes[i + 2];
|
|
214
|
+
result += ALPHABET[b1 >> 2];
|
|
215
|
+
result += ALPHABET[(b1 & 3) << 4 | b2 >> 4];
|
|
216
|
+
result += ALPHABET[(b2 & 15) << 2 | b3 >> 6];
|
|
217
|
+
result += ALPHABET[b3 & 63];
|
|
218
|
+
}
|
|
219
|
+
if (i < bytes.length) {
|
|
220
|
+
const b1 = bytes[i];
|
|
221
|
+
if (i + 1 === bytes.length) {
|
|
222
|
+
result += ALPHABET[b1 >> 2];
|
|
223
|
+
result += ALPHABET[(b1 & 3) << 4];
|
|
224
|
+
} else {
|
|
225
|
+
const b2 = bytes[i + 1];
|
|
226
|
+
result += ALPHABET[b1 >> 2];
|
|
227
|
+
result += ALPHABET[(b1 & 3) << 4 | b2 >> 4];
|
|
228
|
+
result += ALPHABET[(b2 & 15) << 2];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
function base64urlEncodeString(s) {
|
|
234
|
+
return base64urlEncode(new TextEncoder().encode(s));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/keys/thumbprint.ts
|
|
238
|
+
async function computeJwkThumbprint(jwk) {
|
|
239
|
+
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || !jwk.x || !jwk.y) {
|
|
240
|
+
throw new Error("[PollarClient:thumbprint] Expected EC P-256 JWK with x and y");
|
|
241
|
+
}
|
|
242
|
+
const canonical = `{"crv":"${jwk.crv}","kty":"${jwk.kty}","x":"${jwk.x}","y":"${jwk.y}"}`;
|
|
243
|
+
const digest = await sha256(new TextEncoder().encode(canonical));
|
|
244
|
+
return base64urlEncode(digest);
|
|
245
|
+
}
|
|
246
|
+
function canonicalEcJwk(jwk) {
|
|
247
|
+
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || typeof jwk.y !== "string") {
|
|
248
|
+
throw new Error("[PollarClient:thumbprint] Source JWK is not an EC P-256 public key");
|
|
249
|
+
}
|
|
250
|
+
return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/keys/web-crypto.ts
|
|
254
|
+
var DB_NAME = "pollar-keys";
|
|
255
|
+
var DB_VERSION = 1;
|
|
256
|
+
var STORE_NAME = "keys";
|
|
257
|
+
function openDb() {
|
|
258
|
+
return new Promise((resolve, reject) => {
|
|
259
|
+
if (typeof indexedDB === "undefined") {
|
|
260
|
+
reject(new Error("[PollarClient:keys] IndexedDB not available"));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
264
|
+
req.onerror = () => reject(req.error ?? new Error("IDB open failed"));
|
|
265
|
+
req.onsuccess = () => resolve(req.result);
|
|
266
|
+
req.onupgradeneeded = () => {
|
|
267
|
+
const db = req.result;
|
|
268
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
269
|
+
db.createObjectStore(STORE_NAME);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function awaitTx(req) {
|
|
275
|
+
return new Promise((resolve, reject) => {
|
|
276
|
+
req.onsuccess = () => resolve(req.result);
|
|
277
|
+
req.onerror = () => reject(req.error ?? new Error("IDB request failed"));
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
async function dbGet(key) {
|
|
281
|
+
const db = await openDb();
|
|
282
|
+
try {
|
|
283
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
284
|
+
const result = await awaitTx(tx.objectStore(STORE_NAME).get(key));
|
|
285
|
+
return result;
|
|
286
|
+
} finally {
|
|
287
|
+
db.close();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function dbPut(key, value) {
|
|
291
|
+
const db = await openDb();
|
|
292
|
+
try {
|
|
293
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
294
|
+
await awaitTx(tx.objectStore(STORE_NAME).put(value, key));
|
|
295
|
+
} finally {
|
|
296
|
+
db.close();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function dbDelete(key) {
|
|
300
|
+
const db = await openDb();
|
|
301
|
+
try {
|
|
302
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
303
|
+
await awaitTx(tx.objectStore(STORE_NAME).delete(key));
|
|
304
|
+
} finally {
|
|
305
|
+
db.close();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function isCryptoKeyPair(v) {
|
|
309
|
+
if (typeof v !== "object" || v === null) return false;
|
|
310
|
+
const obj = v;
|
|
311
|
+
return obj.privateKey !== void 0 && obj.publicKey !== void 0;
|
|
312
|
+
}
|
|
313
|
+
var WebCryptoKeyManager = class {
|
|
314
|
+
constructor(apiKey) {
|
|
315
|
+
this.apiKeyHash = null;
|
|
316
|
+
this.keyPair = null;
|
|
317
|
+
this.publicJwk = null;
|
|
318
|
+
this.thumbprint = null;
|
|
319
|
+
/**
|
|
320
|
+
* Cached in-flight init. Lets `init()` be called concurrently (or implicitly
|
|
321
|
+
* from `getPublicJwk` / `sign`) without doing the work twice. Cleared on
|
|
322
|
+
* failure so callers can retry, and cleared on `reset()`.
|
|
323
|
+
*/
|
|
324
|
+
this._initPromise = null;
|
|
325
|
+
if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
|
|
326
|
+
throw new Error(
|
|
327
|
+
"[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost)."
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
this.apiKey = apiKey;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Idempotent and safe under concurrency. The first call kicks off the real
|
|
334
|
+
* init; subsequent (and concurrent) calls return the same in-flight promise.
|
|
335
|
+
* Other methods (`getPublicJwk`, `getThumbprint`, `sign`) auto-await this so
|
|
336
|
+
* the manager is self-healing if `init()` was never explicitly invoked.
|
|
337
|
+
*/
|
|
338
|
+
async init() {
|
|
339
|
+
if (this.keyPair) return;
|
|
340
|
+
if (!this._initPromise) {
|
|
341
|
+
this._initPromise = this._doInit().catch((err) => {
|
|
342
|
+
console.error("[PollarClient:keys] WebCryptoKeyManager init failed", err);
|
|
343
|
+
this._initPromise = null;
|
|
344
|
+
throw err;
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return this._initPromise;
|
|
348
|
+
}
|
|
349
|
+
async _doInit() {
|
|
350
|
+
if (!this.apiKeyHash) {
|
|
351
|
+
this.apiKeyHash = await hashApiKey(this.apiKey);
|
|
352
|
+
}
|
|
353
|
+
let pair;
|
|
354
|
+
try {
|
|
355
|
+
pair = await dbGet(this.apiKeyHash);
|
|
356
|
+
if (pair && !isCryptoKeyPair(pair)) pair = void 0;
|
|
357
|
+
} catch {
|
|
358
|
+
pair = void 0;
|
|
359
|
+
}
|
|
360
|
+
if (!pair) {
|
|
361
|
+
pair = await globalThis.crypto.subtle.generateKey(
|
|
362
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
363
|
+
// false → private key non-extractable; per W3C ECDSA spec the public
|
|
364
|
+
// key is always extractable regardless of this flag.
|
|
365
|
+
false,
|
|
366
|
+
["sign", "verify"]
|
|
367
|
+
);
|
|
368
|
+
try {
|
|
369
|
+
await dbPut(this.apiKeyHash, pair);
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
this.keyPair = pair;
|
|
374
|
+
const exported = await globalThis.crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
375
|
+
this.publicJwk = canonicalEcJwk(exported);
|
|
376
|
+
this.thumbprint = await computeJwkThumbprint(this.publicJwk);
|
|
377
|
+
}
|
|
378
|
+
async reset() {
|
|
379
|
+
try {
|
|
380
|
+
if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
|
|
381
|
+
} catch {
|
|
382
|
+
}
|
|
383
|
+
this.keyPair = null;
|
|
384
|
+
this.publicJwk = null;
|
|
385
|
+
this.thumbprint = null;
|
|
386
|
+
this._initPromise = null;
|
|
387
|
+
}
|
|
388
|
+
async getPublicJwk() {
|
|
389
|
+
if (!this.publicJwk) await this.init();
|
|
390
|
+
if (!this.publicJwk) {
|
|
391
|
+
throw new Error("[PollarClient:keys] Keypair initialization failed; getPublicJwk unavailable");
|
|
392
|
+
}
|
|
393
|
+
return { kty: this.publicJwk.kty, crv: this.publicJwk.crv, x: this.publicJwk.x, y: this.publicJwk.y };
|
|
394
|
+
}
|
|
395
|
+
async getThumbprint() {
|
|
396
|
+
if (!this.thumbprint) await this.init();
|
|
397
|
+
if (!this.thumbprint) {
|
|
398
|
+
throw new Error("[PollarClient:keys] Keypair initialization failed; getThumbprint unavailable");
|
|
399
|
+
}
|
|
400
|
+
return this.thumbprint;
|
|
401
|
+
}
|
|
402
|
+
async sign(payload) {
|
|
403
|
+
if (!this.keyPair) await this.init();
|
|
404
|
+
if (!this.keyPair) {
|
|
405
|
+
throw new Error("[PollarClient:keys] Keypair initialization failed; sign unavailable");
|
|
406
|
+
}
|
|
407
|
+
const sig = await globalThis.crypto.subtle.sign(
|
|
408
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
409
|
+
this.keyPair.privateKey,
|
|
410
|
+
payload
|
|
411
|
+
);
|
|
412
|
+
return new Uint8Array(sig);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
172
416
|
// ../../node_modules/openapi-fetch/dist/index.mjs
|
|
173
417
|
var PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
174
418
|
var supportsRequestInitExt = () => {
|
|
@@ -196,7 +440,7 @@ function createClient(clientOptions) {
|
|
|
196
440
|
const {
|
|
197
441
|
baseUrl: localBaseUrl,
|
|
198
442
|
fetch: fetch2 = baseFetch,
|
|
199
|
-
Request = CustomRequest,
|
|
443
|
+
Request: Request2 = CustomRequest,
|
|
200
444
|
headers,
|
|
201
445
|
params = {},
|
|
202
446
|
parseAs = "json",
|
|
@@ -248,7 +492,7 @@ function createClient(clientOptions) {
|
|
|
248
492
|
};
|
|
249
493
|
let id;
|
|
250
494
|
let options;
|
|
251
|
-
let request = new
|
|
495
|
+
let request = new Request2(
|
|
252
496
|
createFinalURL(schemaPath, { baseUrl: finalBaseUrl, params, querySerializer, pathSerializer }),
|
|
253
497
|
requestInit
|
|
254
498
|
);
|
|
@@ -278,7 +522,7 @@ function createClient(clientOptions) {
|
|
|
278
522
|
id
|
|
279
523
|
});
|
|
280
524
|
if (result) {
|
|
281
|
-
if (result instanceof
|
|
525
|
+
if (result instanceof Request2) {
|
|
282
526
|
request = result;
|
|
283
527
|
} else if (result instanceof Response) {
|
|
284
528
|
response = result;
|
|
@@ -650,6 +894,22 @@ function createApiClient(baseUrl) {
|
|
|
650
894
|
return createClient({ baseUrl });
|
|
651
895
|
}
|
|
652
896
|
|
|
897
|
+
// src/api/endpoints/distribution.ts
|
|
898
|
+
async function listDistributionRules(api) {
|
|
899
|
+
const { data, error } = await api.GET("/distribution/rules");
|
|
900
|
+
if (!data?.content || error) {
|
|
901
|
+
throw new Error(error?.error ?? "Failed to list distribution rules");
|
|
902
|
+
}
|
|
903
|
+
return data.content.rules;
|
|
904
|
+
}
|
|
905
|
+
async function claimDistributionRule(api, body) {
|
|
906
|
+
const { data, error } = await api.POST("/distribution/claim", { body });
|
|
907
|
+
if (!data?.content || error) {
|
|
908
|
+
throw new Error(error?.error ?? "Failed to claim distribution rule");
|
|
909
|
+
}
|
|
910
|
+
return data.content;
|
|
911
|
+
}
|
|
912
|
+
|
|
653
913
|
// src/api/endpoints/kyc.ts
|
|
654
914
|
async function getKycStatus(api, providerId) {
|
|
655
915
|
const { data, error } = await api.GET("/kyc/status", {
|
|
@@ -717,6 +977,68 @@ async function pollRampTransaction(api, txId, { intervalMs = 5e3, timeoutMs = 6e
|
|
|
717
977
|
throw new Error("Ramp transaction polling timed out");
|
|
718
978
|
}
|
|
719
979
|
|
|
980
|
+
// src/dpop.ts
|
|
981
|
+
async function buildProof(args, keyManager) {
|
|
982
|
+
const jwk = await keyManager.getPublicJwk();
|
|
983
|
+
const header = {
|
|
984
|
+
typ: "dpop+jwt",
|
|
985
|
+
alg: "ES256",
|
|
986
|
+
jwk
|
|
987
|
+
};
|
|
988
|
+
const payload = {
|
|
989
|
+
jti: generateJti(),
|
|
990
|
+
htm: args.htm.toUpperCase(),
|
|
991
|
+
htu: normalizeHtu(args.htu),
|
|
992
|
+
iat: Math.floor(Date.now() / 1e3)
|
|
993
|
+
};
|
|
994
|
+
if (args.accessToken !== void 0 && args.accessToken !== "") {
|
|
995
|
+
payload.ath = base64urlEncode(await sha256(new TextEncoder().encode(args.accessToken)));
|
|
996
|
+
}
|
|
997
|
+
if (args.nonce !== void 0 && args.nonce !== "") {
|
|
998
|
+
payload.nonce = args.nonce;
|
|
999
|
+
}
|
|
1000
|
+
const encodedHeader = base64urlEncodeString(JSON.stringify(header));
|
|
1001
|
+
const encodedPayload = base64urlEncodeString(JSON.stringify(payload));
|
|
1002
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
1003
|
+
const signature = await keyManager.sign(new TextEncoder().encode(signingInput));
|
|
1004
|
+
const encodedSignature = base64urlEncode(signature);
|
|
1005
|
+
return `${signingInput}.${encodedSignature}`;
|
|
1006
|
+
}
|
|
1007
|
+
function normalizeHtu(rawUrl) {
|
|
1008
|
+
let url;
|
|
1009
|
+
try {
|
|
1010
|
+
url = new URL(rawUrl);
|
|
1011
|
+
} catch {
|
|
1012
|
+
return rawUrl.split("#")[0].split("?")[0];
|
|
1013
|
+
}
|
|
1014
|
+
const scheme = url.protocol.toLowerCase();
|
|
1015
|
+
const host = url.hostname.toLowerCase();
|
|
1016
|
+
let port = url.port;
|
|
1017
|
+
if (scheme === "https:" && port === "443" || scheme === "http:" && port === "80") {
|
|
1018
|
+
port = "";
|
|
1019
|
+
}
|
|
1020
|
+
const portPart = port ? `:${port}` : "";
|
|
1021
|
+
return `${scheme}//${host}${portPart}${url.pathname}`;
|
|
1022
|
+
}
|
|
1023
|
+
function generateJti() {
|
|
1024
|
+
const c = globalThis.crypto;
|
|
1025
|
+
if (c && typeof c.randomUUID === "function") {
|
|
1026
|
+
return c.randomUUID();
|
|
1027
|
+
}
|
|
1028
|
+
if (c && typeof c.getRandomValues === "function") {
|
|
1029
|
+
const bytes = new Uint8Array(16);
|
|
1030
|
+
c.getRandomValues(bytes);
|
|
1031
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
1032
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1033
|
+
const hex = [];
|
|
1034
|
+
for (let i = 0; i < 16; i++) hex.push(bytes[i].toString(16).padStart(2, "0"));
|
|
1035
|
+
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
|
|
1036
|
+
}
|
|
1037
|
+
throw new Error(
|
|
1038
|
+
"[PollarClient:dpop] No secure random source available (crypto.randomUUID / crypto.getRandomValues). DPoP requires a secure context (HTTPS) or, in React Native, the `react-native-get-random-values` polyfill."
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
720
1042
|
// src/stellar/StellarClient.ts
|
|
721
1043
|
var HORIZON_URLS = {
|
|
722
1044
|
mainnet: "https://horizon.stellar.org",
|
|
@@ -745,6 +1067,93 @@ var StellarClient = class {
|
|
|
745
1067
|
}
|
|
746
1068
|
};
|
|
747
1069
|
|
|
1070
|
+
// src/storage/web.ts
|
|
1071
|
+
var LOG_PREFIX = "[PollarClient:storage]";
|
|
1072
|
+
function createMemoryAdapter() {
|
|
1073
|
+
const store = /* @__PURE__ */ new Map();
|
|
1074
|
+
return {
|
|
1075
|
+
async get(key) {
|
|
1076
|
+
const value = store.get(key);
|
|
1077
|
+
return value === void 0 ? null : value;
|
|
1078
|
+
},
|
|
1079
|
+
async set(key, value) {
|
|
1080
|
+
store.set(key, value);
|
|
1081
|
+
},
|
|
1082
|
+
async remove(key) {
|
|
1083
|
+
store.delete(key);
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
function createLocalStorageAdapter(options = {}) {
|
|
1088
|
+
const fallback = createMemoryAdapter();
|
|
1089
|
+
let degraded = false;
|
|
1090
|
+
function degrade(reason, error) {
|
|
1091
|
+
if (degraded) return;
|
|
1092
|
+
degraded = true;
|
|
1093
|
+
console.warn(`${LOG_PREFIX} localStorage unavailable (${reason}); degrading to in-memory storage`);
|
|
1094
|
+
options.onDegrade?.(reason, error);
|
|
1095
|
+
}
|
|
1096
|
+
return {
|
|
1097
|
+
async get(key) {
|
|
1098
|
+
if (degraded) return fallback.get(key);
|
|
1099
|
+
try {
|
|
1100
|
+
return globalThis.localStorage.getItem(key);
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
degrade("read-failed", error);
|
|
1103
|
+
return fallback.get(key);
|
|
1104
|
+
}
|
|
1105
|
+
},
|
|
1106
|
+
async set(key, value) {
|
|
1107
|
+
if (degraded) return fallback.set(key, value);
|
|
1108
|
+
try {
|
|
1109
|
+
globalThis.localStorage.setItem(key, value);
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
const reason = isQuotaError(error) ? "quota-exceeded" : "write-failed";
|
|
1112
|
+
degrade(reason, error);
|
|
1113
|
+
await fallback.set(key, value);
|
|
1114
|
+
}
|
|
1115
|
+
},
|
|
1116
|
+
async remove(key) {
|
|
1117
|
+
if (degraded) return fallback.remove(key);
|
|
1118
|
+
try {
|
|
1119
|
+
globalThis.localStorage.removeItem(key);
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
degrade("remove-failed", error);
|
|
1122
|
+
await fallback.remove(key);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function isQuotaError(error) {
|
|
1128
|
+
if (typeof error !== "object" || error === null) return false;
|
|
1129
|
+
const name = error.name;
|
|
1130
|
+
const code = error.code;
|
|
1131
|
+
return name === "QuotaExceededError" || name === "NS_ERROR_DOM_QUOTA_REACHED" || code === 22 || code === 1014;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// src/storage/autodetect.ts
|
|
1135
|
+
var PROBE_KEY = "__pollar_storage_probe__";
|
|
1136
|
+
function defaultStorage(options = {}) {
|
|
1137
|
+
if (typeof globalThis === "undefined" || typeof globalThis.localStorage === "undefined") {
|
|
1138
|
+
options.onDegrade?.("unavailable");
|
|
1139
|
+
return createMemoryAdapter();
|
|
1140
|
+
}
|
|
1141
|
+
try {
|
|
1142
|
+
const probeValue = String(Date.now());
|
|
1143
|
+
globalThis.localStorage.setItem(PROBE_KEY, probeValue);
|
|
1144
|
+
const read = globalThis.localStorage.getItem(PROBE_KEY);
|
|
1145
|
+
globalThis.localStorage.removeItem(PROBE_KEY);
|
|
1146
|
+
if (read !== probeValue) {
|
|
1147
|
+
options.onDegrade?.("probe-failed");
|
|
1148
|
+
return createMemoryAdapter();
|
|
1149
|
+
}
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
options.onDegrade?.("probe-failed", error);
|
|
1152
|
+
return createMemoryAdapter();
|
|
1153
|
+
}
|
|
1154
|
+
return createLocalStorageAdapter(options);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
748
1157
|
// src/types.ts
|
|
749
1158
|
var AUTH_ERROR_CODES = {
|
|
750
1159
|
SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
|
|
@@ -933,24 +1342,42 @@ var AlbedoAdapter = class {
|
|
|
933
1342
|
};
|
|
934
1343
|
|
|
935
1344
|
// src/client/session.ts
|
|
936
|
-
var
|
|
937
|
-
var
|
|
1345
|
+
var SESSION_SUFFIX = ":session";
|
|
1346
|
+
var WALLET_TYPE_SUFFIX = ":walletType";
|
|
1347
|
+
function sessionStorageKey(apiKeyHash) {
|
|
1348
|
+
return `pollar:${apiKeyHash}${SESSION_SUFFIX}`;
|
|
1349
|
+
}
|
|
1350
|
+
function walletTypeStorageKey(apiKeyHash) {
|
|
1351
|
+
return `pollar:${apiKeyHash}${WALLET_TYPE_SUFFIX}`;
|
|
1352
|
+
}
|
|
1353
|
+
var MAX_ACCESS_TOKEN = 4096;
|
|
1354
|
+
var MAX_REFRESH_TOKEN = 4096;
|
|
1355
|
+
var MAX_USER_ID = 64;
|
|
1356
|
+
var MAX_CLIENT_SESSION_ID = 64;
|
|
1357
|
+
var MAX_STATUS = 64;
|
|
1358
|
+
var MAX_WALLET_PUBLIC_KEY = 128;
|
|
1359
|
+
var MAX_WALLET_TYPE = 32;
|
|
1360
|
+
function isBoundedString(v, max, allowEmpty = false) {
|
|
1361
|
+
if (typeof v !== "string") return false;
|
|
1362
|
+
if (!allowEmpty && v.length === 0) return false;
|
|
1363
|
+
return v.length <= max;
|
|
1364
|
+
}
|
|
938
1365
|
function isValidSession(value) {
|
|
939
1366
|
if (typeof value !== "object" || value === null) {
|
|
940
1367
|
console.warn("[PollarClient:session] Invalid session \u2014 value is not an object");
|
|
941
1368
|
return false;
|
|
942
1369
|
}
|
|
943
1370
|
const s = value;
|
|
944
|
-
if (
|
|
945
|
-
console.warn("[PollarClient:session] Invalid session \u2014 clientSessionId missing
|
|
1371
|
+
if (!isBoundedString(s["clientSessionId"], MAX_CLIENT_SESSION_ID)) {
|
|
1372
|
+
console.warn("[PollarClient:session] Invalid session \u2014 clientSessionId missing/empty/too long");
|
|
946
1373
|
return false;
|
|
947
1374
|
}
|
|
948
|
-
if (s["userId"] !== null &&
|
|
949
|
-
console.warn("[PollarClient:session] Invalid session \u2014 userId must be string
|
|
1375
|
+
if (s["userId"] !== null && !isBoundedString(s["userId"], MAX_USER_ID)) {
|
|
1376
|
+
console.warn("[PollarClient:session] Invalid session \u2014 userId must be string|null");
|
|
950
1377
|
return false;
|
|
951
1378
|
}
|
|
952
|
-
if (
|
|
953
|
-
console.warn("[PollarClient:session] Invalid session \u2014 status must be string
|
|
1379
|
+
if (!isBoundedString(s["status"], MAX_STATUS)) {
|
|
1380
|
+
console.warn("[PollarClient:session] Invalid session \u2014 status must be string");
|
|
954
1381
|
return false;
|
|
955
1382
|
}
|
|
956
1383
|
const token = s["token"];
|
|
@@ -959,12 +1386,12 @@ function isValidSession(value) {
|
|
|
959
1386
|
return false;
|
|
960
1387
|
}
|
|
961
1388
|
const t = token;
|
|
962
|
-
if (
|
|
963
|
-
console.warn("[PollarClient:session] Invalid session \u2014 token.accessToken missing
|
|
1389
|
+
if (!isBoundedString(t["accessToken"], MAX_ACCESS_TOKEN)) {
|
|
1390
|
+
console.warn("[PollarClient:session] Invalid session \u2014 token.accessToken missing/empty/too long");
|
|
964
1391
|
return false;
|
|
965
1392
|
}
|
|
966
|
-
if (
|
|
967
|
-
console.warn("[PollarClient:session] Invalid session \u2014 token.refreshToken missing
|
|
1393
|
+
if (!isBoundedString(t["refreshToken"], MAX_REFRESH_TOKEN)) {
|
|
1394
|
+
console.warn("[PollarClient:session] Invalid session \u2014 token.refreshToken missing/empty/too long");
|
|
968
1395
|
return false;
|
|
969
1396
|
}
|
|
970
1397
|
if (typeof t["expiresAt"] !== "number" || !Number.isFinite(t["expiresAt"])) {
|
|
@@ -977,12 +1404,12 @@ function isValidSession(value) {
|
|
|
977
1404
|
return false;
|
|
978
1405
|
}
|
|
979
1406
|
const u = user;
|
|
980
|
-
if (u["id"] !== void 0 &&
|
|
981
|
-
console.warn("[PollarClient:session] Invalid session \u2014 user.id must be string if present
|
|
1407
|
+
if (u["id"] !== void 0 && !isBoundedString(u["id"], MAX_USER_ID)) {
|
|
1408
|
+
console.warn("[PollarClient:session] Invalid session \u2014 user.id must be string if present");
|
|
982
1409
|
return false;
|
|
983
1410
|
}
|
|
984
1411
|
if (typeof u["ready"] !== "boolean") {
|
|
985
|
-
console.warn("[PollarClient:session] Invalid session \u2014 user.ready must be boolean
|
|
1412
|
+
console.warn("[PollarClient:session] Invalid session \u2014 user.ready must be boolean");
|
|
986
1413
|
return false;
|
|
987
1414
|
}
|
|
988
1415
|
const wallet = s["wallet"];
|
|
@@ -991,11 +1418,8 @@ function isValidSession(value) {
|
|
|
991
1418
|
return false;
|
|
992
1419
|
}
|
|
993
1420
|
const w = wallet;
|
|
994
|
-
if (w["publicKey"] !== null &&
|
|
995
|
-
console.warn(
|
|
996
|
-
"[PollarClient:session] Invalid session \u2014 wallet.publicKey must be string or null, got:",
|
|
997
|
-
typeof w["publicKey"]
|
|
998
|
-
);
|
|
1421
|
+
if (w["publicKey"] !== null && !isBoundedString(w["publicKey"], MAX_WALLET_PUBLIC_KEY)) {
|
|
1422
|
+
console.warn("[PollarClient:session] Invalid session \u2014 wallet.publicKey must be string|null");
|
|
999
1423
|
return false;
|
|
1000
1424
|
}
|
|
1001
1425
|
if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
|
|
@@ -1006,78 +1430,43 @@ function isValidSession(value) {
|
|
|
1006
1430
|
console.warn("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
|
|
1007
1431
|
return false;
|
|
1008
1432
|
}
|
|
1009
|
-
const data = s["data"];
|
|
1010
|
-
if (typeof data !== "object" || data === null) {
|
|
1011
|
-
console.warn("[PollarClient:session] Invalid session \u2014 data missing or not an object");
|
|
1012
|
-
return false;
|
|
1013
|
-
}
|
|
1014
|
-
const d = data;
|
|
1015
|
-
for (const field of ["mail", "first_name", "last_name", "avatar"]) {
|
|
1016
|
-
if (typeof d[field] !== "string") {
|
|
1017
|
-
console.warn(`[PollarClient:session] Invalid session \u2014 data.${field} must be string, got:`, typeof d[field]);
|
|
1018
|
-
return false;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
const providers = d["providers"];
|
|
1022
|
-
if (typeof providers !== "object" || providers === null) {
|
|
1023
|
-
console.warn("[PollarClient:session] Invalid session \u2014 data.providers missing or not an object");
|
|
1024
|
-
return false;
|
|
1025
|
-
}
|
|
1026
|
-
const p = providers;
|
|
1027
|
-
const providerInnerField = { email: "address", google: "id", github: "id", wallet: "address" };
|
|
1028
|
-
for (const [field, innerField] of Object.entries(providerInnerField)) {
|
|
1029
|
-
const v = p[field];
|
|
1030
|
-
if (v === null) continue;
|
|
1031
|
-
if (typeof v !== "object") {
|
|
1032
|
-
console.warn(`[PollarClient:session] Invalid session \u2014 data.providers.${field} must be object or null, got:`, typeof v);
|
|
1033
|
-
return false;
|
|
1034
|
-
}
|
|
1035
|
-
const vObj = v;
|
|
1036
|
-
if (typeof vObj[innerField] !== "string" || vObj[innerField] === "") {
|
|
1037
|
-
console.warn(`[PollarClient:session] Invalid session \u2014 data.providers.${field}.${innerField} must be a non-empty string`);
|
|
1038
|
-
return false;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
1433
|
return true;
|
|
1042
1434
|
}
|
|
1043
|
-
function readStorage() {
|
|
1044
|
-
const raw =
|
|
1045
|
-
if (!raw)
|
|
1046
|
-
return null;
|
|
1047
|
-
}
|
|
1435
|
+
async function readStorage(storage, apiKeyHash) {
|
|
1436
|
+
const raw = await storage.get(sessionStorageKey(apiKeyHash));
|
|
1437
|
+
if (!raw) return null;
|
|
1048
1438
|
try {
|
|
1049
1439
|
const session = JSON.parse(raw);
|
|
1050
1440
|
if (!isValidSession(session)) {
|
|
1051
|
-
|
|
1441
|
+
await storage.remove(sessionStorageKey(apiKeyHash));
|
|
1052
1442
|
console.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
|
|
1053
1443
|
return null;
|
|
1054
1444
|
}
|
|
1055
1445
|
if (session.token.expiresAt * 1e3 < Date.now()) {
|
|
1056
|
-
|
|
1057
|
-
console.warn("[PollarClient:session] Session token has expired \u2014 clearing storage");
|
|
1058
|
-
return null;
|
|
1446
|
+
return session;
|
|
1059
1447
|
}
|
|
1060
1448
|
return session;
|
|
1061
1449
|
} catch (error) {
|
|
1062
1450
|
console.error("[PollarClient:session] Failed to parse session from storage", error);
|
|
1063
|
-
|
|
1451
|
+
await storage.remove(sessionStorageKey(apiKeyHash));
|
|
1064
1452
|
return null;
|
|
1065
1453
|
}
|
|
1066
1454
|
}
|
|
1067
|
-
function writeStorage(session) {
|
|
1068
|
-
|
|
1069
|
-
console.info("[PollarClient:session] Session written to storage");
|
|
1455
|
+
async function writeStorage(storage, apiKeyHash, session) {
|
|
1456
|
+
await storage.set(sessionStorageKey(apiKeyHash), JSON.stringify(session));
|
|
1070
1457
|
}
|
|
1071
|
-
function removeStorage() {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
console.info("[PollarClient:session] Session removed from storage");
|
|
1458
|
+
async function removeStorage(storage, apiKeyHash) {
|
|
1459
|
+
await storage.remove(sessionStorageKey(apiKeyHash));
|
|
1460
|
+
await storage.remove(walletTypeStorageKey(apiKeyHash));
|
|
1075
1461
|
}
|
|
1076
|
-
function writeWalletType(type) {
|
|
1077
|
-
|
|
1462
|
+
async function writeWalletType(storage, apiKeyHash, type) {
|
|
1463
|
+
if (type.length > MAX_WALLET_TYPE) {
|
|
1464
|
+
throw new Error(`[PollarClient:session] walletType too long: ${type.length} > ${MAX_WALLET_TYPE}`);
|
|
1465
|
+
}
|
|
1466
|
+
await storage.set(walletTypeStorageKey(apiKeyHash), type);
|
|
1078
1467
|
}
|
|
1079
|
-
function readWalletType() {
|
|
1080
|
-
return
|
|
1468
|
+
async function readWalletType(storage, apiKeyHash) {
|
|
1469
|
+
return storage.get(walletTypeStorageKey(apiKeyHash));
|
|
1081
1470
|
}
|
|
1082
1471
|
|
|
1083
1472
|
// src/client/stream.ts
|
|
@@ -1094,7 +1483,14 @@ function abortableDelay(ms, signal) {
|
|
|
1094
1483
|
);
|
|
1095
1484
|
});
|
|
1096
1485
|
}
|
|
1486
|
+
var MAX_BACKOFF_MS = 5e3;
|
|
1097
1487
|
async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200, signal) {
|
|
1488
|
+
let backoff = retryDelayMs;
|
|
1489
|
+
const sleep = async (ms) => {
|
|
1490
|
+
if (ms <= 0) return;
|
|
1491
|
+
if (signal) await abortableDelay(ms, signal);
|
|
1492
|
+
else await new Promise((r) => setTimeout(r, ms));
|
|
1493
|
+
};
|
|
1098
1494
|
while (true) {
|
|
1099
1495
|
signal?.throwIfAborted();
|
|
1100
1496
|
let data, error;
|
|
@@ -1109,13 +1505,14 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1109
1505
|
console.warn(e);
|
|
1110
1506
|
}
|
|
1111
1507
|
if (error || !data) {
|
|
1112
|
-
|
|
1113
|
-
|
|
1508
|
+
await sleep(backoff);
|
|
1509
|
+
backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
|
|
1114
1510
|
continue;
|
|
1115
1511
|
}
|
|
1116
1512
|
const reader = data.getReader();
|
|
1117
1513
|
const decoder = new TextDecoder();
|
|
1118
1514
|
let streamDone = false;
|
|
1515
|
+
let sawAnyChunk = false;
|
|
1119
1516
|
try {
|
|
1120
1517
|
while (true) {
|
|
1121
1518
|
signal?.throwIfAborted();
|
|
@@ -1124,6 +1521,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1124
1521
|
streamDone = true;
|
|
1125
1522
|
break;
|
|
1126
1523
|
}
|
|
1524
|
+
sawAnyChunk = true;
|
|
1127
1525
|
const chunk = decoder.decode(value);
|
|
1128
1526
|
for (const message of chunk.split("\n\n").filter(Boolean)) {
|
|
1129
1527
|
const dataLine = message.split("\n").find((l) => l.startsWith("data:"));
|
|
@@ -1143,11 +1541,10 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1143
1541
|
} finally {
|
|
1144
1542
|
reader.releaseLock();
|
|
1145
1543
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
}
|
|
1544
|
+
if (sawAnyChunk) backoff = retryDelayMs;
|
|
1545
|
+
else backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
|
|
1546
|
+
const delay = streamDone ? backoff : 0;
|
|
1547
|
+
if (delay) await sleep(delay);
|
|
1151
1548
|
}
|
|
1152
1549
|
}
|
|
1153
1550
|
|
|
@@ -1156,8 +1553,13 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
|
|
|
1156
1553
|
const { api, signal, setAuthState, storeSession, clearSession } = deps;
|
|
1157
1554
|
setAuthState({ step: "authenticating" });
|
|
1158
1555
|
await streamUntilFound(api, clientSessionId, (data2) => data2?.status === "READY", 200, signal);
|
|
1556
|
+
const dpopJwk = await deps.getPublicJwk();
|
|
1159
1557
|
const { data, error } = await api.POST("/auth/login", {
|
|
1160
|
-
body: {
|
|
1558
|
+
body: {
|
|
1559
|
+
clientSessionId,
|
|
1560
|
+
dpopJwk,
|
|
1561
|
+
...deps.deviceLabel ? { deviceLabel: deps.deviceLabel } : {}
|
|
1562
|
+
},
|
|
1161
1563
|
signal
|
|
1162
1564
|
});
|
|
1163
1565
|
if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content)) {
|
|
@@ -1267,9 +1669,17 @@ async function verifyAndAuthenticate(code, clientSessionId, email, deps) {
|
|
|
1267
1669
|
}
|
|
1268
1670
|
|
|
1269
1671
|
// src/client/auth/oauthFlow.ts
|
|
1672
|
+
function severOpener(popup) {
|
|
1673
|
+
if (!popup) return;
|
|
1674
|
+
try {
|
|
1675
|
+
popup.opener = null;
|
|
1676
|
+
} catch {
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1270
1679
|
async function loginOAuth(provider, deps) {
|
|
1271
1680
|
const { setAuthState, basePath, apiKey } = deps;
|
|
1272
1681
|
const popup = window.open("about:blank", "_blank");
|
|
1682
|
+
severOpener(popup);
|
|
1273
1683
|
const clientSessionId = await createAuthSession(deps);
|
|
1274
1684
|
if (!clientSessionId) {
|
|
1275
1685
|
popup?.close();
|
|
@@ -1282,8 +1692,9 @@ async function loginOAuth(provider, deps) {
|
|
|
1282
1692
|
url.searchParams.set("redirect_uri", window.location.origin);
|
|
1283
1693
|
if (popup) {
|
|
1284
1694
|
popup.location.href = url.toString();
|
|
1695
|
+
severOpener(popup);
|
|
1285
1696
|
} else {
|
|
1286
|
-
window.open(url.toString(), "_blank");
|
|
1697
|
+
window.open(url.toString(), "_blank", "noopener,noreferrer");
|
|
1287
1698
|
}
|
|
1288
1699
|
await authenticate(clientSessionId, deps);
|
|
1289
1700
|
}
|
|
@@ -1308,7 +1719,7 @@ async function loginWallet(type, deps) {
|
|
|
1308
1719
|
let connectedWallet;
|
|
1309
1720
|
try {
|
|
1310
1721
|
setAuthState({ step: "connecting_wallet", walletType: type });
|
|
1311
|
-
const adapter =
|
|
1722
|
+
const adapter = await deps.resolveWalletAdapter(type);
|
|
1312
1723
|
const available = await withSignal(adapter.isAvailable(), signal);
|
|
1313
1724
|
if (!available) {
|
|
1314
1725
|
setAuthState({ step: "wallet_not_installed", walletType: type });
|
|
@@ -1352,7 +1763,26 @@ function warnServerSide(method) {
|
|
|
1352
1763
|
}
|
|
1353
1764
|
var PollarClient = class {
|
|
1354
1765
|
constructor(config) {
|
|
1766
|
+
/**
|
|
1767
|
+
* Per-API-key storage namespace. Computed asynchronously inside
|
|
1768
|
+
* `_initialize()` because SHA-256 lives behind `crypto.subtle.digest`.
|
|
1769
|
+
* Accessing `apiKeyHash` before `await client.ready()` throws.
|
|
1770
|
+
*/
|
|
1771
|
+
this._apiKeyHash = null;
|
|
1355
1772
|
this._session = null;
|
|
1773
|
+
this._profile = null;
|
|
1774
|
+
/** Last `DPoP-Nonce` we saw from a server response. Carried into the next proof. */
|
|
1775
|
+
this._dpopNonce = null;
|
|
1776
|
+
/**
|
|
1777
|
+
* Snapshot of each in-flight request's body, taken in `onRequest` before
|
|
1778
|
+
* `fetch()` consumes the stream. Needed because `Request.clone()` throws
|
|
1779
|
+
* once the body is disturbed, so the auto-retry path (DPoP nonce challenge
|
|
1780
|
+
* / 401 refresh) must rebuild the request from scratch instead of cloning.
|
|
1781
|
+
*/
|
|
1782
|
+
this._requestBodyCache = /* @__PURE__ */ new WeakMap();
|
|
1783
|
+
/** Singleton in-flight refresh — concurrent 401s coalesce into one /auth/refresh call. */
|
|
1784
|
+
this._refreshPromise = null;
|
|
1785
|
+
this._storageEventHandler = null;
|
|
1356
1786
|
this._transactionState = null;
|
|
1357
1787
|
this._transactionStateListeners = /* @__PURE__ */ new Set();
|
|
1358
1788
|
this._txHistoryState = { step: "idle" };
|
|
@@ -1368,33 +1798,213 @@ var PollarClient = class {
|
|
|
1368
1798
|
this.apiKey = config.apiKey;
|
|
1369
1799
|
this.id = crypto.randomUUID();
|
|
1370
1800
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
1801
|
+
this._storage = config.storage ?? defaultStorage(config.onStorageDegrade ? { onDegrade: config.onStorageDegrade } : void 0);
|
|
1802
|
+
this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
|
|
1803
|
+
this._walletAdapterResolver = config.walletAdapter ?? null;
|
|
1804
|
+
this._deviceLabel = config.deviceLabel;
|
|
1371
1805
|
this._api = createApiClient(this.basePath);
|
|
1806
|
+
this._wireMiddlewares();
|
|
1807
|
+
this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
|
|
1808
|
+
if (!isBrowser) {
|
|
1809
|
+
warnServerSide("constructor");
|
|
1810
|
+
this._initialized = Promise.resolve();
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
console.info(`[PollarClient] Initialized \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`);
|
|
1814
|
+
this._initialized = this._initialize();
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Short SHA-256-derived namespace for this client's persisted state.
|
|
1818
|
+
* Available after `await client.ready()` (or any awaited method); throws
|
|
1819
|
+
* if read before initialization completes.
|
|
1820
|
+
*/
|
|
1821
|
+
get apiKeyHash() {
|
|
1822
|
+
if (this._apiKeyHash === null) {
|
|
1823
|
+
throw new Error("[PollarClient] apiKeyHash is not available until client.ready() resolves");
|
|
1824
|
+
}
|
|
1825
|
+
return this._apiKeyHash;
|
|
1826
|
+
}
|
|
1827
|
+
/** Awaitable handle for the initial keypair + session restore. */
|
|
1828
|
+
ready() {
|
|
1829
|
+
return this._initialized;
|
|
1830
|
+
}
|
|
1831
|
+
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
|
1832
|
+
async _initialize() {
|
|
1833
|
+
this._apiKeyHash = await hashApiKey(this.apiKey);
|
|
1834
|
+
if (typeof window !== "undefined") {
|
|
1835
|
+
const sessionKey = sessionStorageKey(this._apiKeyHash);
|
|
1836
|
+
const handler = (e) => {
|
|
1837
|
+
if (e.key === sessionKey) {
|
|
1838
|
+
this._restoreSession().catch((err) => console.error("[PollarClient] Cross-tab restore failed", err));
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
window.addEventListener("storage", handler);
|
|
1842
|
+
this._storageEventHandler = handler;
|
|
1843
|
+
}
|
|
1844
|
+
try {
|
|
1845
|
+
await this._keyManager.init();
|
|
1846
|
+
} catch (err) {
|
|
1847
|
+
console.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
|
|
1848
|
+
}
|
|
1849
|
+
await this._restoreSession();
|
|
1850
|
+
}
|
|
1851
|
+
/** Detach the cross-tab storage listener and abort any in-flight login. */
|
|
1852
|
+
destroy() {
|
|
1853
|
+
if (this._storageEventHandler && typeof window !== "undefined") {
|
|
1854
|
+
window.removeEventListener("storage", this._storageEventHandler);
|
|
1855
|
+
this._storageEventHandler = null;
|
|
1856
|
+
}
|
|
1857
|
+
this._loginController?.abort();
|
|
1858
|
+
this._loginController = null;
|
|
1859
|
+
}
|
|
1860
|
+
// ─── Middlewares (DPoP + auto-refresh) ────────────────────────────────────
|
|
1861
|
+
_wireMiddlewares() {
|
|
1372
1862
|
const self = this;
|
|
1373
1863
|
this._api.use({
|
|
1374
|
-
onRequest({ request }) {
|
|
1375
|
-
request.headers.set("x-pollar-api-key",
|
|
1864
|
+
onRequest: async ({ request }) => {
|
|
1865
|
+
request.headers.set("x-pollar-api-key", self.apiKey);
|
|
1866
|
+
await self._initialized;
|
|
1867
|
+
if (request.body !== null) {
|
|
1868
|
+
try {
|
|
1869
|
+
self._requestBodyCache.set(request, await request.clone().arrayBuffer());
|
|
1870
|
+
} catch (err) {
|
|
1871
|
+
console.warn("[PollarClient] Could not snapshot request body for retry", err);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
const isRefresh = request.url.includes("/auth/refresh");
|
|
1875
|
+
if (!isRefresh && self._refreshPromise) await self._refreshPromise;
|
|
1876
|
+
if (isRefresh) {
|
|
1877
|
+
const refreshProof = await self._buildProofForRequest(request, void 0);
|
|
1878
|
+
if (refreshProof) request.headers.set("DPoP", refreshProof);
|
|
1879
|
+
return request;
|
|
1880
|
+
}
|
|
1376
1881
|
const accessToken = self._session?.token?.accessToken;
|
|
1377
|
-
if (accessToken)
|
|
1882
|
+
if (!accessToken) return request;
|
|
1883
|
+
const proof = await self._buildProofForRequest(request, accessToken);
|
|
1884
|
+
if (proof) {
|
|
1885
|
+
request.headers.set("Authorization", `DPoP ${accessToken}`);
|
|
1886
|
+
request.headers.set("DPoP", proof);
|
|
1887
|
+
} else {
|
|
1378
1888
|
request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
1379
1889
|
}
|
|
1380
1890
|
return request;
|
|
1891
|
+
},
|
|
1892
|
+
onResponse: async ({ request, response }) => {
|
|
1893
|
+
const newNonce = response.headers.get("DPoP-Nonce");
|
|
1894
|
+
if (newNonce) self._dpopNonce = newNonce;
|
|
1895
|
+
if (response.status !== 401) return response;
|
|
1896
|
+
if (request.url.includes("/auth/refresh")) return response;
|
|
1897
|
+
const wwwAuth = response.headers.get("WWW-Authenticate") ?? "";
|
|
1898
|
+
const isNonceChallenge = wwwAuth.includes("use_dpop_nonce");
|
|
1899
|
+
if (!isNonceChallenge) {
|
|
1900
|
+
try {
|
|
1901
|
+
await self.refresh();
|
|
1902
|
+
} catch {
|
|
1903
|
+
return response;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
return self._retryRequest(request);
|
|
1381
1907
|
}
|
|
1382
1908
|
});
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
return
|
|
1909
|
+
}
|
|
1910
|
+
async _buildProofForRequest(request, accessToken) {
|
|
1911
|
+
try {
|
|
1912
|
+
const htu = request.url.split("?")[0].split("#")[0];
|
|
1913
|
+
return await buildProof(
|
|
1914
|
+
{
|
|
1915
|
+
htm: request.method,
|
|
1916
|
+
htu,
|
|
1917
|
+
...accessToken ? { accessToken } : {},
|
|
1918
|
+
...this._dpopNonce !== null ? { nonce: this._dpopNonce } : {}
|
|
1919
|
+
},
|
|
1920
|
+
this._keyManager
|
|
1921
|
+
);
|
|
1922
|
+
} catch (err) {
|
|
1923
|
+
console.warn("[PollarClient] DPoP proof build failed", err);
|
|
1924
|
+
return null;
|
|
1388
1925
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1926
|
+
}
|
|
1927
|
+
async _retryRequest(originalRequest) {
|
|
1928
|
+
const headers = new Headers(originalRequest.headers);
|
|
1929
|
+
const accessToken = this._session?.token?.accessToken;
|
|
1930
|
+
if (accessToken) {
|
|
1931
|
+
const proof = await this._buildProofForRequest(originalRequest, accessToken);
|
|
1932
|
+
if (proof) {
|
|
1933
|
+
headers.set("Authorization", `DPoP ${accessToken}`);
|
|
1934
|
+
headers.set("DPoP", proof);
|
|
1935
|
+
} else {
|
|
1936
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
1396
1937
|
}
|
|
1938
|
+
}
|
|
1939
|
+
const cachedBody = this._requestBodyCache.get(originalRequest);
|
|
1940
|
+
const retried = new Request(originalRequest.url, {
|
|
1941
|
+
method: originalRequest.method,
|
|
1942
|
+
headers,
|
|
1943
|
+
body: cachedBody ?? null,
|
|
1944
|
+
credentials: originalRequest.credentials,
|
|
1945
|
+
mode: originalRequest.mode,
|
|
1946
|
+
redirect: originalRequest.redirect,
|
|
1947
|
+
referrer: originalRequest.referrer,
|
|
1948
|
+
integrity: originalRequest.integrity
|
|
1949
|
+
});
|
|
1950
|
+
return fetch(retried);
|
|
1951
|
+
}
|
|
1952
|
+
// ─── Refresh (race-safe singleton) ───────────────────────────────────────
|
|
1953
|
+
/**
|
|
1954
|
+
* Coalesce concurrent refresh attempts. The first caller does the work;
|
|
1955
|
+
* everyone else awaits the same promise and sees the new tokens.
|
|
1956
|
+
*/
|
|
1957
|
+
refresh() {
|
|
1958
|
+
if (this._refreshPromise) return this._refreshPromise;
|
|
1959
|
+
this._refreshPromise = this._doRefresh().finally(() => {
|
|
1960
|
+
this._refreshPromise = null;
|
|
1397
1961
|
});
|
|
1962
|
+
return this._refreshPromise;
|
|
1963
|
+
}
|
|
1964
|
+
async _doRefresh() {
|
|
1965
|
+
const refreshToken = this._session?.token?.refreshToken;
|
|
1966
|
+
if (!refreshToken) {
|
|
1967
|
+
console.warn("[PollarClient] Refresh skipped: no refresh token in session");
|
|
1968
|
+
await this._clearSession();
|
|
1969
|
+
throw new Error("No refresh token available");
|
|
1970
|
+
}
|
|
1971
|
+
let data;
|
|
1972
|
+
let error;
|
|
1973
|
+
try {
|
|
1974
|
+
const response = await this._api.POST("/auth/refresh", { body: { refreshToken } });
|
|
1975
|
+
data = response.data;
|
|
1976
|
+
error = response.error;
|
|
1977
|
+
} catch (err) {
|
|
1978
|
+
console.error("[PollarClient] /auth/refresh request threw", err);
|
|
1979
|
+
await this._clearSession();
|
|
1980
|
+
throw err;
|
|
1981
|
+
}
|
|
1982
|
+
if (error || !data) {
|
|
1983
|
+
console.warn("[PollarClient] /auth/refresh returned error", { error });
|
|
1984
|
+
await this._clearSession();
|
|
1985
|
+
throw new Error("Refresh failed");
|
|
1986
|
+
}
|
|
1987
|
+
const successData = data;
|
|
1988
|
+
if (!successData.success || !successData.content?.token) {
|
|
1989
|
+
console.warn("[PollarClient] /auth/refresh response malformed", successData);
|
|
1990
|
+
await this._clearSession();
|
|
1991
|
+
throw new Error("Refresh response malformed");
|
|
1992
|
+
}
|
|
1993
|
+
const newToken = successData.content.token;
|
|
1994
|
+
if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
|
|
1995
|
+
console.warn("[PollarClient] /auth/refresh token shape invalid", newToken);
|
|
1996
|
+
await this._clearSession();
|
|
1997
|
+
throw new Error("Refresh response token shape invalid");
|
|
1998
|
+
}
|
|
1999
|
+
if (this._session) {
|
|
2000
|
+
try {
|
|
2001
|
+
this._session = { ...this._session, token: newToken };
|
|
2002
|
+
await writeStorage(this._storage, this.apiKeyHash, this._session);
|
|
2003
|
+
console.info("[PollarClient] Tokens refreshed");
|
|
2004
|
+
} catch (err) {
|
|
2005
|
+
console.error("[PollarClient] Failed to persist refreshed session", err);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
1398
2008
|
}
|
|
1399
2009
|
// ─── Auth state ──────────────────────────────────────────────────────────────
|
|
1400
2010
|
getAuthState() {
|
|
@@ -1405,6 +2015,10 @@ var PollarClient = class {
|
|
|
1405
2015
|
cb(this._authState);
|
|
1406
2016
|
return () => this._authStateListeners.delete(cb);
|
|
1407
2017
|
}
|
|
2018
|
+
/** PII (email, names, avatar, providers). Held in memory only — never persisted. */
|
|
2019
|
+
getUserProfile() {
|
|
2020
|
+
return this._profile;
|
|
2021
|
+
}
|
|
1408
2022
|
// ─── Login (unified entry point) ─────────────────────────────────────────
|
|
1409
2023
|
login(options) {
|
|
1410
2024
|
if (!isBrowser) {
|
|
@@ -1486,13 +2100,80 @@ var PollarClient = class {
|
|
|
1486
2100
|
this._setAuthState({ step: "idle" });
|
|
1487
2101
|
}
|
|
1488
2102
|
// ─── Logout ───────────────────────────────────────────────────────────────
|
|
1489
|
-
|
|
2103
|
+
/**
|
|
2104
|
+
* Revoke the current session server-side, then clear local storage.
|
|
2105
|
+
*
|
|
2106
|
+
* Server revocation is best-effort: if the POST fails (offline, server
|
|
2107
|
+
* down), local state is wiped regardless. The orphan refresh token then
|
|
2108
|
+
* remains unused until its natural expiry. The in-flight access token
|
|
2109
|
+
* stays valid until its own TTL elapses (≤10 min for DPoP-bound tokens).
|
|
2110
|
+
*
|
|
2111
|
+
* Pass `everywhere: true` to revoke every active session for this user
|
|
2112
|
+
* across all devices.
|
|
2113
|
+
*/
|
|
2114
|
+
async logout(options = {}) {
|
|
1490
2115
|
if (!isBrowser) {
|
|
1491
2116
|
warnServerSide("logout");
|
|
1492
2117
|
return;
|
|
1493
2118
|
}
|
|
1494
|
-
console.info("[PollarClient] Logout requested");
|
|
1495
|
-
this.
|
|
2119
|
+
console.info("[PollarClient] Logout requested", { everywhere: !!options.everywhere });
|
|
2120
|
+
if (this._session?.token?.accessToken) {
|
|
2121
|
+
try {
|
|
2122
|
+
await this._api.POST("/auth/logout", {
|
|
2123
|
+
body: options.everywhere ? { everywhere: true } : {}
|
|
2124
|
+
});
|
|
2125
|
+
} catch (err) {
|
|
2126
|
+
console.warn("[PollarClient] Server logout failed (continuing with local clear)", err);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
try {
|
|
2130
|
+
await this._clearSession();
|
|
2131
|
+
} catch (err) {
|
|
2132
|
+
console.warn("[PollarClient] Local logout cleanup failed", err);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
/** Convenience: revoke every active session for this user (all devices). */
|
|
2136
|
+
logoutEverywhere() {
|
|
2137
|
+
return this.logout({ everywhere: true });
|
|
2138
|
+
}
|
|
2139
|
+
/**
|
|
2140
|
+
* List active sessions for the authenticated user. Returns one entry per
|
|
2141
|
+
* refresh-token family with the metadata captured at issuance time. The
|
|
2142
|
+
* `current` flag identifies which entry corresponds to this client.
|
|
2143
|
+
*/
|
|
2144
|
+
async listSessions() {
|
|
2145
|
+
if (!isBrowser) {
|
|
2146
|
+
warnServerSide("listSessions");
|
|
2147
|
+
return [];
|
|
2148
|
+
}
|
|
2149
|
+
if (!this._session?.token?.accessToken) {
|
|
2150
|
+
throw new Error("[PollarClient] listSessions requires an authenticated session");
|
|
2151
|
+
}
|
|
2152
|
+
const { data, error } = await this._api.GET("/auth/sessions");
|
|
2153
|
+
if (error || !data?.success) {
|
|
2154
|
+
throw new Error("[PollarClient] Failed to list sessions");
|
|
2155
|
+
}
|
|
2156
|
+
return data.content.sessions;
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Revoke a specific refresh-token family (a single device session). Use
|
|
2160
|
+
* `listSessions` to enumerate the familyIds. Revoking the current session
|
|
2161
|
+
* does NOT clear local state — call `logout()` for that case.
|
|
2162
|
+
*/
|
|
2163
|
+
async revokeSession(familyId) {
|
|
2164
|
+
if (!isBrowser) {
|
|
2165
|
+
warnServerSide("revokeSession");
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
if (!this._session?.token?.accessToken) {
|
|
2169
|
+
throw new Error("[PollarClient] revokeSession requires an authenticated session");
|
|
2170
|
+
}
|
|
2171
|
+
const { error } = await this._api.DELETE("/auth/sessions/{familyId}", {
|
|
2172
|
+
params: { path: { familyId } }
|
|
2173
|
+
});
|
|
2174
|
+
if (error) {
|
|
2175
|
+
throw new Error("[PollarClient] Failed to revoke session");
|
|
2176
|
+
}
|
|
1496
2177
|
}
|
|
1497
2178
|
// ─── Network ──────────────────────────────────────────────────────────────
|
|
1498
2179
|
getNetwork() {
|
|
@@ -1591,7 +2272,8 @@ var PollarClient = class {
|
|
|
1591
2272
|
const details = error?.details;
|
|
1592
2273
|
this._setTransactionState({ step: "error", ...details && { details } });
|
|
1593
2274
|
}
|
|
1594
|
-
} catch {
|
|
2275
|
+
} catch (err) {
|
|
2276
|
+
console.error("[PollarClient] buildTx failed", err);
|
|
1595
2277
|
this._setTransactionState({ step: "error" });
|
|
1596
2278
|
}
|
|
1597
2279
|
}
|
|
@@ -1601,10 +2283,9 @@ var PollarClient = class {
|
|
|
1601
2283
|
async signAndSubmitTx(unsignedXdr) {
|
|
1602
2284
|
const state = this._transactionState;
|
|
1603
2285
|
const buildData = state?.step === "built" ? state.buildData : state?.step === "error" ? state.buildData : void 0;
|
|
1604
|
-
const isBuiltFlow = !!buildData;
|
|
1605
2286
|
const stateExtra = buildData ? { buildData } : { external: true };
|
|
1606
2287
|
this._setTransactionState({ step: "signing", ...stateExtra });
|
|
1607
|
-
const accountToSign =
|
|
2288
|
+
const accountToSign = this._session?.wallet?.publicKey;
|
|
1608
2289
|
if (this._walletAdapter) {
|
|
1609
2290
|
try {
|
|
1610
2291
|
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
@@ -1680,6 +2361,13 @@ var PollarClient = class {
|
|
|
1680
2361
|
pollRampTransaction(txId, opts) {
|
|
1681
2362
|
return pollRampTransaction(this._api, txId, opts);
|
|
1682
2363
|
}
|
|
2364
|
+
// ─── Distribution ─────────────────────────────────────────────────────────
|
|
2365
|
+
listDistributionRules() {
|
|
2366
|
+
return listDistributionRules(this._api);
|
|
2367
|
+
}
|
|
2368
|
+
claimDistributionRule(body) {
|
|
2369
|
+
return claimDistributionRule(this._api, body);
|
|
2370
|
+
}
|
|
1683
2371
|
_setTxHistoryState(next) {
|
|
1684
2372
|
this._txHistoryState = next;
|
|
1685
2373
|
for (const cb of this._txHistoryStateListeners) cb(next);
|
|
@@ -1689,13 +2377,11 @@ var PollarClient = class {
|
|
|
1689
2377
|
for (const cb of this._walletBalanceStateListeners) cb(next);
|
|
1690
2378
|
}
|
|
1691
2379
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
1692
|
-
/** Creates a new AbortController, cancelling any existing flow first. */
|
|
1693
2380
|
_newController() {
|
|
1694
2381
|
this._loginController?.abort();
|
|
1695
2382
|
this._loginController = new AbortController();
|
|
1696
2383
|
return this._loginController;
|
|
1697
2384
|
}
|
|
1698
|
-
/** Builds the deps object passed to flow functions via bind pattern. */
|
|
1699
2385
|
_flowDeps(signal) {
|
|
1700
2386
|
return {
|
|
1701
2387
|
api: this._api,
|
|
@@ -1703,12 +2389,31 @@ var PollarClient = class {
|
|
|
1703
2389
|
setAuthState: this._setAuthState.bind(this),
|
|
1704
2390
|
storeSession: this._storeSession.bind(this),
|
|
1705
2391
|
clearSession: this._clearSession.bind(this),
|
|
1706
|
-
|
|
2392
|
+
getPublicJwk: () => this._keyManager.getPublicJwk(),
|
|
2393
|
+
resolveWalletAdapter: (id) => this._resolveWalletAdapter(id),
|
|
2394
|
+
storeWalletAdapter: async (adapter, id) => {
|
|
1707
2395
|
this._walletAdapter = adapter;
|
|
1708
|
-
writeWalletType(
|
|
1709
|
-
}
|
|
2396
|
+
await writeWalletType(this._storage, this.apiKeyHash, id);
|
|
2397
|
+
},
|
|
2398
|
+
...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
|
|
1710
2399
|
};
|
|
1711
2400
|
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Resolves a wallet adapter for the requested id. Uses the consumer's
|
|
2403
|
+
* injected `walletAdapter` resolver when present; otherwise falls back to
|
|
2404
|
+
* the built-in `FreighterAdapter` / `AlbedoAdapter`. Throws if the id is
|
|
2405
|
+
* unknown and no resolver is configured.
|
|
2406
|
+
*/
|
|
2407
|
+
async _resolveWalletAdapter(id) {
|
|
2408
|
+
if (this._walletAdapterResolver) {
|
|
2409
|
+
return Promise.resolve(this._walletAdapterResolver(id));
|
|
2410
|
+
}
|
|
2411
|
+
if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
|
|
2412
|
+
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
|
|
2413
|
+
throw new Error(
|
|
2414
|
+
`[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
1712
2417
|
_handleFlowError(error) {
|
|
1713
2418
|
if (error instanceof Error && error.name === "AbortError") {
|
|
1714
2419
|
console.info("[PollarClient] Login cancelled");
|
|
@@ -1723,34 +2428,58 @@ var PollarClient = class {
|
|
|
1723
2428
|
errorCode: AUTH_ERROR_CODES.UNEXPECTED_ERROR
|
|
1724
2429
|
});
|
|
1725
2430
|
}
|
|
1726
|
-
_restoreSession() {
|
|
1727
|
-
this._session = readStorage();
|
|
2431
|
+
async _restoreSession() {
|
|
2432
|
+
this._session = await readStorage(this._storage, this.apiKeyHash);
|
|
1728
2433
|
if (this._session) {
|
|
1729
|
-
|
|
1730
|
-
if (
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
this._walletAdapter = new AlbedoAdapter();
|
|
2434
|
+
const storedType = await readWalletType(this._storage, this.apiKeyHash);
|
|
2435
|
+
if (storedType) {
|
|
2436
|
+
try {
|
|
2437
|
+
this._walletAdapter = await this._resolveWalletAdapter(storedType);
|
|
2438
|
+
} catch (err) {
|
|
2439
|
+
console.warn("[PollarClient] Could not restore wallet adapter for stored id", { id: storedType, err });
|
|
1736
2440
|
}
|
|
1737
2441
|
}
|
|
1738
2442
|
console.info("[PollarClient] Session restored from storage");
|
|
2443
|
+
this._setAuthState({ step: "authenticated", session: this._session });
|
|
1739
2444
|
} else {
|
|
1740
2445
|
console.info("[PollarClient] No session in storage");
|
|
1741
2446
|
}
|
|
1742
2447
|
}
|
|
1743
|
-
_storeSession(session) {
|
|
1744
|
-
console.info(
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
2448
|
+
async _storeSession(session) {
|
|
2449
|
+
console.info("[PollarClient] Session stored");
|
|
2450
|
+
const persisted = {
|
|
2451
|
+
clientSessionId: session.clientSessionId,
|
|
2452
|
+
userId: session.userId ?? null,
|
|
2453
|
+
status: session.status,
|
|
2454
|
+
token: session.token,
|
|
2455
|
+
user: session.user,
|
|
2456
|
+
wallet: session.wallet
|
|
2457
|
+
};
|
|
2458
|
+
this._session = persisted;
|
|
2459
|
+
if (session.data) {
|
|
2460
|
+
this._profile = {
|
|
2461
|
+
mail: session.data.mail,
|
|
2462
|
+
first_name: session.data.first_name,
|
|
2463
|
+
last_name: session.data.last_name,
|
|
2464
|
+
avatar: session.data.avatar,
|
|
2465
|
+
providers: session.data.providers
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
await writeStorage(this._storage, this.apiKeyHash, persisted);
|
|
2469
|
+
this._setAuthState({ step: "authenticated", session: persisted });
|
|
1748
2470
|
}
|
|
1749
|
-
_clearSession() {
|
|
2471
|
+
async _clearSession() {
|
|
1750
2472
|
console.info("[PollarClient] Session cleared");
|
|
1751
2473
|
this._session = null;
|
|
2474
|
+
this._profile = null;
|
|
1752
2475
|
this._walletAdapter = null;
|
|
1753
|
-
|
|
2476
|
+
this._dpopNonce = null;
|
|
2477
|
+
try {
|
|
2478
|
+
await this._keyManager.reset();
|
|
2479
|
+
} catch (err) {
|
|
2480
|
+
console.warn("[PollarClient] KeyManager reset failed during clearSession", err);
|
|
2481
|
+
}
|
|
2482
|
+
await removeStorage(this._storage, this.apiKeyHash);
|
|
1754
2483
|
this._transactionState = null;
|
|
1755
2484
|
this._setAuthState({ step: "idle" });
|
|
1756
2485
|
}
|
|
@@ -1775,6 +2504,9 @@ var PollarClient = class {
|
|
|
1775
2504
|
}
|
|
1776
2505
|
};
|
|
1777
2506
|
|
|
1778
|
-
|
|
2507
|
+
// src/index.ts
|
|
2508
|
+
_setDefaultKeyManagerFactory((_storage, apiKey) => new WebCryptoKeyManager(apiKey));
|
|
2509
|
+
|
|
2510
|
+
export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, PollarClient, StellarClient, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, claimDistributionRule, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, listDistributionRules, normalizeHtu, pollKycStatus, pollRampTransaction, resolveKyc, startKyc };
|
|
1779
2511
|
//# sourceMappingURL=index.mjs.map
|
|
1780
2512
|
//# sourceMappingURL=index.mjs.map
|