@pollar/core 0.8.2 → 0.9.0-rc.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 +35 -0
- package/dist/adapters/react-native-appstate.d.mts +1 -1
- package/dist/adapters/react-native-appstate.d.ts +1 -1
- package/dist/index.d.mts +996 -96
- package/dist/index.d.ts +996 -96
- package/dist/index.js +546 -112
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +545 -113
- package/dist/index.mjs.map +1 -1
- package/dist/index.rn.d.mts +2 -2
- package/dist/index.rn.d.ts +2 -2
- package/dist/index.rn.js +691 -262
- package/dist/index.rn.js.map +1 -1
- package/dist/index.rn.mjs +690 -263
- package/dist/index.rn.mjs.map +1 -1
- package/dist/{types-84G_htcn.d.mts → types-Dyky8g0p.d.mts} +5 -12
- package/dist/{types-84G_htcn.d.ts → types-Dyky8g0p.d.ts} +5 -12
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -184,17 +184,6 @@ function defaultKeyManager(storage, apiKey) {
|
|
|
184
184
|
}
|
|
185
185
|
return _factory(storage, apiKey);
|
|
186
186
|
}
|
|
187
|
-
async function sha256(data) {
|
|
188
|
-
return sha256$1(data);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// src/lib/api-key-hash.ts
|
|
192
|
-
async function hashApiKey(apiKey) {
|
|
193
|
-
const digest = await sha256(new TextEncoder().encode(apiKey));
|
|
194
|
-
let hex = "";
|
|
195
|
-
for (let i = 0; i < 4; i++) hex += digest[i].toString(16).padStart(2, "0");
|
|
196
|
-
return hex;
|
|
197
|
-
}
|
|
198
187
|
|
|
199
188
|
// src/lib/base64url.ts
|
|
200
189
|
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
@@ -232,6 +221,17 @@ function base64urlEncode(bytes) {
|
|
|
232
221
|
function base64urlEncodeString(s) {
|
|
233
222
|
return base64urlEncode(new TextEncoder().encode(s));
|
|
234
223
|
}
|
|
224
|
+
async function sha256(data) {
|
|
225
|
+
return sha256$1(data);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/lib/api-key-hash.ts
|
|
229
|
+
async function hashApiKey(apiKey) {
|
|
230
|
+
const digest = await sha256(new TextEncoder().encode(apiKey));
|
|
231
|
+
let hex = "";
|
|
232
|
+
for (let i = 0; i < 4; i++) hex += digest[i].toString(16).padStart(2, "0");
|
|
233
|
+
return hex;
|
|
234
|
+
}
|
|
235
235
|
|
|
236
236
|
// src/keys/thumbprint.ts
|
|
237
237
|
async function computeJwkThumbprint(jwk) {
|
|
@@ -242,11 +242,14 @@ async function computeJwkThumbprint(jwk) {
|
|
|
242
242
|
const digest = await sha256(new TextEncoder().encode(canonical));
|
|
243
243
|
return base64urlEncode(digest);
|
|
244
244
|
}
|
|
245
|
+
function toBase64url(value) {
|
|
246
|
+
return value.replace(/\+/g, "-").replace(/\//g, "_").replace(/[^A-Za-z0-9_-]/g, "");
|
|
247
|
+
}
|
|
245
248
|
function canonicalEcJwk(jwk) {
|
|
246
249
|
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || typeof jwk.y !== "string") {
|
|
247
250
|
throw new Error("[PollarClient:thumbprint] Source JWK is not an EC P-256 public key");
|
|
248
251
|
}
|
|
249
|
-
return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
|
|
252
|
+
return { kty: "EC", crv: "P-256", x: toBase64url(jwk.x), y: toBase64url(jwk.y) };
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
// src/keys/web-crypto.ts
|
|
@@ -260,7 +263,7 @@ function openDb() {
|
|
|
260
263
|
return;
|
|
261
264
|
}
|
|
262
265
|
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
263
|
-
req.onerror = () => reject(req.error ?? new Error("IDB open failed"));
|
|
266
|
+
req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB open failed"));
|
|
264
267
|
req.onsuccess = () => resolve(req.result);
|
|
265
268
|
req.onupgradeneeded = () => {
|
|
266
269
|
const db = req.result;
|
|
@@ -273,7 +276,7 @@ function openDb() {
|
|
|
273
276
|
function awaitTx(req) {
|
|
274
277
|
return new Promise((resolve, reject) => {
|
|
275
278
|
req.onsuccess = () => resolve(req.result);
|
|
276
|
-
req.onerror = () => reject(req.error ?? new Error("IDB request failed"));
|
|
279
|
+
req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB request failed"));
|
|
277
280
|
});
|
|
278
281
|
}
|
|
279
282
|
async function dbGet(key) {
|
|
@@ -336,7 +339,6 @@ var WebCryptoKeyManager = class {
|
|
|
336
339
|
if (this.keyPair) return;
|
|
337
340
|
if (!this._initPromise) {
|
|
338
341
|
this._initPromise = this._doInit().catch((err) => {
|
|
339
|
-
console.error("[PollarClient:keys] WebCryptoKeyManager init failed", err);
|
|
340
342
|
this._initPromise = null;
|
|
341
343
|
throw err;
|
|
342
344
|
});
|
|
@@ -368,10 +370,39 @@ var WebCryptoKeyManager = class {
|
|
|
368
370
|
}
|
|
369
371
|
}
|
|
370
372
|
this.keyPair = pair;
|
|
371
|
-
|
|
372
|
-
this.publicJwk = canonicalEcJwk(exported);
|
|
373
|
+
this.publicJwk = await this._exportPublicJwk(pair.publicKey);
|
|
373
374
|
this.thumbprint = await computeJwkThumbprint(this.publicJwk);
|
|
374
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Derive the public JWK from a `CryptoKey`. Prefers the `'raw'` export (the
|
|
378
|
+
* 65-byte uncompressed point `0x04 || X(32) || Y(32)`) and base64url-encodes
|
|
379
|
+
* the coordinates ourselves — that sidesteps polyfills whose `exportKey('jwk')`
|
|
380
|
+
* emits non-base64url `x`/`y` (standard base64, `=` padding, or — as seen with
|
|
381
|
+
* `react-native-quick-crypto` — a stray `.`). Real browsers and most polyfills
|
|
382
|
+
* support `'raw'` for public EC keys.
|
|
383
|
+
*
|
|
384
|
+
* Falls back to the `'jwk'` export (normalized via `canonicalEcJwk`) if `'raw'`
|
|
385
|
+
* is unsupported or returns an unexpected shape, so this can't regress on a
|
|
386
|
+
* runtime that only implements the JWK path. Both routes yield identical
|
|
387
|
+
* coordinate bytes, so the `cnf.jkt` thumbprint is unchanged either way.
|
|
388
|
+
*/
|
|
389
|
+
async _exportPublicJwk(publicKey) {
|
|
390
|
+
try {
|
|
391
|
+
const raw = new Uint8Array(await globalThis.crypto.subtle.exportKey("raw", publicKey));
|
|
392
|
+
if (raw.length !== 65 || raw[0] !== 4) {
|
|
393
|
+
throw new Error(`[PollarClient:keys] Unexpected raw EC point (len=${raw.length}, tag=${raw[0]})`);
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
kty: "EC",
|
|
397
|
+
crv: "P-256",
|
|
398
|
+
x: base64urlEncode(raw.slice(1, 33)),
|
|
399
|
+
y: base64urlEncode(raw.slice(33, 65))
|
|
400
|
+
};
|
|
401
|
+
} catch {
|
|
402
|
+
const jwk = await globalThis.crypto.subtle.exportKey("jwk", publicKey);
|
|
403
|
+
return canonicalEcJwk(jwk);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
375
406
|
async reset() {
|
|
376
407
|
try {
|
|
377
408
|
if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
|
|
@@ -1042,6 +1073,16 @@ function normalizeHtu(rawUrl) {
|
|
|
1042
1073
|
return `${scheme}//${host}${portPart}${url.pathname}`;
|
|
1043
1074
|
}
|
|
1044
1075
|
|
|
1076
|
+
// src/lib/logger.ts
|
|
1077
|
+
var RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
1078
|
+
function createLogger(level = "info", sink = console) {
|
|
1079
|
+
const threshold = RANK[level];
|
|
1080
|
+
const gate = (lvl) => (...args) => {
|
|
1081
|
+
if (threshold >= RANK[lvl]) sink[lvl](...args);
|
|
1082
|
+
};
|
|
1083
|
+
return { error: gate("error"), warn: gate("warn"), info: gate("info"), debug: gate("debug") };
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1045
1086
|
// src/storage/web.ts
|
|
1046
1087
|
var LOG_PREFIX = "[PollarClient:storage]";
|
|
1047
1088
|
function createMemoryAdapter() {
|
|
@@ -1065,7 +1106,7 @@ function createLocalStorageAdapter(options = {}) {
|
|
|
1065
1106
|
function degrade(reason, error) {
|
|
1066
1107
|
if (degraded) return;
|
|
1067
1108
|
degraded = true;
|
|
1068
|
-
console.warn(`${LOG_PREFIX} localStorage unavailable (${reason}); degrading to in-memory storage`);
|
|
1109
|
+
(options.logger ?? console).warn(`${LOG_PREFIX} localStorage unavailable (${reason}); degrading to in-memory storage`);
|
|
1069
1110
|
options.onDegrade?.(reason, error);
|
|
1070
1111
|
}
|
|
1071
1112
|
return {
|
|
@@ -1129,6 +1170,9 @@ function defaultStorage(options = {}) {
|
|
|
1129
1170
|
return createLocalStorageAdapter(options);
|
|
1130
1171
|
}
|
|
1131
1172
|
|
|
1173
|
+
// src/version.ts
|
|
1174
|
+
var POLLAR_CORE_VERSION = "0.9.0-rc.1" ;
|
|
1175
|
+
|
|
1132
1176
|
// src/visibility/noop.ts
|
|
1133
1177
|
function createNoopVisibilityProvider() {
|
|
1134
1178
|
return {
|
|
@@ -1193,6 +1237,7 @@ var AUTH_ERROR_CODES = {
|
|
|
1193
1237
|
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1194
1238
|
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1195
1239
|
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1240
|
+
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1196
1241
|
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1197
1242
|
};
|
|
1198
1243
|
var PollarFlowError = class extends Error {
|
|
@@ -1238,7 +1283,7 @@ var FreighterAdapter = class {
|
|
|
1238
1283
|
if (!userInfo?.publicKey) {
|
|
1239
1284
|
throw new Error("Failed to get user information from Freighter");
|
|
1240
1285
|
}
|
|
1241
|
-
return { address: userInfo.publicKey
|
|
1286
|
+
return { address: userInfo.publicKey };
|
|
1242
1287
|
}
|
|
1243
1288
|
async disconnect() {
|
|
1244
1289
|
}
|
|
@@ -1276,6 +1321,19 @@ var FreighterAdapter = class {
|
|
|
1276
1321
|
};
|
|
1277
1322
|
|
|
1278
1323
|
// src/wallets/AlbedoAdapter.ts
|
|
1324
|
+
var PUBLIC_PASSPHRASE = "Public Global Stellar Network ; September 2015";
|
|
1325
|
+
var TESTNET_PASSPHRASE = "Test SDF Network ; September 2015";
|
|
1326
|
+
function albedoNetwork(options, fallback) {
|
|
1327
|
+
switch (options?.networkPassphrase) {
|
|
1328
|
+
case PUBLIC_PASSPHRASE:
|
|
1329
|
+
return "public";
|
|
1330
|
+
case TESTNET_PASSPHRASE:
|
|
1331
|
+
return "testnet";
|
|
1332
|
+
}
|
|
1333
|
+
if (options?.network === "public" || options?.network === "mainnet") return "public";
|
|
1334
|
+
if (options?.network === "testnet") return "testnet";
|
|
1335
|
+
return fallback;
|
|
1336
|
+
}
|
|
1279
1337
|
function openAlbedoPopup(url) {
|
|
1280
1338
|
const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
|
|
1281
1339
|
if (!popup) {
|
|
@@ -1314,7 +1372,13 @@ function waitForAlbedoResult() {
|
|
|
1314
1372
|
});
|
|
1315
1373
|
}
|
|
1316
1374
|
var AlbedoAdapter = class {
|
|
1317
|
-
|
|
1375
|
+
/**
|
|
1376
|
+
* Network used for `connect` and `signAuthEntry` (which carry no per-call
|
|
1377
|
+
* network) and as the fallback for `signTransaction`. Defaults to `'testnet'`
|
|
1378
|
+
* to preserve the previous behavior when constructed with no argument.
|
|
1379
|
+
*/
|
|
1380
|
+
constructor(network = "testnet") {
|
|
1381
|
+
this.network = network;
|
|
1318
1382
|
this.type = "albedo" /* ALBEDO */;
|
|
1319
1383
|
}
|
|
1320
1384
|
async isAvailable() {
|
|
@@ -1324,7 +1388,7 @@ var AlbedoAdapter = class {
|
|
|
1324
1388
|
const url = new URL("https://albedo.link");
|
|
1325
1389
|
url.searchParams.set("intent", "public-key");
|
|
1326
1390
|
url.searchParams.set("app_name", "Pollar");
|
|
1327
|
-
url.searchParams.set("network",
|
|
1391
|
+
url.searchParams.set("network", this.network);
|
|
1328
1392
|
url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
|
|
1329
1393
|
url.searchParams.set("origin", window.location.origin);
|
|
1330
1394
|
openAlbedoPopup(url.toString());
|
|
@@ -1332,7 +1396,7 @@ var AlbedoAdapter = class {
|
|
|
1332
1396
|
if (!result.pubkey) {
|
|
1333
1397
|
throw new Error("Albedo connection rejected");
|
|
1334
1398
|
}
|
|
1335
|
-
return { address: result.pubkey
|
|
1399
|
+
return { address: result.pubkey };
|
|
1336
1400
|
}
|
|
1337
1401
|
async disconnect() {
|
|
1338
1402
|
}
|
|
@@ -1342,12 +1406,12 @@ var AlbedoAdapter = class {
|
|
|
1342
1406
|
async getNetwork() {
|
|
1343
1407
|
throw new Error("Albedo does not expose network");
|
|
1344
1408
|
}
|
|
1345
|
-
async signTransaction(xdr,
|
|
1409
|
+
async signTransaction(xdr, options) {
|
|
1346
1410
|
const url = new URL("https://albedo.link");
|
|
1347
1411
|
url.searchParams.set("intent", "tx");
|
|
1348
1412
|
url.searchParams.set("xdr", xdr);
|
|
1349
1413
|
url.searchParams.set("app_name", "Pollar");
|
|
1350
|
-
url.searchParams.set("network",
|
|
1414
|
+
url.searchParams.set("network", albedoNetwork(options, this.network));
|
|
1351
1415
|
url.searchParams.set("callback", window.location.href);
|
|
1352
1416
|
url.searchParams.set("origin", window.location.origin);
|
|
1353
1417
|
window.location.href = url.toString();
|
|
@@ -1360,7 +1424,7 @@ var AlbedoAdapter = class {
|
|
|
1360
1424
|
url.searchParams.set("intent", "sign-auth-entry");
|
|
1361
1425
|
url.searchParams.set("xdr", entryXdr);
|
|
1362
1426
|
url.searchParams.set("app_name", "Pollar");
|
|
1363
|
-
url.searchParams.set("network",
|
|
1427
|
+
url.searchParams.set("network", this.network);
|
|
1364
1428
|
url.searchParams.set("callback", window.location.href);
|
|
1365
1429
|
url.searchParams.set("origin", window.location.origin);
|
|
1366
1430
|
window.location.href = url.toString();
|
|
@@ -1391,84 +1455,98 @@ function isBoundedString(v, max, allowEmpty = false) {
|
|
|
1391
1455
|
if (!allowEmpty && v.length === 0) return false;
|
|
1392
1456
|
return v.length <= max;
|
|
1393
1457
|
}
|
|
1394
|
-
function isValidSession(value) {
|
|
1458
|
+
function isValidSession(value, logger = console) {
|
|
1395
1459
|
if (typeof value !== "object" || value === null) {
|
|
1396
|
-
|
|
1460
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 value is not an object");
|
|
1397
1461
|
return false;
|
|
1398
1462
|
}
|
|
1399
1463
|
const s = value;
|
|
1400
1464
|
if (!isBoundedString(s["clientSessionId"], MAX_CLIENT_SESSION_ID)) {
|
|
1401
|
-
|
|
1465
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 clientSessionId missing/empty/too long");
|
|
1402
1466
|
return false;
|
|
1403
1467
|
}
|
|
1404
1468
|
if (s["userId"] !== null && !isBoundedString(s["userId"], MAX_USER_ID)) {
|
|
1405
|
-
|
|
1469
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 userId must be string|null");
|
|
1406
1470
|
return false;
|
|
1407
1471
|
}
|
|
1408
1472
|
if (!isBoundedString(s["status"], MAX_STATUS)) {
|
|
1409
|
-
|
|
1473
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 status must be string");
|
|
1410
1474
|
return false;
|
|
1411
1475
|
}
|
|
1412
1476
|
const token = s["token"];
|
|
1413
1477
|
if (typeof token !== "object" || token === null) {
|
|
1414
|
-
|
|
1478
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 token missing or not an object");
|
|
1415
1479
|
return false;
|
|
1416
1480
|
}
|
|
1417
1481
|
const t = token;
|
|
1418
1482
|
if (!isBoundedString(t["accessToken"], MAX_ACCESS_TOKEN)) {
|
|
1419
|
-
|
|
1483
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 token.accessToken missing/empty/too long");
|
|
1420
1484
|
return false;
|
|
1421
1485
|
}
|
|
1422
1486
|
if (!isBoundedString(t["refreshToken"], MAX_REFRESH_TOKEN)) {
|
|
1423
|
-
|
|
1487
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 token.refreshToken missing/empty/too long");
|
|
1424
1488
|
return false;
|
|
1425
1489
|
}
|
|
1426
1490
|
if (typeof t["expiresAt"] !== "number" || !Number.isFinite(t["expiresAt"])) {
|
|
1427
|
-
|
|
1491
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 token.expiresAt must be a finite number");
|
|
1428
1492
|
return false;
|
|
1429
1493
|
}
|
|
1430
1494
|
const user = s["user"];
|
|
1431
1495
|
if (typeof user !== "object" || user === null) {
|
|
1432
|
-
|
|
1496
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 user missing or not an object");
|
|
1433
1497
|
return false;
|
|
1434
1498
|
}
|
|
1435
1499
|
const u = user;
|
|
1436
1500
|
if (u["id"] !== void 0 && !isBoundedString(u["id"], MAX_USER_ID)) {
|
|
1437
|
-
|
|
1501
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 user.id must be string if present");
|
|
1438
1502
|
return false;
|
|
1439
1503
|
}
|
|
1440
1504
|
if (typeof u["ready"] !== "boolean") {
|
|
1441
|
-
|
|
1505
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 user.ready must be boolean");
|
|
1442
1506
|
return false;
|
|
1443
1507
|
}
|
|
1444
1508
|
const wallet = s["wallet"];
|
|
1445
1509
|
if (typeof wallet !== "object" || wallet === null) {
|
|
1446
|
-
|
|
1510
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 wallet missing or not an object");
|
|
1447
1511
|
return false;
|
|
1448
1512
|
}
|
|
1449
1513
|
const w = wallet;
|
|
1450
|
-
if (w["
|
|
1451
|
-
|
|
1514
|
+
if (w["type"] !== "custodial" && w["type"] !== "smart" && w["type"] !== "external") {
|
|
1515
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.type must be custodial|smart|external");
|
|
1516
|
+
return false;
|
|
1517
|
+
}
|
|
1518
|
+
if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
|
|
1519
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.address must be string|null");
|
|
1452
1520
|
return false;
|
|
1453
1521
|
}
|
|
1454
1522
|
if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
|
|
1455
|
-
|
|
1523
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.existsOnStellar must be boolean if present");
|
|
1456
1524
|
return false;
|
|
1457
1525
|
}
|
|
1458
1526
|
if (w["createdAt"] !== void 0 && (typeof w["createdAt"] !== "number" || !Number.isFinite(w["createdAt"]))) {
|
|
1459
|
-
|
|
1527
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
|
|
1528
|
+
return false;
|
|
1529
|
+
}
|
|
1530
|
+
if (w["linkedAt"] !== void 0 && (typeof w["linkedAt"] !== "number" || !Number.isFinite(w["linkedAt"]))) {
|
|
1531
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.linkedAt must be a finite number if present");
|
|
1460
1532
|
return false;
|
|
1461
1533
|
}
|
|
1462
1534
|
return true;
|
|
1463
1535
|
}
|
|
1464
|
-
async function readStorage(storage, apiKeyHash) {
|
|
1536
|
+
async function readStorage(storage, apiKeyHash, logger = console) {
|
|
1465
1537
|
const raw = await storage.get(sessionStorageKey(apiKeyHash));
|
|
1466
1538
|
if (!raw) return null;
|
|
1467
1539
|
try {
|
|
1468
1540
|
const session = JSON.parse(raw);
|
|
1469
|
-
if (
|
|
1541
|
+
if (typeof session === "object" && session !== null) {
|
|
1542
|
+
const w = session.wallet;
|
|
1543
|
+
if (w && w["address"] == null && typeof w["publicKey"] === "string") {
|
|
1544
|
+
w["address"] = w["publicKey"];
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (!isValidSession(session, logger)) {
|
|
1470
1548
|
await storage.remove(sessionStorageKey(apiKeyHash));
|
|
1471
|
-
|
|
1549
|
+
logger.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
|
|
1472
1550
|
return null;
|
|
1473
1551
|
}
|
|
1474
1552
|
if (session.token.expiresAt * 1e3 < Date.now()) {
|
|
@@ -1476,7 +1554,7 @@ async function readStorage(storage, apiKeyHash) {
|
|
|
1476
1554
|
}
|
|
1477
1555
|
return session;
|
|
1478
1556
|
} catch (error) {
|
|
1479
|
-
|
|
1557
|
+
logger.error("[PollarClient:session] Failed to parse session from storage", error);
|
|
1480
1558
|
await storage.remove(sessionStorageKey(apiKeyHash));
|
|
1481
1559
|
return null;
|
|
1482
1560
|
}
|
|
@@ -1538,7 +1616,7 @@ function abortableDelay(ms, signal) {
|
|
|
1538
1616
|
});
|
|
1539
1617
|
}
|
|
1540
1618
|
var MAX_BACKOFF_MS = 5e3;
|
|
1541
|
-
async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200, signal) {
|
|
1619
|
+
async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200, signal, logger = console) {
|
|
1542
1620
|
let backoff = retryDelayMs;
|
|
1543
1621
|
const sleep = async (ms) => {
|
|
1544
1622
|
if (ms <= 0) return;
|
|
@@ -1556,7 +1634,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1556
1634
|
}));
|
|
1557
1635
|
} catch (e) {
|
|
1558
1636
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1559
|
-
|
|
1637
|
+
logger.debug("[PollarClient:stream] session-status request failed; will retry", e);
|
|
1560
1638
|
}
|
|
1561
1639
|
if (error || !data) {
|
|
1562
1640
|
await sleep(backoff);
|
|
@@ -1596,7 +1674,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1596
1674
|
} catch (e) {
|
|
1597
1675
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1598
1676
|
if (e instanceof SessionStatusError) throw e;
|
|
1599
|
-
|
|
1677
|
+
logger.debug("[PollarClient:stream] session-status stream read failed; will retry", e);
|
|
1600
1678
|
} finally {
|
|
1601
1679
|
reader.releaseLock();
|
|
1602
1680
|
}
|
|
@@ -1606,7 +1684,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1606
1684
|
if (delay) await sleep(delay);
|
|
1607
1685
|
}
|
|
1608
1686
|
}
|
|
1609
|
-
async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500, signal) {
|
|
1687
|
+
async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500, signal, logger = console) {
|
|
1610
1688
|
const url = `${baseUrl}/auth/session/status/${encodeURIComponent(clientSessionId)}/poll`;
|
|
1611
1689
|
let backoff = intervalMs;
|
|
1612
1690
|
const sleep = async (ms) => {
|
|
@@ -1624,7 +1702,7 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
|
|
|
1624
1702
|
envelope = await response.json().catch(() => null);
|
|
1625
1703
|
} catch (e) {
|
|
1626
1704
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1627
|
-
|
|
1705
|
+
logger.debug("[PollarClient:stream] session-status poll failed; will retry", e);
|
|
1628
1706
|
}
|
|
1629
1707
|
if (httpStatus === 404 || envelope?.code === "INVALID_CLIENT_SESSION_ID") {
|
|
1630
1708
|
throw new SessionStatusError("INVALID_CLIENT_SESSION_ID");
|
|
@@ -1641,13 +1719,13 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
|
|
|
1641
1719
|
}
|
|
1642
1720
|
}
|
|
1643
1721
|
function waitForSessionReady(args) {
|
|
1644
|
-
const { api, baseUrl, clientSessionId, check, useStreaming, retryDelayMs, signal } = args;
|
|
1645
|
-
return useStreaming ? streamUntilFound(api, clientSessionId, check, retryDelayMs ?? 200, signal) : pollUntilFound(baseUrl, clientSessionId, check, retryDelayMs ?? 500, signal);
|
|
1722
|
+
const { api, baseUrl, clientSessionId, check, useStreaming, retryDelayMs, signal, logger = console } = args;
|
|
1723
|
+
return useStreaming ? streamUntilFound(api, clientSessionId, check, retryDelayMs ?? 200, signal, logger) : pollUntilFound(baseUrl, clientSessionId, check, retryDelayMs ?? 500, signal, logger);
|
|
1646
1724
|
}
|
|
1647
1725
|
|
|
1648
1726
|
// src/client/auth/authenticate.ts
|
|
1649
1727
|
async function authenticate(clientSessionId, deps, expectedWallet) {
|
|
1650
|
-
const { api, basePath, useStreaming, signal, setAuthState, storeSession, clearSession } = deps;
|
|
1728
|
+
const { api, logger, basePath, useStreaming, signal, setAuthState, storeSession, clearSession } = deps;
|
|
1651
1729
|
setAuthState({ step: "authenticating" });
|
|
1652
1730
|
try {
|
|
1653
1731
|
await waitForSessionReady({
|
|
@@ -1656,7 +1734,8 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
|
|
|
1656
1734
|
clientSessionId,
|
|
1657
1735
|
check: (data2) => data2?.status === "READY",
|
|
1658
1736
|
useStreaming,
|
|
1659
|
-
signal
|
|
1737
|
+
signal,
|
|
1738
|
+
logger
|
|
1660
1739
|
});
|
|
1661
1740
|
} catch (err) {
|
|
1662
1741
|
if (err instanceof SessionStatusError) {
|
|
@@ -1681,7 +1760,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
|
|
|
1681
1760
|
},
|
|
1682
1761
|
signal
|
|
1683
1762
|
});
|
|
1684
|
-
if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content)) {
|
|
1763
|
+
if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content, logger)) {
|
|
1685
1764
|
if (expectedWallet && data.content.data.providers.wallet?.address !== expectedWallet) {
|
|
1686
1765
|
setAuthState({
|
|
1687
1766
|
step: "error",
|
|
@@ -1828,6 +1907,55 @@ async function loginOAuth(provider, deps) {
|
|
|
1828
1907
|
await authenticate(clientSessionId, deps);
|
|
1829
1908
|
}
|
|
1830
1909
|
|
|
1910
|
+
// src/client/auth/passkeyFlow.ts
|
|
1911
|
+
async function loginSmartWallet(deps) {
|
|
1912
|
+
const { api, signal, setAuthState, passkey } = deps;
|
|
1913
|
+
if (!passkey) {
|
|
1914
|
+
setAuthState({
|
|
1915
|
+
step: "error",
|
|
1916
|
+
previousStep: "creating_session",
|
|
1917
|
+
message: "Passkey support is not configured",
|
|
1918
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1919
|
+
});
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
const clientSessionId = await createAuthSession(deps);
|
|
1923
|
+
if (!clientSessionId) return;
|
|
1924
|
+
try {
|
|
1925
|
+
const { data: challengeData } = await api.POST("/auth/passkey/challenge", {
|
|
1926
|
+
body: { clientSessionId },
|
|
1927
|
+
signal
|
|
1928
|
+
});
|
|
1929
|
+
const challenge = challengeData?.content?.challenge;
|
|
1930
|
+
if (!challengeData?.success || !challenge) {
|
|
1931
|
+
return failPasskey(setAuthState, "Failed to start passkey");
|
|
1932
|
+
}
|
|
1933
|
+
setAuthState({ step: "creating_passkey" });
|
|
1934
|
+
const ceremony = await passkey({ challenge });
|
|
1935
|
+
const response = ceremony.response;
|
|
1936
|
+
if (ceremony.kind === "register") {
|
|
1937
|
+
setAuthState({ step: "deploying_smart_account" });
|
|
1938
|
+
const { data } = await api.POST("/auth/passkey/register", {
|
|
1939
|
+
body: { clientSessionId, response },
|
|
1940
|
+
signal
|
|
1941
|
+
});
|
|
1942
|
+
if (!data?.success) return failPasskey(setAuthState, "Passkey registration failed");
|
|
1943
|
+
} else {
|
|
1944
|
+
const { data } = await api.POST("/auth/passkey/login", {
|
|
1945
|
+
body: { clientSessionId, response },
|
|
1946
|
+
signal
|
|
1947
|
+
});
|
|
1948
|
+
if (!data?.success) return failPasskey(setAuthState, "Passkey authentication failed");
|
|
1949
|
+
}
|
|
1950
|
+
} catch {
|
|
1951
|
+
return failPasskey(setAuthState, "Passkey login failed");
|
|
1952
|
+
}
|
|
1953
|
+
await authenticate(clientSessionId, deps);
|
|
1954
|
+
}
|
|
1955
|
+
function failPasskey(setAuthState, message) {
|
|
1956
|
+
setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED });
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1831
1959
|
// src/client/auth/walletFlow.ts
|
|
1832
1960
|
function withSignal(promise, signal) {
|
|
1833
1961
|
return Promise.race([
|
|
@@ -1854,12 +1982,12 @@ async function loginWallet(type, deps) {
|
|
|
1854
1982
|
setAuthState({ step: "wallet_not_installed", walletType: type });
|
|
1855
1983
|
return;
|
|
1856
1984
|
}
|
|
1857
|
-
const {
|
|
1858
|
-
connectedWallet =
|
|
1985
|
+
const { address } = await withSignal(adapter.connect(), signal);
|
|
1986
|
+
connectedWallet = address;
|
|
1859
1987
|
deps.storeWalletAdapter(adapter, type);
|
|
1860
1988
|
setAuthState({ step: "authenticating_wallet" });
|
|
1861
1989
|
const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
|
|
1862
|
-
body: { clientSessionId, walletAddress:
|
|
1990
|
+
body: { clientSessionId, walletAddress: address },
|
|
1863
1991
|
signal
|
|
1864
1992
|
});
|
|
1865
1993
|
if (walletError || !walletData?.success) {
|
|
@@ -1924,8 +2052,12 @@ var PollarClient = class {
|
|
|
1924
2052
|
this._transactionStateListeners = /* @__PURE__ */ new Set();
|
|
1925
2053
|
this._txHistoryState = { step: "idle" };
|
|
1926
2054
|
this._txHistoryStateListeners = /* @__PURE__ */ new Set();
|
|
2055
|
+
this._sessionsState = { step: "idle" };
|
|
2056
|
+
this._sessionsStateListeners = /* @__PURE__ */ new Set();
|
|
1927
2057
|
this._walletBalanceState = { step: "idle" };
|
|
1928
2058
|
this._walletBalanceStateListeners = /* @__PURE__ */ new Set();
|
|
2059
|
+
this._enabledAssetsState = { step: "idle" };
|
|
2060
|
+
this._enabledAssetsStateListeners = /* @__PURE__ */ new Set();
|
|
1929
2061
|
this._authState = { step: "idle" };
|
|
1930
2062
|
this._authStateListeners = /* @__PURE__ */ new Set();
|
|
1931
2063
|
this._networkState = { step: "idle" };
|
|
@@ -1941,10 +2073,14 @@ var PollarClient = class {
|
|
|
1941
2073
|
this._storageDegradeListeners = /* @__PURE__ */ new Set();
|
|
1942
2074
|
this._walletAdapter = null;
|
|
1943
2075
|
this._loginController = null;
|
|
2076
|
+
/** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
|
|
2077
|
+
this._resumeController = null;
|
|
1944
2078
|
this.apiKey = config.apiKey;
|
|
1945
2079
|
this.id = randomUUID();
|
|
1946
2080
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
2081
|
+
this._log = createLogger(config.logLevel ?? "info", config.logger);
|
|
1947
2082
|
this._storage = config.storage ?? defaultStorage({
|
|
2083
|
+
logger: this._log,
|
|
1948
2084
|
onDegrade: (reason, error) => {
|
|
1949
2085
|
config.onStorageDegrade?.(reason, error);
|
|
1950
2086
|
this._dispatchStorageDegrade(reason, error);
|
|
@@ -1953,6 +2089,8 @@ var PollarClient = class {
|
|
|
1953
2089
|
this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
|
|
1954
2090
|
this._walletAdapterResolver = config.walletAdapter ?? null;
|
|
1955
2091
|
this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
|
|
2092
|
+
this._passkey = config.passkey ?? null;
|
|
2093
|
+
this._passkeySign = config.passkeySign ?? null;
|
|
1956
2094
|
this._deviceLabel = config.deviceLabel;
|
|
1957
2095
|
this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
|
|
1958
2096
|
this._maxIdleMs = config.maxIdleMs;
|
|
@@ -1966,7 +2104,9 @@ var PollarClient = class {
|
|
|
1966
2104
|
this._initialized = Promise.resolve();
|
|
1967
2105
|
return;
|
|
1968
2106
|
}
|
|
1969
|
-
|
|
2107
|
+
this._log.info(
|
|
2108
|
+
`[PollarClient] Initialized v${POLLAR_CORE_VERSION} \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`
|
|
2109
|
+
);
|
|
1970
2110
|
this._initialized = this._initialize();
|
|
1971
2111
|
}
|
|
1972
2112
|
/**
|
|
@@ -1991,7 +2131,7 @@ var PollarClient = class {
|
|
|
1991
2131
|
const sessionKey = sessionStorageKey(this._apiKeyHash);
|
|
1992
2132
|
const handler = (e) => {
|
|
1993
2133
|
if (e.key === sessionKey) {
|
|
1994
|
-
this._restoreSession().catch((err) =>
|
|
2134
|
+
this._restoreSession().catch((err) => this._log.error("[PollarClient] Cross-tab restore failed", err));
|
|
1995
2135
|
}
|
|
1996
2136
|
};
|
|
1997
2137
|
window.addEventListener("storage", handler);
|
|
@@ -2000,11 +2140,15 @@ var PollarClient = class {
|
|
|
2000
2140
|
try {
|
|
2001
2141
|
await this._keyManager.init();
|
|
2002
2142
|
} catch (err) {
|
|
2003
|
-
|
|
2143
|
+
this._log.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
|
|
2004
2144
|
}
|
|
2005
2145
|
await this._restoreSession();
|
|
2006
2146
|
this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
|
|
2007
|
-
if (visible)
|
|
2147
|
+
if (!visible) return;
|
|
2148
|
+
void this._maybeProactiveRefresh();
|
|
2149
|
+
if (this._authState.step === "authenticated" && !this._authState.verified) {
|
|
2150
|
+
void this._resume();
|
|
2151
|
+
}
|
|
2008
2152
|
});
|
|
2009
2153
|
}
|
|
2010
2154
|
/** Detach the cross-tab storage listener and abort any in-flight login. */
|
|
@@ -2015,6 +2159,8 @@ var PollarClient = class {
|
|
|
2015
2159
|
}
|
|
2016
2160
|
this._loginController?.abort();
|
|
2017
2161
|
this._loginController = null;
|
|
2162
|
+
this._resumeController?.abort();
|
|
2163
|
+
this._resumeController = null;
|
|
2018
2164
|
this._clearRefreshTimer();
|
|
2019
2165
|
if (this._visibilityUnsubscribe) {
|
|
2020
2166
|
this._visibilityUnsubscribe();
|
|
@@ -2029,11 +2175,13 @@ var PollarClient = class {
|
|
|
2029
2175
|
request.headers.set("x-pollar-api-key", self.apiKey);
|
|
2030
2176
|
self._lastRequestAt = Date.now();
|
|
2031
2177
|
await self._initialized;
|
|
2032
|
-
|
|
2178
|
+
const cacheMethod = request.method.toUpperCase();
|
|
2179
|
+
const cacheBodyAllowed = cacheMethod !== "GET" && cacheMethod !== "HEAD";
|
|
2180
|
+
if (cacheBodyAllowed && request.body != null) {
|
|
2033
2181
|
try {
|
|
2034
2182
|
self._requestBodyCache.set(request, await request.clone().arrayBuffer());
|
|
2035
2183
|
} catch (err) {
|
|
2036
|
-
|
|
2184
|
+
this._log.warn("[PollarClient] Could not snapshot request body for retry", err);
|
|
2037
2185
|
}
|
|
2038
2186
|
}
|
|
2039
2187
|
const isRefresh = request.url.includes("/auth/refresh");
|
|
@@ -2092,7 +2240,7 @@ var PollarClient = class {
|
|
|
2092
2240
|
this._keyManager
|
|
2093
2241
|
);
|
|
2094
2242
|
} catch (err) {
|
|
2095
|
-
|
|
2243
|
+
this._log.warn("[PollarClient] DPoP proof build failed", err);
|
|
2096
2244
|
return null;
|
|
2097
2245
|
}
|
|
2098
2246
|
}
|
|
@@ -2116,7 +2264,9 @@ var PollarClient = class {
|
|
|
2116
2264
|
}
|
|
2117
2265
|
}
|
|
2118
2266
|
}
|
|
2119
|
-
const
|
|
2267
|
+
const retryMethod = originalRequest.method.toUpperCase();
|
|
2268
|
+
const retryBodyAllowed = retryMethod !== "GET" && retryMethod !== "HEAD";
|
|
2269
|
+
const cachedBody = retryBodyAllowed ? this._requestBodyCache.get(originalRequest) : void 0;
|
|
2120
2270
|
const retried = new Request(originalRequest.url, {
|
|
2121
2271
|
method: originalRequest.method,
|
|
2122
2272
|
headers,
|
|
@@ -2144,7 +2294,7 @@ var PollarClient = class {
|
|
|
2144
2294
|
async _doRefresh() {
|
|
2145
2295
|
const refreshToken = this._session?.token?.refreshToken;
|
|
2146
2296
|
if (!refreshToken) {
|
|
2147
|
-
|
|
2297
|
+
this._log.warn("[PollarClient] Refresh skipped: no refresh token in session");
|
|
2148
2298
|
await this._clearSession();
|
|
2149
2299
|
throw new Error("No refresh token available");
|
|
2150
2300
|
}
|
|
@@ -2155,24 +2305,31 @@ var PollarClient = class {
|
|
|
2155
2305
|
data = response.data;
|
|
2156
2306
|
error = response.error;
|
|
2157
2307
|
} catch (err) {
|
|
2158
|
-
|
|
2308
|
+
this._log.error("[PollarClient] /auth/refresh request threw", err);
|
|
2159
2309
|
await this._clearSession();
|
|
2160
2310
|
throw err;
|
|
2161
2311
|
}
|
|
2162
2312
|
if (error || !data) {
|
|
2163
|
-
|
|
2313
|
+
this._log.error("[PollarClient] /auth/refresh returned error", { error });
|
|
2164
2314
|
await this._clearSession();
|
|
2165
2315
|
throw new Error("Refresh failed");
|
|
2166
2316
|
}
|
|
2167
2317
|
const successData = data;
|
|
2168
2318
|
if (!successData.success || !successData.content?.token) {
|
|
2169
|
-
|
|
2319
|
+
this._log.error("[PollarClient] /auth/refresh response malformed", {
|
|
2320
|
+
success: successData.success,
|
|
2321
|
+
hasToken: !!successData.content?.token
|
|
2322
|
+
});
|
|
2170
2323
|
await this._clearSession();
|
|
2171
2324
|
throw new Error("Refresh response malformed");
|
|
2172
2325
|
}
|
|
2173
2326
|
const newToken = successData.content.token;
|
|
2174
2327
|
if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
|
|
2175
|
-
|
|
2328
|
+
this._log.error("[PollarClient] /auth/refresh token shape invalid", {
|
|
2329
|
+
accessToken: typeof newToken.accessToken,
|
|
2330
|
+
refreshToken: typeof newToken.refreshToken,
|
|
2331
|
+
expiresAt: typeof newToken.expiresAt
|
|
2332
|
+
});
|
|
2176
2333
|
await this._clearSession();
|
|
2177
2334
|
throw new Error("Refresh response token shape invalid");
|
|
2178
2335
|
}
|
|
@@ -2180,9 +2337,9 @@ var PollarClient = class {
|
|
|
2180
2337
|
try {
|
|
2181
2338
|
this._session = { ...this._session, token: newToken };
|
|
2182
2339
|
await writeStorage(this._storage, this.apiKeyHash, this._session);
|
|
2183
|
-
|
|
2340
|
+
this._log.info("[PollarClient] Tokens refreshed");
|
|
2184
2341
|
} catch (err) {
|
|
2185
|
-
|
|
2342
|
+
this._log.error("[PollarClient] Failed to persist refreshed session", err);
|
|
2186
2343
|
}
|
|
2187
2344
|
this._scheduleNextRefresh();
|
|
2188
2345
|
}
|
|
@@ -2236,7 +2393,7 @@ var PollarClient = class {
|
|
|
2236
2393
|
try {
|
|
2237
2394
|
await this.refresh();
|
|
2238
2395
|
} catch (err) {
|
|
2239
|
-
|
|
2396
|
+
this._log.warn("[PollarClient] Proactive refresh failed; session cleared", err);
|
|
2240
2397
|
}
|
|
2241
2398
|
}
|
|
2242
2399
|
_clearRefreshTimer() {
|
|
@@ -2282,7 +2439,7 @@ var PollarClient = class {
|
|
|
2282
2439
|
try {
|
|
2283
2440
|
cb(reason, error);
|
|
2284
2441
|
} catch (err) {
|
|
2285
|
-
|
|
2442
|
+
this._log.error("[PollarClient] onStorageDegrade listener threw", err);
|
|
2286
2443
|
}
|
|
2287
2444
|
}
|
|
2288
2445
|
}
|
|
@@ -2366,6 +2523,19 @@ var PollarClient = class {
|
|
|
2366
2523
|
const controller = this._newController();
|
|
2367
2524
|
loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2368
2525
|
}
|
|
2526
|
+
/**
|
|
2527
|
+
* "Smart Wallet" login: runs the passkey (WebAuthn) ceremony and, for a new
|
|
2528
|
+
* user, creates a sponsored smart-account C-address. Requires the `passkey`
|
|
2529
|
+
* ceremony to be configured (e.g. via `@pollar/react`).
|
|
2530
|
+
*/
|
|
2531
|
+
loginSmartWallet() {
|
|
2532
|
+
if (!isClientRuntime) {
|
|
2533
|
+
warnServerSide("loginSmartWallet");
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
const controller = this._newController();
|
|
2537
|
+
loginSmartWallet(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2538
|
+
}
|
|
2369
2539
|
// ─── Cancel ───────────────────────────────────────────────────────────────
|
|
2370
2540
|
cancelLogin() {
|
|
2371
2541
|
this._loginController?.abort();
|
|
@@ -2389,20 +2559,20 @@ var PollarClient = class {
|
|
|
2389
2559
|
warnServerSide("logout");
|
|
2390
2560
|
return;
|
|
2391
2561
|
}
|
|
2392
|
-
|
|
2562
|
+
this._log.info("[PollarClient] Logout requested", { everywhere: !!options.everywhere });
|
|
2393
2563
|
if (this._session?.token?.accessToken) {
|
|
2394
2564
|
try {
|
|
2395
2565
|
await this._api.POST("/auth/logout", {
|
|
2396
2566
|
body: options.everywhere ? { everywhere: true } : {}
|
|
2397
2567
|
});
|
|
2398
2568
|
} catch (err) {
|
|
2399
|
-
|
|
2569
|
+
this._log.warn("[PollarClient] Server logout failed (continuing with local clear)", err);
|
|
2400
2570
|
}
|
|
2401
2571
|
}
|
|
2402
2572
|
try {
|
|
2403
2573
|
await this._clearSession();
|
|
2404
2574
|
} catch (err) {
|
|
2405
|
-
|
|
2575
|
+
this._log.warn("[PollarClient] Local logout cleanup failed", err);
|
|
2406
2576
|
}
|
|
2407
2577
|
}
|
|
2408
2578
|
/** Convenience: revoke every active session for this user (all devices). */
|
|
@@ -2428,6 +2598,29 @@ var PollarClient = class {
|
|
|
2428
2598
|
}
|
|
2429
2599
|
return data.content.sessions;
|
|
2430
2600
|
}
|
|
2601
|
+
getSessionsState() {
|
|
2602
|
+
return this._sessionsState;
|
|
2603
|
+
}
|
|
2604
|
+
onSessionsStateChange(cb) {
|
|
2605
|
+
this._sessionsStateListeners.add(cb);
|
|
2606
|
+
cb(this._sessionsState);
|
|
2607
|
+
return () => this._sessionsStateListeners.delete(cb);
|
|
2608
|
+
}
|
|
2609
|
+
/**
|
|
2610
|
+
* Fire-and-forget variant of {@link listSessions} that drives the observable
|
|
2611
|
+
* `SessionsState` store instead of returning the array. UI layers subscribe
|
|
2612
|
+
* via `onSessionsStateChange` and stay pure readers — mirrors `fetchTxHistory`.
|
|
2613
|
+
*/
|
|
2614
|
+
async fetchSessions() {
|
|
2615
|
+
this._setSessionsState({ step: "loading" });
|
|
2616
|
+
try {
|
|
2617
|
+
const sessions = await this.listSessions();
|
|
2618
|
+
this._setSessionsState({ step: "loaded", sessions });
|
|
2619
|
+
} catch (err) {
|
|
2620
|
+
const message = err instanceof Error ? err.message : "Failed to load sessions";
|
|
2621
|
+
this._setSessionsState({ step: "error", message });
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2431
2624
|
/**
|
|
2432
2625
|
* Revoke a specific refresh-token family (a single device session). Use
|
|
2433
2626
|
* `listSessions` to enumerate the familyIds. Revoking the current session
|
|
@@ -2455,6 +2648,14 @@ var PollarClient = class {
|
|
|
2455
2648
|
getNetworkState() {
|
|
2456
2649
|
return this._networkState;
|
|
2457
2650
|
}
|
|
2651
|
+
/**
|
|
2652
|
+
* The client's level-gated logger (built from `logLevel` / `logger`). Exposed
|
|
2653
|
+
* so the runtime layer (`@pollar/react`) can route its own logs through the
|
|
2654
|
+
* same level and sink instead of calling `console` directly.
|
|
2655
|
+
*/
|
|
2656
|
+
getLogger() {
|
|
2657
|
+
return this._log;
|
|
2658
|
+
}
|
|
2458
2659
|
setNetwork(network) {
|
|
2459
2660
|
this._setNetworkState({ step: "connected", network });
|
|
2460
2661
|
}
|
|
@@ -2504,16 +2705,19 @@ var PollarClient = class {
|
|
|
2504
2705
|
cb(this._walletBalanceState);
|
|
2505
2706
|
return () => this._walletBalanceStateListeners.delete(cb);
|
|
2506
2707
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2708
|
+
/**
|
|
2709
|
+
* Refreshes the balances of the authenticated user's OWN wallet. The wallet
|
|
2710
|
+
* and network are resolved server-side from the session — no arguments. Drives
|
|
2711
|
+
* `walletBalanceState`. For an arbitrary wallet, use {@link getWalletBalance}.
|
|
2712
|
+
*/
|
|
2713
|
+
async refreshBalance() {
|
|
2714
|
+
if (!this._session?.wallet?.address) {
|
|
2510
2715
|
this._setWalletBalanceState({ step: "error", message: "No wallet connected" });
|
|
2511
2716
|
return;
|
|
2512
2717
|
}
|
|
2513
2718
|
this._setWalletBalanceState({ step: "loading" });
|
|
2514
2719
|
try {
|
|
2515
|
-
const
|
|
2516
|
-
const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
|
|
2720
|
+
const { data, error } = await this._api.GET("/wallet/balance");
|
|
2517
2721
|
if (!error && data?.success && data.content) {
|
|
2518
2722
|
this._setWalletBalanceState({ step: "loaded", data: data.content });
|
|
2519
2723
|
} else {
|
|
@@ -2523,6 +2727,53 @@ var PollarClient = class {
|
|
|
2523
2727
|
this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
|
|
2524
2728
|
}
|
|
2525
2729
|
}
|
|
2730
|
+
/**
|
|
2731
|
+
* General-purpose balance lookup for ANY wallet on ANY network — not scoped
|
|
2732
|
+
* to this application. Enumerates the account's real on-chain holdings via
|
|
2733
|
+
* Horizon (server-side) and returns the data directly (no reactive state).
|
|
2734
|
+
* `network` defaults to the client's current network.
|
|
2735
|
+
*/
|
|
2736
|
+
async getWalletBalance(publicKey, network) {
|
|
2737
|
+
const { data, error } = await this._api.GET("/wallet/{publicKey}/balance", {
|
|
2738
|
+
params: { path: { publicKey }, query: { network: network ?? this.getNetwork() } }
|
|
2739
|
+
});
|
|
2740
|
+
if (error || !data?.success || !data.content) {
|
|
2741
|
+
throw new Error("[PollarClient] Failed to load wallet balance");
|
|
2742
|
+
}
|
|
2743
|
+
return data.content;
|
|
2744
|
+
}
|
|
2745
|
+
// ─── Enabled assets ───────────────────────────────────────────────────────
|
|
2746
|
+
getEnabledAssetsState() {
|
|
2747
|
+
return this._enabledAssetsState;
|
|
2748
|
+
}
|
|
2749
|
+
onEnabledAssetsStateChange(cb) {
|
|
2750
|
+
this._enabledAssetsStateListeners.add(cb);
|
|
2751
|
+
cb(this._enabledAssetsState);
|
|
2752
|
+
return () => this._enabledAssetsStateListeners.delete(cb);
|
|
2753
|
+
}
|
|
2754
|
+
/**
|
|
2755
|
+
* Loads the application's enabled assets paired with the authenticated
|
|
2756
|
+
* wallet's on-chain trustline state — so the SDK knows which trustlines still
|
|
2757
|
+
* need to be added. Wallet and network are resolved server-side from the
|
|
2758
|
+
* session. Drives `enabledAssetsState`; mirrors {@link refreshBalance}.
|
|
2759
|
+
*/
|
|
2760
|
+
async refreshAssets() {
|
|
2761
|
+
if (!this._session?.wallet?.address) {
|
|
2762
|
+
this._setEnabledAssetsState({ step: "error", message: "No wallet connected" });
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
this._setEnabledAssetsState({ step: "loading" });
|
|
2766
|
+
try {
|
|
2767
|
+
const { data, error } = await this._api.GET("/wallet/assets");
|
|
2768
|
+
if (!error && data?.success && data.content) {
|
|
2769
|
+
this._setEnabledAssetsState({ step: "loaded", data: data.content });
|
|
2770
|
+
} else {
|
|
2771
|
+
this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
|
|
2772
|
+
}
|
|
2773
|
+
} catch {
|
|
2774
|
+
this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2526
2777
|
// ─── Transactions ─────────────────────────────────────────────────────────
|
|
2527
2778
|
/**
|
|
2528
2779
|
* Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
|
|
@@ -2530,14 +2781,14 @@ var PollarClient = class {
|
|
|
2530
2781
|
* inspect the result without subscribing to state changes.
|
|
2531
2782
|
*/
|
|
2532
2783
|
async buildTx(operation, params, options) {
|
|
2533
|
-
if (!this._session?.wallet?.
|
|
2784
|
+
if (!this._session?.wallet?.address) {
|
|
2534
2785
|
const details = "No wallet connected";
|
|
2535
2786
|
this._setTransactionState({ step: "error", phase: "building", details });
|
|
2536
2787
|
return { status: "error", details };
|
|
2537
2788
|
}
|
|
2538
2789
|
const body = {
|
|
2539
2790
|
network: this.getNetwork(),
|
|
2540
|
-
|
|
2791
|
+
address: this._session.wallet.address,
|
|
2541
2792
|
operation,
|
|
2542
2793
|
params,
|
|
2543
2794
|
options: options ?? {}
|
|
@@ -2553,7 +2804,7 @@ var PollarClient = class {
|
|
|
2553
2804
|
this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
|
|
2554
2805
|
return { status: "error", ...details && { details } };
|
|
2555
2806
|
} catch (err) {
|
|
2556
|
-
|
|
2807
|
+
this._log.error("[PollarClient] buildTx failed", err);
|
|
2557
2808
|
this._setTransactionState({ step: "error", phase: "building" });
|
|
2558
2809
|
return { status: "error" };
|
|
2559
2810
|
}
|
|
@@ -2578,7 +2829,7 @@ var PollarClient = class {
|
|
|
2578
2829
|
const buildData = this._currentBuildData();
|
|
2579
2830
|
this._setTransactionState({ step: "signing", ...buildData && { buildData } });
|
|
2580
2831
|
if (this._walletAdapter) {
|
|
2581
|
-
const accountToSign = this._session?.wallet?.
|
|
2832
|
+
const accountToSign = this._session?.wallet?.address;
|
|
2582
2833
|
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
2583
2834
|
try {
|
|
2584
2835
|
const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
|
|
@@ -2599,10 +2850,10 @@ var PollarClient = class {
|
|
|
2599
2850
|
return { status: "error", ...details && { details } };
|
|
2600
2851
|
}
|
|
2601
2852
|
}
|
|
2602
|
-
const
|
|
2853
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2603
2854
|
try {
|
|
2604
2855
|
const { data, error } = await this._api.POST("/tx/sign", {
|
|
2605
|
-
body: { network: this.getNetwork(),
|
|
2856
|
+
body: { network: this.getNetwork(), address, unsignedXdr }
|
|
2606
2857
|
});
|
|
2607
2858
|
if (!error && data?.success && data.content?.signedXdr) {
|
|
2608
2859
|
const { signedXdr, idempotencyKey } = data.content;
|
|
@@ -2655,12 +2906,12 @@ var PollarClient = class {
|
|
|
2655
2906
|
const buildData = this._currentBuildData();
|
|
2656
2907
|
const outcomeExtra = buildData ? { buildData } : {};
|
|
2657
2908
|
this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
|
|
2658
|
-
const
|
|
2909
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2659
2910
|
try {
|
|
2660
2911
|
const { data, error } = await this._api.POST("/tx/submit", {
|
|
2661
2912
|
body: {
|
|
2662
2913
|
network: this.getNetwork(),
|
|
2663
|
-
|
|
2914
|
+
address,
|
|
2664
2915
|
signedXdr,
|
|
2665
2916
|
...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
|
|
2666
2917
|
}
|
|
@@ -2720,6 +2971,19 @@ var PollarClient = class {
|
|
|
2720
2971
|
* `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
|
|
2721
2972
|
*/
|
|
2722
2973
|
async signAndSubmitTx(unsignedXdr) {
|
|
2974
|
+
if (this._session?.wallet?.type === "smart") {
|
|
2975
|
+
const buildData2 = this._currentBuildData();
|
|
2976
|
+
if (!buildData2?.smart) {
|
|
2977
|
+
const details = "no prepared smart transaction; call buildTx first";
|
|
2978
|
+
this._setTransactionState({ step: "error", phase: "signing", details });
|
|
2979
|
+
return { status: "error", details };
|
|
2980
|
+
}
|
|
2981
|
+
return this._signSubmitSmart(buildData2);
|
|
2982
|
+
}
|
|
2983
|
+
if (!unsignedXdr) {
|
|
2984
|
+
this._setTransactionState({ step: "error", phase: "signing", details: "missing unsigned transaction" });
|
|
2985
|
+
return { status: "error", details: "missing unsigned transaction" };
|
|
2986
|
+
}
|
|
2723
2987
|
if (this._walletAdapter) {
|
|
2724
2988
|
const signed = await this.signTx(unsignedXdr);
|
|
2725
2989
|
if (signed.status === "error") {
|
|
@@ -2737,7 +3001,7 @@ var PollarClient = class {
|
|
|
2737
3001
|
this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
|
|
2738
3002
|
const body = {
|
|
2739
3003
|
network: this.getNetwork(),
|
|
2740
|
-
|
|
3004
|
+
address: this._session?.wallet?.address ?? "",
|
|
2741
3005
|
unsignedXdr
|
|
2742
3006
|
};
|
|
2743
3007
|
try {
|
|
@@ -2806,14 +3070,20 @@ var PollarClient = class {
|
|
|
2806
3070
|
* `signTx`, and `submitTx` separately instead.
|
|
2807
3071
|
*/
|
|
2808
3072
|
async buildAndSignAndSubmitTx(operation, params, options) {
|
|
3073
|
+
if (this._session?.wallet?.type === "smart") {
|
|
3074
|
+
return this._runSmartTx(operation, params, options);
|
|
3075
|
+
}
|
|
2809
3076
|
if (this._walletAdapter) {
|
|
2810
3077
|
const built = await this.buildTx(operation, params, options);
|
|
2811
3078
|
if (built.status === "error") {
|
|
2812
3079
|
return { status: "error", ...built.details && { details: built.details } };
|
|
2813
3080
|
}
|
|
3081
|
+
if (!built.buildData.unsignedXdr) {
|
|
3082
|
+
return { status: "error", details: "build returned no unsigned transaction" };
|
|
3083
|
+
}
|
|
2814
3084
|
return this.signAndSubmitTx(built.buildData.unsignedXdr);
|
|
2815
3085
|
}
|
|
2816
|
-
if (!this._session?.wallet?.
|
|
3086
|
+
if (!this._session?.wallet?.address) {
|
|
2817
3087
|
this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
|
|
2818
3088
|
return { status: "error", details: "No wallet connected" };
|
|
2819
3089
|
}
|
|
@@ -2822,7 +3092,7 @@ var PollarClient = class {
|
|
|
2822
3092
|
const { data, error } = await this._api.POST("/tx/build-sign-submit", {
|
|
2823
3093
|
body: {
|
|
2824
3094
|
network: this.getNetwork(),
|
|
2825
|
-
|
|
3095
|
+
address: this._session.wallet.address,
|
|
2826
3096
|
operation,
|
|
2827
3097
|
params,
|
|
2828
3098
|
options: options ?? {}
|
|
@@ -2866,6 +3136,113 @@ var PollarClient = class {
|
|
|
2866
3136
|
async runTx(operation, params, options) {
|
|
2867
3137
|
return this.buildAndSignAndSubmitTx(operation, params, options);
|
|
2868
3138
|
}
|
|
3139
|
+
/**
|
|
3140
|
+
* Smart-wallet (passkey / C-address) transaction: build (server prepares the
|
|
3141
|
+
* SAC transfer + returns the auth digest) → sign the digest with the passkey
|
|
3142
|
+
* → submit (server assembles the signed auth entry and broadcasts; the
|
|
3143
|
+
* sponsor pays the fee). State machine: building → built → signing →
|
|
3144
|
+
* submitting → success.
|
|
3145
|
+
*/
|
|
3146
|
+
async _runSmartTx(operation, params, options) {
|
|
3147
|
+
const address = this._session?.wallet?.address;
|
|
3148
|
+
if (!address) {
|
|
3149
|
+
this._setTransactionState({ step: "error", phase: "building", details: "No wallet connected" });
|
|
3150
|
+
return { status: "error", details: "No wallet connected" };
|
|
3151
|
+
}
|
|
3152
|
+
if (!this._passkeySign) {
|
|
3153
|
+
const details = "Passkey signer not configured";
|
|
3154
|
+
this._setTransactionState({ step: "error", phase: "signing", details });
|
|
3155
|
+
return { status: "error", details };
|
|
3156
|
+
}
|
|
3157
|
+
this._setTransactionState({ step: "building" });
|
|
3158
|
+
let buildData;
|
|
3159
|
+
try {
|
|
3160
|
+
const body = {
|
|
3161
|
+
network: this.getNetwork(),
|
|
3162
|
+
address,
|
|
3163
|
+
operation,
|
|
3164
|
+
params,
|
|
3165
|
+
options: options ?? {}
|
|
3166
|
+
};
|
|
3167
|
+
const { data, error } = await this._api.POST("/tx/build", { body });
|
|
3168
|
+
if (error || !data?.success || !data.content?.smart) {
|
|
3169
|
+
const details = error?.details ?? "Failed to build transaction";
|
|
3170
|
+
this._setTransactionState({ step: "error", phase: "building", details });
|
|
3171
|
+
return { status: "error", details };
|
|
3172
|
+
}
|
|
3173
|
+
buildData = data.content;
|
|
3174
|
+
} catch (err) {
|
|
3175
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3176
|
+
this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
|
|
3177
|
+
return { status: "error", ...details && { details } };
|
|
3178
|
+
}
|
|
3179
|
+
this._setTransactionState({ step: "built", buildData });
|
|
3180
|
+
return this._signSubmitSmart(buildData);
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Steps 2–3 of the smart-wallet flow: sign the prepared auth digest with the
|
|
3184
|
+
* passkey, then submit. Shared by `_runSmartTx` (atomic) and `signAndSubmitTx`
|
|
3185
|
+
* (split flow, when a smart build is already on the state machine).
|
|
3186
|
+
*/
|
|
3187
|
+
async _signSubmitSmart(buildData) {
|
|
3188
|
+
const address = this._session?.wallet?.address;
|
|
3189
|
+
const smart = buildData.smart;
|
|
3190
|
+
if (!address || !smart) {
|
|
3191
|
+
const details = "no prepared smart transaction";
|
|
3192
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, details });
|
|
3193
|
+
return { status: "error", buildData, details };
|
|
3194
|
+
}
|
|
3195
|
+
if (!this._passkeySign) {
|
|
3196
|
+
const details = "Passkey signer not configured";
|
|
3197
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, details });
|
|
3198
|
+
return { status: "error", buildData, details };
|
|
3199
|
+
}
|
|
3200
|
+
this._setTransactionState({ step: "signing", buildData });
|
|
3201
|
+
let assertion;
|
|
3202
|
+
try {
|
|
3203
|
+
assertion = await this._passkeySign({ credentialId: smart.credentialId, challenge: smart.digest });
|
|
3204
|
+
} catch (err) {
|
|
3205
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3206
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, ...details && { details } });
|
|
3207
|
+
return { status: "error", buildData, ...details && { details } };
|
|
3208
|
+
}
|
|
3209
|
+
this._setTransactionState({ step: "submitting", buildData });
|
|
3210
|
+
const outcomeExtra = { buildData };
|
|
3211
|
+
try {
|
|
3212
|
+
const { data, error } = await this._api.POST("/tx/submit", {
|
|
3213
|
+
body: {
|
|
3214
|
+
network: this.getNetwork(),
|
|
3215
|
+
address,
|
|
3216
|
+
smart: { entryXdr: smart.entryXdr, funcXdr: smart.funcXdr, assertion }
|
|
3217
|
+
}
|
|
3218
|
+
});
|
|
3219
|
+
if (!error && data?.success && data.content) {
|
|
3220
|
+
const { hash, status: backendStatus, resultCode } = data.content;
|
|
3221
|
+
if (backendStatus === "SUCCESS") {
|
|
3222
|
+
this._setTransactionState({ step: "success", hash, buildData });
|
|
3223
|
+
return { status: "success", hash, ...outcomeExtra };
|
|
3224
|
+
}
|
|
3225
|
+
if (backendStatus === "PENDING") {
|
|
3226
|
+
this._setTransactionState({ step: "submitted", hash, buildData });
|
|
3227
|
+
return { status: "pending", hash, ...outcomeExtra };
|
|
3228
|
+
}
|
|
3229
|
+
this._setTransactionState({
|
|
3230
|
+
step: "error",
|
|
3231
|
+
phase: "submitting",
|
|
3232
|
+
buildData,
|
|
3233
|
+
...resultCode && { details: resultCode }
|
|
3234
|
+
});
|
|
3235
|
+
return { status: "error", hash, ...outcomeExtra, ...resultCode && { details: resultCode, resultCode } };
|
|
3236
|
+
}
|
|
3237
|
+
const details = error?.details;
|
|
3238
|
+
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
3239
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
3240
|
+
} catch (err) {
|
|
3241
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3242
|
+
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
3243
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
2869
3246
|
// ─── App config ───────────────────────────────────────────────────────────
|
|
2870
3247
|
async getAppConfig() {
|
|
2871
3248
|
try {
|
|
@@ -2919,10 +3296,18 @@ var PollarClient = class {
|
|
|
2919
3296
|
this._txHistoryState = next;
|
|
2920
3297
|
for (const cb of this._txHistoryStateListeners) cb(next);
|
|
2921
3298
|
}
|
|
3299
|
+
_setSessionsState(next) {
|
|
3300
|
+
this._sessionsState = next;
|
|
3301
|
+
for (const cb of this._sessionsStateListeners) cb(next);
|
|
3302
|
+
}
|
|
2922
3303
|
_setWalletBalanceState(next) {
|
|
2923
3304
|
this._walletBalanceState = next;
|
|
2924
3305
|
for (const cb of this._walletBalanceStateListeners) cb(next);
|
|
2925
3306
|
}
|
|
3307
|
+
_setEnabledAssetsState(next) {
|
|
3308
|
+
this._enabledAssetsState = next;
|
|
3309
|
+
for (const cb of this._enabledAssetsStateListeners) cb(next);
|
|
3310
|
+
}
|
|
2926
3311
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
2927
3312
|
_newController() {
|
|
2928
3313
|
this._loginController?.abort();
|
|
@@ -2932,6 +3317,7 @@ var PollarClient = class {
|
|
|
2932
3317
|
_flowDeps(signal) {
|
|
2933
3318
|
return {
|
|
2934
3319
|
api: this._api,
|
|
3320
|
+
logger: this._log,
|
|
2935
3321
|
basePath: this.basePath,
|
|
2936
3322
|
// SSE status streaming works on web; React Native's `fetch` has no
|
|
2937
3323
|
// readable `response.body`, so those clients poll the non-streaming
|
|
@@ -2947,6 +3333,7 @@ var PollarClient = class {
|
|
|
2947
3333
|
this._walletAdapter = adapter;
|
|
2948
3334
|
await writeWalletType(this._storage, this.apiKeyHash, id);
|
|
2949
3335
|
},
|
|
3336
|
+
...this._passkey ? { passkey: this._passkey } : {},
|
|
2950
3337
|
...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
|
|
2951
3338
|
};
|
|
2952
3339
|
}
|
|
@@ -2976,19 +3363,19 @@ var PollarClient = class {
|
|
|
2976
3363
|
}
|
|
2977
3364
|
}
|
|
2978
3365
|
if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
|
|
2979
|
-
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
|
|
3366
|
+
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter(this.getNetwork() === "mainnet" ? "public" : "testnet");
|
|
2980
3367
|
throw new Error(
|
|
2981
3368
|
`[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
|
|
2982
3369
|
);
|
|
2983
3370
|
}
|
|
2984
3371
|
_handleFlowError(error) {
|
|
2985
3372
|
if (error instanceof Error && error.name === "AbortError") {
|
|
2986
|
-
|
|
3373
|
+
this._log.debug("[PollarClient] Login cancelled");
|
|
2987
3374
|
this._setAuthState({ step: "idle" });
|
|
2988
3375
|
return;
|
|
2989
3376
|
}
|
|
2990
3377
|
if (error instanceof Error && error.code === AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT) {
|
|
2991
|
-
|
|
3378
|
+
this._log.error("[PollarClient]", error.message);
|
|
2992
3379
|
this._setAuthState({
|
|
2993
3380
|
step: "error",
|
|
2994
3381
|
previousStep: this._authState.step,
|
|
@@ -2997,7 +3384,7 @@ var PollarClient = class {
|
|
|
2997
3384
|
});
|
|
2998
3385
|
return;
|
|
2999
3386
|
}
|
|
3000
|
-
|
|
3387
|
+
this._log.error("[PollarClient] Unexpected error in auth flow", error);
|
|
3001
3388
|
this._setAuthState({
|
|
3002
3389
|
step: "error",
|
|
3003
3390
|
previousStep: this._authState.step,
|
|
@@ -3006,32 +3393,77 @@ var PollarClient = class {
|
|
|
3006
3393
|
});
|
|
3007
3394
|
}
|
|
3008
3395
|
async _restoreSession() {
|
|
3009
|
-
this._session = await readStorage(this._storage, this.apiKeyHash);
|
|
3396
|
+
this._session = await readStorage(this._storage, this.apiKeyHash, this._log);
|
|
3010
3397
|
if (this._session) {
|
|
3011
3398
|
const storedType = await readWalletType(this._storage, this.apiKeyHash);
|
|
3012
3399
|
if (storedType) {
|
|
3013
3400
|
try {
|
|
3014
3401
|
this._walletAdapter = await this._resolveWalletAdapter(storedType);
|
|
3015
3402
|
} catch (err) {
|
|
3016
|
-
|
|
3403
|
+
this._log.warn("[PollarClient] Could not restore wallet adapter for stored id", { id: storedType, err });
|
|
3017
3404
|
}
|
|
3018
3405
|
}
|
|
3019
|
-
|
|
3020
|
-
this._setAuthState({ step: "authenticated", session: this._session });
|
|
3406
|
+
this._log.info("[PollarClient] Session restored from storage");
|
|
3407
|
+
this._setAuthState({ step: "authenticated", session: this._session, verified: false });
|
|
3021
3408
|
this._scheduleNextRefresh();
|
|
3409
|
+
void this._resume();
|
|
3022
3410
|
} else {
|
|
3023
|
-
|
|
3411
|
+
this._log.info("[PollarClient] No session in storage");
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
/**
|
|
3415
|
+
* Validate the restored session against the server and repopulate the
|
|
3416
|
+
* in-memory profile (PII is never persisted, so it's null after a cold
|
|
3417
|
+
* reload). Goes through the normal authed client, so it coalesces with any
|
|
3418
|
+
* in-flight refresh (onRequest awaits `_refreshPromise`) and, being a GET,
|
|
3419
|
+
* is auto-retried after a 401-triggered refresh.
|
|
3420
|
+
*
|
|
3421
|
+
* - 200 → store profile, mark the session `verified`.
|
|
3422
|
+
* - 401 → the refresh-on-401 path already ran; if the family was
|
|
3423
|
+
* revoked, refresh failed and `_clearSession()` took us to
|
|
3424
|
+
* idle. Nothing to do here — don't double-handle.
|
|
3425
|
+
* - network error → stay optimistic (do NOT log out); revalidated later on
|
|
3426
|
+
* `visibilitychange` or first use.
|
|
3427
|
+
*/
|
|
3428
|
+
async _resume() {
|
|
3429
|
+
if (!this._session) return;
|
|
3430
|
+
this._resumeController?.abort();
|
|
3431
|
+
const controller = new AbortController();
|
|
3432
|
+
this._resumeController = controller;
|
|
3433
|
+
try {
|
|
3434
|
+
const { data, error } = await this._api.GET("/auth/session/resume", { signal: controller.signal });
|
|
3435
|
+
if (error || !data) return;
|
|
3436
|
+
const content = data.content;
|
|
3437
|
+
if (!content || !this._session) return;
|
|
3438
|
+
this._profile = { ...content };
|
|
3439
|
+
this._setAuthState({ step: "authenticated", session: this._session, verified: true });
|
|
3440
|
+
} catch (err) {
|
|
3441
|
+
if (err?.name === "AbortError") return;
|
|
3442
|
+
this._log.warn("[PollarClient] resume failed (network); will retry", err);
|
|
3443
|
+
} finally {
|
|
3444
|
+
if (this._resumeController === controller) this._resumeController = null;
|
|
3024
3445
|
}
|
|
3025
3446
|
}
|
|
3026
3447
|
async _storeSession(session) {
|
|
3027
|
-
|
|
3448
|
+
this._log.info("[PollarClient] Session stored");
|
|
3449
|
+
const w = session.wallet;
|
|
3028
3450
|
const persisted = {
|
|
3029
3451
|
clientSessionId: session.clientSessionId,
|
|
3030
3452
|
userId: session.userId ?? null,
|
|
3031
3453
|
status: session.status,
|
|
3032
3454
|
token: session.token,
|
|
3033
3455
|
user: session.user,
|
|
3034
|
-
|
|
3456
|
+
// The wire response still carries the legacy `publicKey` alias (kept for
|
|
3457
|
+
// older SDKs); the persisted session standardizes on `address` only.
|
|
3458
|
+
wallet: {
|
|
3459
|
+
type: w.type,
|
|
3460
|
+
address: w.address ?? w.publicKey ?? null,
|
|
3461
|
+
...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
|
|
3462
|
+
...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},
|
|
3463
|
+
...w.linkedAt !== void 0 ? { linkedAt: w.linkedAt } : {},
|
|
3464
|
+
...w.network !== void 0 ? { network: w.network } : {},
|
|
3465
|
+
...w.deployTxHash !== void 0 ? { deployTxHash: w.deployTxHash } : {}
|
|
3466
|
+
}
|
|
3035
3467
|
};
|
|
3036
3468
|
this._session = persisted;
|
|
3037
3469
|
if (session.data) {
|
|
@@ -3044,11 +3476,11 @@ var PollarClient = class {
|
|
|
3044
3476
|
};
|
|
3045
3477
|
}
|
|
3046
3478
|
await writeStorage(this._storage, this.apiKeyHash, persisted);
|
|
3047
|
-
this._setAuthState({ step: "authenticated", session: persisted });
|
|
3479
|
+
this._setAuthState({ step: "authenticated", session: persisted, verified: true });
|
|
3048
3480
|
this._scheduleNextRefresh();
|
|
3049
3481
|
}
|
|
3050
3482
|
async _clearSession() {
|
|
3051
|
-
|
|
3483
|
+
this._log.info("[PollarClient] Session cleared");
|
|
3052
3484
|
this._clearRefreshTimer();
|
|
3053
3485
|
this._session = null;
|
|
3054
3486
|
this._profile = null;
|
|
@@ -3057,7 +3489,7 @@ var PollarClient = class {
|
|
|
3057
3489
|
try {
|
|
3058
3490
|
await this._keyManager.reset();
|
|
3059
3491
|
} catch (err) {
|
|
3060
|
-
|
|
3492
|
+
this._log.warn("[PollarClient] KeyManager reset failed during clearSession", err);
|
|
3061
3493
|
}
|
|
3062
3494
|
await removeStorage(this._storage, this.apiKeyHash);
|
|
3063
3495
|
this._transactionState = null;
|
|
@@ -3069,17 +3501,17 @@ var PollarClient = class {
|
|
|
3069
3501
|
_setNetworkState(next) {
|
|
3070
3502
|
this._networkState = next;
|
|
3071
3503
|
const label = next.step === "connected" ? next.network : next.step;
|
|
3072
|
-
|
|
3504
|
+
this._log.debug(`[PollarClient] network:${label}`);
|
|
3073
3505
|
for (const cb of this._networkStateListeners) cb(next);
|
|
3074
3506
|
}
|
|
3075
3507
|
_setAuthState(next) {
|
|
3076
3508
|
this._authState = next;
|
|
3077
|
-
|
|
3509
|
+
this._log.debug(`[PollarClient] auth:${next.step}`);
|
|
3078
3510
|
for (const cb of this._authStateListeners) cb(next);
|
|
3079
3511
|
}
|
|
3080
3512
|
_setTransactionState(next) {
|
|
3081
3513
|
this._transactionState = next;
|
|
3082
|
-
|
|
3514
|
+
this._log.debug(`[PollarClient] transaction:${next.step}`);
|
|
3083
3515
|
for (const cb of this._transactionStateListeners) cb(next);
|
|
3084
3516
|
}
|
|
3085
3517
|
/**
|
|
@@ -3127,6 +3559,6 @@ var StellarClient = class {
|
|
|
3127
3559
|
// src/index.ts
|
|
3128
3560
|
_setDefaultKeyManagerFactory((_storage, apiKey) => new WebCryptoKeyManager(apiKey));
|
|
3129
3561
|
|
|
3130
|
-
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 };
|
|
3562
|
+
export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, POLLAR_CORE_VERSION, PollarClient, StellarClient, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, claimDistributionRule, computeJwkThumbprint, createLocalStorageAdapter, createLogger, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, listDistributionRules, normalizeHtu, pollKycStatus, pollRampTransaction, resolveKyc, startKyc };
|
|
3131
3563
|
//# sourceMappingURL=index.mjs.map
|
|
3132
3564
|
//# sourceMappingURL=index.mjs.map
|