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