@opexa/portal-sdk 0.59.95 → 0.59.96

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/dist/index.cjs CHANGED
@@ -4709,6 +4709,20 @@ async function getFingerPrint() {
4709
4709
  return null;
4710
4710
  }
4711
4711
  }
4712
+ function isWebPlatform() {
4713
+ try {
4714
+ return core.Capacitor.getPlatform() === "web";
4715
+ } catch {
4716
+ return true;
4717
+ }
4718
+ }
4719
+ function isIOSPlatform() {
4720
+ try {
4721
+ return core.Capacitor.getPlatform() === "ios";
4722
+ } catch {
4723
+ return false;
4724
+ }
4725
+ }
4712
4726
 
4713
4727
  // src/utils/status-code-to-operation-error.ts
4714
4728
  function statusCodeToOperationError(value, message) {
@@ -4729,13 +4743,6 @@ var ERROR_MAP = {
4729
4743
  };
4730
4744
 
4731
4745
  // src/services/auth.service.ts
4732
- function isWebPlatform() {
4733
- try {
4734
- return core.Capacitor.getPlatform() === "web";
4735
- } catch {
4736
- return false;
4737
- }
4738
- }
4739
4746
  var AuthService = class {
4740
4747
  url;
4741
4748
  options;
@@ -4791,12 +4798,15 @@ var AuthService = class {
4791
4798
  try {
4792
4799
  const isWeb = isWebPlatform();
4793
4800
  const queryParam = isWeb && version === 4 ? "?type=web" : "";
4794
- const res = await fetch(`${this.url}${version !== 1 ? `/v${version}/sessions` : "/sessions"}${queryParam}`, {
4795
- method: "POST",
4796
- headers,
4797
- body: JSON.stringify(input),
4798
- ...version === 4 && { credentials: "include" }
4799
- });
4801
+ const res = await fetch(
4802
+ `${this.url}${version !== 1 ? `/v${version}/sessions` : "/sessions"}${queryParam}`,
4803
+ {
4804
+ method: "POST",
4805
+ headers,
4806
+ body: JSON.stringify(input),
4807
+ ...version === 4 && { credentials: "include" }
4808
+ }
4809
+ );
4800
4810
  const body = await res.json();
4801
4811
  if (res.ok) {
4802
4812
  return {
@@ -4879,7 +4889,7 @@ var AuthService = class {
4879
4889
  }
4880
4890
  async refreshSession(refreshToken, version = 1) {
4881
4891
  const headers = new Headers(this.headers);
4882
- if (version !== 4) {
4892
+ if (version !== 4 || refreshToken) {
4883
4893
  headers.append("Authorization", `Bearer ${refreshToken}`);
4884
4894
  }
4885
4895
  try {
@@ -4993,11 +5003,14 @@ var AuthService = class {
4993
5003
  }
4994
5004
  }
4995
5005
  try {
4996
- const res = await fetch(`${this.url}${version !== 1 ? `/v${version}/otps` : "/otps"}`, {
4997
- method: "POST",
4998
- headers: this.headers,
4999
- body: JSON.stringify(input)
5000
- });
5006
+ const res = await fetch(
5007
+ `${this.url}${version !== 1 ? `/v${version}/otps` : "/otps"}`,
5008
+ {
5009
+ method: "POST",
5010
+ headers: this.headers,
5011
+ body: JSON.stringify(input)
5012
+ }
5013
+ );
5001
5014
  if (res.status === 403) {
5002
5015
  const data = await res.json();
5003
5016
  const code = getErrorCode(data.message);
@@ -7457,6 +7470,48 @@ function pollable(func, config) {
7457
7470
  };
7458
7471
  }
7459
7472
 
7473
+ // src/sdk/refresh-token-store.ts
7474
+ var PreferencesRefreshTokenStore = class {
7475
+ constructor(key, logger) {
7476
+ this.key = key;
7477
+ this.logger = logger;
7478
+ }
7479
+ async preferences() {
7480
+ const { Preferences } = await import('@capacitor/preferences');
7481
+ return Preferences;
7482
+ }
7483
+ async get() {
7484
+ if (!isIOSPlatform()) return null;
7485
+ try {
7486
+ const { value } = await (await this.preferences()).get({ key: this.key });
7487
+ return value ?? null;
7488
+ } catch (err) {
7489
+ this.logger.error(`Failed to read the refresh token: ${err}`);
7490
+ return null;
7491
+ }
7492
+ }
7493
+ async set(token) {
7494
+ if (!isIOSPlatform()) return;
7495
+ try {
7496
+ await (await this.preferences()).set({ key: this.key, value: token });
7497
+ } catch (err) {
7498
+ this.logger.error(`Failed to persist the refresh token: ${err}`);
7499
+ }
7500
+ }
7501
+ async remove() {
7502
+ if (!isIOSPlatform()) return;
7503
+ try {
7504
+ await (await this.preferences()).remove({ key: this.key });
7505
+ } catch (err) {
7506
+ this.logger.error(`Failed to clear the refresh token: ${err}`);
7507
+ }
7508
+ }
7509
+ };
7510
+ function createRefreshTokenStore(logger, sessionPrefix) {
7511
+ const key = sessionPrefix ? `${sessionPrefix}/session/refresh-token` : "session/refresh-token";
7512
+ return new PreferencesRefreshTokenStore(key, logger);
7513
+ }
7514
+
7460
7515
  // src/sdk/session-manager.ts
7461
7516
  var SessionManager = class {
7462
7517
  logger;
@@ -7466,17 +7521,22 @@ var SessionManager = class {
7466
7521
  walletService;
7467
7522
  _refreshing = false;
7468
7523
  /*
7469
- * v4 access tokens are never persisted (localStorage/cookie) so that an
7470
- * XSS-readable storage layer never holds a live access token. They only
7471
- * live in memory and get re-minted from the HttpOnly refresh cookie.
7524
+ * v4 access tokens live in memory only, so an XSS-readable storage layer
7525
+ * never holds a live one. Re-minted from the HttpOnly cookie, or from
7526
+ * `refreshTokenStore` on iOS.
7472
7527
  */
7473
7528
  v4AccessToken = null;
7474
7529
  v4AccessTokenExpiresAt = null;
7475
7530
  v4RefreshPromise = null;
7531
+ refreshTokenStore;
7476
7532
  constructor(config) {
7477
7533
  this.authService = config.authService;
7478
7534
  this.walletService = config.walletService;
7479
7535
  this.logger = config.logger;
7536
+ this.refreshTokenStore = createRefreshTokenStore(
7537
+ config.logger,
7538
+ config.sessionPrefix
7539
+ );
7480
7540
  if (config.sessionPrefix) {
7481
7541
  this.storageKey = `${config.sessionPrefix}/${this.storageKey}`;
7482
7542
  this.platformStorageKey = `${config.sessionPrefix}/${this.platformStorageKey}`;
@@ -7488,7 +7548,7 @@ var SessionManager = class {
7488
7548
  set refreshing(value) {
7489
7549
  this._refreshing = value;
7490
7550
  }
7491
- persist(data, version) {
7551
+ async persist(data, version) {
7492
7552
  const now = /* @__PURE__ */ new Date();
7493
7553
  if (version === 4) {
7494
7554
  if (!data.session || !data.accessToken) {
@@ -7499,6 +7559,9 @@ var SessionManager = class {
7499
7559
  }
7500
7560
  this.v4AccessToken = data.accessToken;
7501
7561
  this.v4AccessTokenExpiresAt = addMinutes(now, 4).getTime();
7562
+ if (data.refreshToken) {
7563
+ await this.refreshTokenStore.set(data.refreshToken);
7564
+ }
7502
7565
  localStorage.setItem(
7503
7566
  this.storageKey,
7504
7567
  JSON.stringify({ version, session: data.session })
@@ -7561,7 +7624,8 @@ var SessionManager = class {
7561
7624
  maxAttempt: 5
7562
7625
  })();
7563
7626
  if (!r1.ok) return r1;
7564
- if (!this.persist(r1.data, version)) return this.malformedResponseError();
7627
+ if (!await this.persist(r1.data, version))
7628
+ return this.malformedResponseError();
7565
7629
  return {
7566
7630
  ok: true,
7567
7631
  data: null
@@ -7570,7 +7634,7 @@ var SessionManager = class {
7570
7634
  if (input.type === "MOBILE_NUMBER") {
7571
7635
  const res2 = await this.authService.createSession(input, version);
7572
7636
  if (res2.ok) {
7573
- if (!this.persist(res2.data, version))
7637
+ if (!await this.persist(res2.data, version))
7574
7638
  return this.malformedResponseError();
7575
7639
  return {
7576
7640
  ok: true,
@@ -7589,7 +7653,7 @@ var SessionManager = class {
7589
7653
  version
7590
7654
  );
7591
7655
  if (res2.ok) {
7592
- if (!this.persist(res2.data, version))
7656
+ if (!await this.persist(res2.data, version))
7593
7657
  return this.malformedResponseError();
7594
7658
  return {
7595
7659
  ok: true,
@@ -7602,7 +7666,7 @@ var SessionManager = class {
7602
7666
  localStorage.setItem(this.platformStorageKey, "CABINET");
7603
7667
  const res2 = await this.authService.createSession(input, version);
7604
7668
  if (res2.ok) {
7605
- if (!this.persist(res2.data, version))
7669
+ if (!await this.persist(res2.data, version))
7606
7670
  return this.malformedResponseError();
7607
7671
  return {
7608
7672
  ok: true,
@@ -7621,7 +7685,8 @@ var SessionManager = class {
7621
7685
  }
7622
7686
  };
7623
7687
  }
7624
- if (!this.persist(res.data, version)) return this.malformedResponseError();
7688
+ if (!await this.persist(res.data, version))
7689
+ return this.malformedResponseError();
7625
7690
  return {
7626
7691
  ok: true,
7627
7692
  data: null
@@ -7632,7 +7697,7 @@ var SessionManager = class {
7632
7697
  if (res.ok) {
7633
7698
  if (this.isServer) {
7634
7699
  this.logger.warn("'localStorage' is not available on the server.");
7635
- } else if (!this.persist(res.data, 1)) {
7700
+ } else if (!await this.persist(res.data, 1)) {
7636
7701
  return this.malformedResponseError();
7637
7702
  }
7638
7703
  return { ok: true };
@@ -7822,14 +7887,31 @@ var SessionManager = class {
7822
7887
  }
7823
7888
  async requestV4Refresh(session) {
7824
7889
  this.logger.info("Refreshing session...");
7890
+ let refreshToken;
7891
+ if (isIOSPlatform()) {
7892
+ refreshToken = await this.refreshTokenStore.get() ?? void 0;
7893
+ if (!refreshToken) {
7894
+ this.logger.warn("No stored refresh token. Session expired.");
7895
+ this.v4AccessToken = null;
7896
+ this.v4AccessTokenExpiresAt = null;
7897
+ return {
7898
+ ok: false,
7899
+ error: {
7900
+ name: "SessionExpiredError",
7901
+ message: "Session expired."
7902
+ }
7903
+ };
7904
+ }
7905
+ }
7825
7906
  this.refreshing = true;
7826
- const res = await this.authService.refreshSession(void 0, 4);
7907
+ const res = await this.authService.refreshSession(refreshToken, 4);
7827
7908
  this.refreshing = false;
7828
7909
  if (!res.ok) {
7829
7910
  this.logger.error(`Failed to refresh session: ${res.error.message}`);
7830
7911
  if (res.error.name === "InvalidTokenError" || res.error.name === "AccountBlacklistedError" || res.error.name === "VerificationLockedError" || res.error.name === "SessionExpiredError") {
7831
7912
  this.v4AccessToken = null;
7832
7913
  this.v4AccessTokenExpiresAt = null;
7914
+ await this.refreshTokenStore.remove();
7833
7915
  }
7834
7916
  return {
7835
7917
  ok: false,
@@ -7837,6 +7919,9 @@ var SessionManager = class {
7837
7919
  };
7838
7920
  }
7839
7921
  this.logger.success("Session refreshed!");
7922
+ if (res.data.refreshToken) {
7923
+ await this.refreshTokenStore.set(res.data.refreshToken);
7924
+ }
7840
7925
  const now = /* @__PURE__ */ new Date();
7841
7926
  this.v4AccessToken = res.data.accessToken;
7842
7927
  this.v4AccessTokenExpiresAt = addMinutes(now, 4).getTime();
@@ -7869,6 +7954,7 @@ var SessionManager = class {
7869
7954
  }
7870
7955
  this.v4AccessToken = null;
7871
7956
  this.v4AccessTokenExpiresAt = null;
7957
+ await this.refreshTokenStore.remove();
7872
7958
  localStorage.removeItem(this.storageKey);
7873
7959
  }
7874
7960
  async verify() {
@@ -7912,6 +7998,7 @@ var SessionManager = class {
7912
7998
  localStorage.removeItem(this.storageKey);
7913
7999
  this.v4AccessToken = null;
7914
8000
  this.v4AccessTokenExpiresAt = null;
8001
+ await this.refreshTokenStore.remove();
7915
8002
  }
7916
8003
  return v ? { valid: true } : { valid: false, reason: "INVALID_SESSION" };
7917
8004
  }
@@ -7941,16 +8028,19 @@ var SessionManagerCookie = class {
7941
8028
  walletService;
7942
8029
  _refreshing = false;
7943
8030
  /*
7944
- * v4 access tokens are never persisted (localStorage/cookie) so that an
7945
- * XSS-readable storage layer never holds a live access token. They only
7946
- * live in memory and get re-minted from the HttpOnly refresh cookie.
8031
+ * v4 access tokens live in memory only, so an XSS-readable storage layer
8032
+ * never holds a live one. Re-minted from the HttpOnly cookie, or from
8033
+ * `refreshTokenStore` on iOS.
7947
8034
  */
7948
8035
  v4AccessToken = null;
7949
8036
  v4AccessTokenExpiresAt = null;
8037
+ v4RefreshPromise = null;
8038
+ refreshTokenStore;
7950
8039
  constructor(config) {
7951
8040
  this.authService = config.authService;
7952
8041
  this.walletService = config.walletService;
7953
8042
  this.logger = config.logger;
8043
+ this.refreshTokenStore = createRefreshTokenStore(config.logger);
7954
8044
  }
7955
8045
  get refreshing() {
7956
8046
  return this._refreshing;
@@ -7958,15 +8048,18 @@ var SessionManagerCookie = class {
7958
8048
  set refreshing(value) {
7959
8049
  this._refreshing = value;
7960
8050
  }
7961
- persist(data, version) {
8051
+ async persist(data, version) {
7962
8052
  const now = /* @__PURE__ */ new Date();
7963
8053
  if (version === 4) {
7964
8054
  this.v4AccessToken = data.accessToken ?? null;
7965
8055
  this.v4AccessTokenExpiresAt = addMinutes(now, 5).getTime();
8056
+ if (data.refreshToken) {
8057
+ await this.refreshTokenStore.set(data.refreshToken);
8058
+ }
7966
8059
  cookies__default.default.set(
7967
8060
  this.storageKey,
7968
8061
  JSON.stringify({ version, session: data.session }),
7969
- { expires: subMinutes(addDays(now, 15), 2).getTime() }
8062
+ { expires: subMinutes(addDays(now, 15), 2) }
7970
8063
  );
7971
8064
  return;
7972
8065
  }
@@ -7978,7 +8071,7 @@ var SessionManagerCookie = class {
7978
8071
  accessTokenExpiresAt: addMinutes(now, 8).getTime(),
7979
8072
  refreshTokenExpiresAt: subMinutes(addDays(now, 30), 2).getTime()
7980
8073
  }),
7981
- { expires: subMinutes(addDays(now, 30), 2).getTime() }
8074
+ { expires: subMinutes(addDays(now, 30), 2) }
7982
8075
  );
7983
8076
  }
7984
8077
  async create(input, version = 1) {
@@ -8007,7 +8100,7 @@ var SessionManagerCookie = class {
8007
8100
  maxAttempt: 5
8008
8101
  })();
8009
8102
  if (!r1.ok) return r1;
8010
- this.persist(r1.data, version);
8103
+ await this.persist(r1.data, version);
8011
8104
  return {
8012
8105
  ok: true,
8013
8106
  data: null
@@ -8016,7 +8109,7 @@ var SessionManagerCookie = class {
8016
8109
  if (input.type === "MOBILE_NUMBER") {
8017
8110
  const res2 = await this.authService.createSession(input, version);
8018
8111
  if (res2.ok) {
8019
- this.persist(res2.data, version);
8112
+ await this.persist(res2.data, version);
8020
8113
  return {
8021
8114
  ok: true,
8022
8115
  data: null
@@ -8033,7 +8126,7 @@ var SessionManagerCookie = class {
8033
8126
  version
8034
8127
  );
8035
8128
  if (res2.ok) {
8036
- this.persist(res2.data, version);
8129
+ await this.persist(res2.data, version);
8037
8130
  return {
8038
8131
  ok: true,
8039
8132
  data: null
@@ -8045,7 +8138,7 @@ var SessionManagerCookie = class {
8045
8138
  localStorage.setItem(this.platformStorageKey, "CABINET");
8046
8139
  const res2 = await this.authService.createSession(input, version);
8047
8140
  if (res2.ok) {
8048
- this.persist(res2.data, version);
8141
+ await this.persist(res2.data, version);
8049
8142
  return {
8050
8143
  ok: true,
8051
8144
  data: null
@@ -8063,7 +8156,7 @@ var SessionManagerCookie = class {
8063
8156
  }
8064
8157
  };
8065
8158
  }
8066
- this.persist(res.data, version);
8159
+ await this.persist(res.data, version);
8067
8160
  return {
8068
8161
  ok: true,
8069
8162
  data: null
@@ -8075,7 +8168,7 @@ var SessionManagerCookie = class {
8075
8168
  if (this.isServer) {
8076
8169
  this.logger.warn("'client cookies' is not available on the server.");
8077
8170
  } else {
8078
- this.persist(res.data, 1);
8171
+ await this.persist(res.data, 1);
8079
8172
  }
8080
8173
  return { ok: true };
8081
8174
  } else {
@@ -8155,7 +8248,7 @@ var SessionManagerCookie = class {
8155
8248
  refreshTokenExpiresAt: subMinutes(addDays(now, 30), 2).getTime()
8156
8249
  };
8157
8250
  cookies__default.default.set(this.storageKey, JSON.stringify(obj), {
8158
- expires: subMinutes(addDays(now, 30), 2).getTime()
8251
+ expires: subMinutes(addDays(now, 30), 2)
8159
8252
  });
8160
8253
  }
8161
8254
  return {
@@ -8227,7 +8320,7 @@ var SessionManagerCookie = class {
8227
8320
  refreshTokenExpiresAt: subMinutes(addDays(now, 30), 2).getTime()
8228
8321
  };
8229
8322
  cookies__default.default.set(this.storageKey, JSON.stringify(obj), {
8230
- expires: subMinutes(addDays(now, 30), 2).getTime()
8323
+ expires: subMinutes(addDays(now, 30), 2)
8231
8324
  });
8232
8325
  return {
8233
8326
  ok: true,
@@ -8258,15 +8351,41 @@ var SessionManagerCookie = class {
8258
8351
  return await this.refreshV4(session);
8259
8352
  }
8260
8353
  async refreshV4(session) {
8354
+ if (this.v4RefreshPromise) return await this.v4RefreshPromise;
8355
+ this.v4RefreshPromise = this.requestV4Refresh(session);
8356
+ try {
8357
+ return await this.v4RefreshPromise;
8358
+ } finally {
8359
+ this.v4RefreshPromise = null;
8360
+ }
8361
+ }
8362
+ async requestV4Refresh(session) {
8261
8363
  this.logger.info("Refreshing session...");
8364
+ let refreshToken;
8365
+ if (isIOSPlatform()) {
8366
+ refreshToken = await this.refreshTokenStore.get() ?? void 0;
8367
+ if (!refreshToken) {
8368
+ this.logger.warn("No stored refresh token. Session expired.");
8369
+ this.v4AccessToken = null;
8370
+ this.v4AccessTokenExpiresAt = null;
8371
+ return {
8372
+ ok: false,
8373
+ error: {
8374
+ name: "SessionExpiredError",
8375
+ message: "Session expired."
8376
+ }
8377
+ };
8378
+ }
8379
+ }
8262
8380
  this.refreshing = true;
8263
- const res = await this.authService.refreshSession(void 0, 4);
8381
+ const res = await this.authService.refreshSession(refreshToken, 4);
8264
8382
  this.refreshing = false;
8265
8383
  if (!res.ok) {
8266
8384
  this.logger.error(`Failed to refresh session: ${res.error.message}`);
8267
8385
  if (res.error.name === "InvalidTokenError" || res.error.name === "AccountBlacklistedError" || res.error.name === "VerificationLockedError" || res.error.name === "SessionExpiredError") {
8268
8386
  this.v4AccessToken = null;
8269
8387
  this.v4AccessTokenExpiresAt = null;
8388
+ await this.refreshTokenStore.remove();
8270
8389
  }
8271
8390
  return {
8272
8391
  ok: false,
@@ -8274,6 +8393,9 @@ var SessionManagerCookie = class {
8274
8393
  };
8275
8394
  }
8276
8395
  this.logger.success("Session refreshed!");
8396
+ if (res.data.refreshToken) {
8397
+ await this.refreshTokenStore.set(res.data.refreshToken);
8398
+ }
8277
8399
  const now = /* @__PURE__ */ new Date();
8278
8400
  this.v4AccessToken = res.data.accessToken;
8279
8401
  this.v4AccessTokenExpiresAt = addMinutes(now, 5).getTime();
@@ -8306,6 +8428,7 @@ var SessionManagerCookie = class {
8306
8428
  }
8307
8429
  this.v4AccessToken = null;
8308
8430
  this.v4AccessTokenExpiresAt = null;
8431
+ await this.refreshTokenStore.remove();
8309
8432
  cookies__default.default.remove(this.storageKey);
8310
8433
  }
8311
8434
  async verify() {
@@ -8349,6 +8472,7 @@ var SessionManagerCookie = class {
8349
8472
  cookies__default.default.remove(this.storageKey);
8350
8473
  this.v4AccessToken = null;
8351
8474
  this.v4AccessTokenExpiresAt = null;
8475
+ await this.refreshTokenStore.remove();
8352
8476
  }
8353
8477
  return v ? { valid: true } : { valid: false, reason: "INVALID_SESSION" };
8354
8478
  }