@phantom/browser-sdk 1.0.4 → 1.0.6

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.
Files changed (3) hide show
  1. package/dist/index.js +145 -65
  2. package/dist/index.mjs +128 -49
  3. package/package.json +11 -11
package/dist/index.js CHANGED
@@ -34,8 +34,8 @@ __export(src_exports, {
34
34
  BrowserSDK: () => BrowserSDK,
35
35
  DebugCategory: () => DebugCategory,
36
36
  DebugLevel: () => DebugLevel,
37
- NetworkId: () => import_constants7.NetworkId,
38
- PHANTOM_ICON: () => import_constants8.PHANTOM_ICON,
37
+ NetworkId: () => import_constants8.NetworkId,
38
+ PHANTOM_ICON: () => import_constants9.PHANTOM_ICON,
39
39
  debug: () => debug,
40
40
  detectBrowser: () => detectBrowser,
41
41
  getBrowserDisplayName: () => getBrowserDisplayName,
@@ -2764,7 +2764,7 @@ var BrowserAuthProvider = class {
2764
2764
  // OAuth session management - defaults to allow refresh unless explicitly clearing after logout
2765
2765
  clear_previous_session: (phantomOptions.clearPreviousSession ?? false).toString(),
2766
2766
  allow_refresh: (phantomOptions.allowRefresh ?? true).toString(),
2767
- sdk_version: "1.0.4",
2767
+ sdk_version: "1.0.6",
2768
2768
  sdk_type: "browser",
2769
2769
  platform: detectBrowser().name,
2770
2770
  algorithm: phantomOptions.algorithm || import_constants3.DEFAULT_AUTHENTICATOR_ALGORITHM
@@ -2904,8 +2904,8 @@ var Auth2AuthProvider = class {
2904
2904
  *
2905
2905
  * Called by EmbeddedProvider.handleRedirectAuth() after the stamper has
2906
2906
  * already been initialized and a pending Session has been saved to storage.
2907
- * We store the PKCE code_verifier and salt into that session so they survive
2908
- * the page redirect without ever touching sessionStorage.
2907
+ * We store the PKCE code_verifier into that session so it survives the page
2908
+ * redirect without ever touching sessionStorage.
2909
2909
  */
2910
2910
  async authenticate(options) {
2911
2911
  if (!this.stamper.getKeyInfo()) {
@@ -2916,12 +2916,11 @@ var Auth2AuthProvider = class {
2916
2916
  throw new Error("Stamper key pair not found.");
2917
2917
  }
2918
2918
  const codeVerifier = (0, import_auth2.createCodeVerifier)();
2919
- const salt = (0, import_auth2.createSalt)();
2920
2919
  const session = await this.storage.getSession();
2921
2920
  if (!session) {
2922
2921
  throw new Error("Session not found.");
2923
2922
  }
2924
- await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier, salt });
2923
+ await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier });
2925
2924
  const url = await (0, import_auth2.createConnectStartUrl)({
2926
2925
  keyPair,
2927
2926
  connectLoginUrl: this.auth2ProviderOptions.connectLoginUrl,
@@ -2930,7 +2929,8 @@ var Auth2AuthProvider = class {
2930
2929
  sessionId: options.sessionId,
2931
2930
  provider: options.provider,
2932
2931
  codeVerifier,
2933
- salt
2932
+ // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
2933
+ salt: ""
2934
2934
  });
2935
2935
  Auth2AuthProvider.navigate(url);
2936
2936
  }
@@ -2965,24 +2965,21 @@ var Auth2AuthProvider = class {
2965
2965
  const description = this.urlParamsAccessor.getParam("error_description");
2966
2966
  throw new Error(`Auth2 callback error: ${description ?? error}`);
2967
2967
  }
2968
- const { idToken, bearerToken, authUserId, expiresInMs } = await (0, import_auth2.exchangeAuthCode)({
2968
+ const { idToken, bearerToken, authUserId, expiresInMs, refreshToken } = await (0, import_auth2.exchangeAuthCode)({
2969
2969
  authApiBaseUrl: this.auth2ProviderOptions.authApiBaseUrl,
2970
2970
  clientId: this.auth2ProviderOptions.clientId,
2971
2971
  redirectUri: this.auth2ProviderOptions.redirectUri,
2972
2972
  code,
2973
2973
  codeVerifier
2974
2974
  });
2975
- this.stamper.idToken = idToken;
2976
- this.stamper.salt = session?.salt;
2975
+ await this.stamper.setTokens({ idToken, bearerToken, refreshToken, expiresInMs });
2977
2976
  await this.storage.saveSession({
2978
2977
  ...session,
2979
2978
  status: "completed",
2980
2979
  bearerToken,
2981
2980
  authUserId,
2982
- pkceCodeVerifier: void 0,
2981
+ pkceCodeVerifier: void 0
2983
2982
  // no longer needed after code exchange
2984
- salt: void 0
2985
- // no longer needed after nonce binding is complete
2986
2983
  });
2987
2984
  const { organizationId, walletId } = await this.kms.discoverOrganizationAndWalletId(bearerToken, authUserId);
2988
2985
  return {
@@ -3002,27 +2999,48 @@ var Auth2AuthProvider = class {
3002
2999
  var import_bs582 = __toESM(require("bs58"));
3003
3000
  var import_base64url = require("@phantom/base64url");
3004
3001
  var import_sdk_types = require("@phantom/sdk-types");
3002
+ var import_auth22 = require("@phantom/auth2");
3003
+ var import_constants4 = require("@phantom/constants");
3005
3004
  var STORE_NAME = "crypto-keys";
3006
3005
  var ACTIVE_KEY = "auth2-p256-signing-key";
3007
3006
  var Auth2Stamper = class {
3008
3007
  /**
3009
3008
  * @param dbName - IndexedDB database name (use a unique name per app to
3010
3009
  * avoid key collisions with other stampers, e.g. `phantom-auth2-<appId>`).
3010
+ * @param refreshConfig - When provided, the stamper will automatically refresh
3011
+ * the id_token using the refresh_token before it expires.
3011
3012
  */
3012
- constructor(dbName) {
3013
+ constructor(dbName, refreshConfig) {
3013
3014
  this.dbName = dbName;
3015
+ this.refreshConfig = refreshConfig;
3014
3016
  this.db = null;
3015
- this.keyPair = null;
3017
+ this._keyPair = null;
3016
3018
  this._keyInfo = null;
3019
+ this._idToken = null;
3020
+ this._bearerToken = null;
3021
+ this._refreshToken = null;
3022
+ this._tokenExpiresAt = null;
3017
3023
  this.algorithm = import_sdk_types.Algorithm.secp256r1;
3018
- this.type = "PKI";
3024
+ this.type = "OIDC";
3019
3025
  }
3020
3026
  async init() {
3021
3027
  await this.openDB();
3022
- const stored = await this.loadKeyPair();
3028
+ const stored = await this.loadRecord();
3023
3029
  if (stored) {
3024
- this.keyPair = stored.keyPair;
3030
+ this._keyPair = stored.keyPair;
3025
3031
  this._keyInfo = stored.keyInfo;
3032
+ if (stored.idToken) {
3033
+ this._idToken = stored.idToken;
3034
+ }
3035
+ if (stored.bearerToken) {
3036
+ this._bearerToken = stored.bearerToken;
3037
+ }
3038
+ if (stored.refreshToken) {
3039
+ this._refreshToken = stored.refreshToken;
3040
+ }
3041
+ if (stored.tokenExpiresAt) {
3042
+ this._tokenExpiresAt = stored.tokenExpiresAt;
3043
+ }
3026
3044
  return this._keyInfo;
3027
3045
  }
3028
3046
  return this.generateAndStore();
@@ -3031,41 +3049,99 @@ var Auth2Stamper = class {
3031
3049
  return this._keyInfo;
3032
3050
  }
3033
3051
  getCryptoKeyPair() {
3034
- return this.keyPair;
3052
+ return this._keyPair;
3053
+ }
3054
+ /**
3055
+ * Returns the current token state (refreshing proactively if near expiry),
3056
+ * or null if no token has been set yet.
3057
+ */
3058
+ async getTokens() {
3059
+ if (this.refreshConfig && this._refreshToken && this._tokenExpiresAt !== null && Date.now() >= this._tokenExpiresAt - import_constants4.TOKEN_REFRESH_BUFFER_MS) {
3060
+ const refreshed = await (0, import_auth22.refreshToken)({
3061
+ authApiBaseUrl: this.refreshConfig.authApiBaseUrl,
3062
+ clientId: this.refreshConfig.clientId,
3063
+ redirectUri: this.refreshConfig.redirectUri,
3064
+ refreshToken: this._refreshToken
3065
+ });
3066
+ await this.setTokens(refreshed);
3067
+ }
3068
+ if (!this._idToken || !this._bearerToken) {
3069
+ return null;
3070
+ }
3071
+ return {
3072
+ idToken: this._idToken,
3073
+ bearerToken: this._bearerToken,
3074
+ refreshToken: this._refreshToken ?? void 0
3075
+ };
3076
+ }
3077
+ /**
3078
+ * Arms the stamper with the OIDC token data for subsequent KMS stamp() calls.
3079
+ *
3080
+ * Persists the tokens to IndexedDB alongside the key pair so that
3081
+ * auto-connect can restore them on the next page load without a new login.
3082
+ *
3083
+ * @param refreshToken - When provided alongside a `refreshConfig`, enables
3084
+ * silent token refresh before the token expires.
3085
+ * @param expiresInMs - Token lifetime in milliseconds (from `expires_in * 1000`).
3086
+ * Used to compute the absolute expiry time for proactive refresh.
3087
+ */
3088
+ async setTokens({
3089
+ idToken,
3090
+ bearerToken,
3091
+ refreshToken,
3092
+ expiresInMs
3093
+ }) {
3094
+ if (!this.db) {
3095
+ await this.openDB();
3096
+ }
3097
+ this._idToken = idToken;
3098
+ this._bearerToken = bearerToken;
3099
+ this._refreshToken = refreshToken ?? null;
3100
+ this._tokenExpiresAt = expiresInMs != null ? Date.now() + expiresInMs : null;
3101
+ const existing = await this.loadRecord();
3102
+ if (existing) {
3103
+ await this.storeRecord({
3104
+ ...existing,
3105
+ idToken,
3106
+ bearerToken,
3107
+ refreshToken,
3108
+ tokenExpiresAt: this._tokenExpiresAt ?? void 0
3109
+ });
3110
+ }
3035
3111
  }
3036
3112
  async stamp(params) {
3037
- if (!this.keyPair || !this._keyInfo) {
3113
+ if (!this._keyPair || !this._keyInfo || this._idToken === null) {
3038
3114
  throw new Error("Auth2Stamper not initialized. Call init() first.");
3039
3115
  }
3040
3116
  const signatureRaw = await crypto.subtle.sign(
3041
3117
  { name: "ECDSA", hash: "SHA-256" },
3042
- this.keyPair.privateKey,
3118
+ this._keyPair.privateKey,
3043
3119
  new Uint8Array(params.data)
3044
3120
  );
3045
3121
  const rawPublicKey = import_bs582.default.decode(this._keyInfo.publicKey);
3046
- if (this.idToken === void 0 || this.salt === void 0) {
3047
- throw new Error("Auth2Stamper not initialized with idToken or salt.");
3048
- }
3049
3122
  const stampData = {
3050
- kind: "OIDC",
3051
- idToken: this.idToken,
3123
+ kind: this.type,
3124
+ idToken: this._idToken,
3052
3125
  publicKey: (0, import_base64url.base64urlEncode)(rawPublicKey),
3053
- algorithm: "Secp256r1",
3054
- salt: this.salt,
3126
+ algorithm: this.algorithm,
3127
+ // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
3128
+ salt: "",
3055
3129
  signature: (0, import_base64url.base64urlEncode)(new Uint8Array(signatureRaw))
3056
3130
  };
3057
3131
  return (0, import_base64url.base64urlEncode)(new TextEncoder().encode(JSON.stringify(stampData)));
3058
3132
  }
3059
3133
  async resetKeyPair() {
3060
- await this.clearStoredKey();
3061
- this.keyPair = null;
3062
- this._keyInfo = null;
3134
+ await this.clear();
3063
3135
  return this.generateAndStore();
3064
3136
  }
3065
3137
  async clear() {
3066
- await this.clearStoredKey();
3067
- this.keyPair = null;
3138
+ await this.clearStoredRecord();
3139
+ this._keyPair = null;
3068
3140
  this._keyInfo = null;
3141
+ this._idToken = null;
3142
+ this._bearerToken = null;
3143
+ this._refreshToken = null;
3144
+ this._tokenExpiresAt = null;
3069
3145
  }
3070
3146
  // Auth2 doesn't use key rotation; provide minimal no-op implementations.
3071
3147
  async rotateKeyPair() {
@@ -3090,13 +3166,13 @@ var Auth2Stamper = class {
3090
3166
  const publicKeyBase58 = import_bs582.default.encode(rawPublicKey);
3091
3167
  const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKey.buffer);
3092
3168
  const keyId = (0, import_base64url.base64urlEncode)(new Uint8Array(keyIdBuffer)).substring(0, 16);
3093
- this.keyPair = keyPair;
3169
+ this._keyPair = keyPair;
3094
3170
  this._keyInfo = {
3095
3171
  keyId,
3096
3172
  publicKey: publicKeyBase58,
3097
3173
  createdAt: Date.now()
3098
3174
  };
3099
- await this.storeKeyPair(keyPair, this._keyInfo);
3175
+ await this.storeRecord({ keyPair, keyInfo: this._keyInfo });
3100
3176
  return this._keyInfo;
3101
3177
  }
3102
3178
  async openDB() {
@@ -3115,7 +3191,7 @@ var Auth2Stamper = class {
3115
3191
  };
3116
3192
  });
3117
3193
  }
3118
- async loadKeyPair() {
3194
+ async loadRecord() {
3119
3195
  return new Promise((resolve, reject) => {
3120
3196
  if (!this.db) {
3121
3197
  throw new Error("Database not initialized");
@@ -3129,12 +3205,12 @@ var Auth2Stamper = class {
3129
3205
  };
3130
3206
  });
3131
3207
  }
3132
- async storeKeyPair(keyPair, keyInfo) {
3208
+ async storeRecord(record) {
3133
3209
  return new Promise((resolve, reject) => {
3134
3210
  if (!this.db) {
3135
3211
  throw new Error("Database not initialized");
3136
3212
  }
3137
- const request = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).put({ keyPair, keyInfo }, ACTIVE_KEY);
3213
+ const request = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).put(record, ACTIVE_KEY);
3138
3214
  request.onsuccess = () => {
3139
3215
  resolve();
3140
3216
  };
@@ -3143,7 +3219,7 @@ var Auth2Stamper = class {
3143
3219
  };
3144
3220
  });
3145
3221
  }
3146
- async clearStoredKey() {
3222
+ async clearStoredRecord() {
3147
3223
  return new Promise((resolve, reject) => {
3148
3224
  if (!this.db) {
3149
3225
  throw new Error("Database not initialized");
@@ -3262,28 +3338,32 @@ var BrowserPhantomAppProvider = class {
3262
3338
 
3263
3339
  // src/providers/embedded/adapters/logger.ts
3264
3340
  var BrowserLogger = class {
3265
- info(category, message, data) {
3266
- debug.info(category, message, data);
3341
+ info(message, ...args) {
3342
+ debug.info(message, args.length > 0 ? String(args[0]) : "", args[1]);
3267
3343
  }
3268
- warn(category, message, data) {
3269
- debug.warn(category, message, data);
3344
+ warn(message, ...args) {
3345
+ debug.warn(message, args.length > 0 ? String(args[0]) : "", args[1]);
3270
3346
  }
3271
- error(category, message, data) {
3272
- debug.error(category, message, data);
3347
+ error(message, ...args) {
3348
+ debug.error(message, args.length > 0 ? String(args[0]) : "", args[1]);
3273
3349
  }
3274
- log(category, message, data) {
3275
- debug.log(category, message, data);
3350
+ debug(message, ...args) {
3351
+ debug.log(message, args.length > 0 ? String(args[0]) : "", args[1]);
3276
3352
  }
3277
3353
  };
3278
3354
 
3279
3355
  // src/providers/embedded/index.ts
3280
- var import_constants4 = require("@phantom/constants");
3356
+ var import_constants5 = require("@phantom/constants");
3281
3357
  var EmbeddedProvider = class extends import_embedded_provider_core.EmbeddedProvider {
3282
3358
  constructor(config) {
3283
3359
  debug.log(DebugCategory.EMBEDDED_PROVIDER, "Initializing Browser EmbeddedProvider", { config });
3284
3360
  const urlParamsAccessor = new BrowserURLParamsAccessor();
3285
3361
  const storage = new BrowserStorage();
3286
- const stamper = config.unstable__auth2Options ? new Auth2Stamper(`phantom-auth2-${config.appId}`) : new import_indexed_db_stamper.IndexedDbStamper({
3362
+ const stamper = config.unstable__auth2Options ? new Auth2Stamper(`phantom-auth2-${config.appId}`, {
3363
+ authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl,
3364
+ clientId: config.unstable__auth2Options.clientId,
3365
+ redirectUri: config.authOptions?.redirectUrl ?? ""
3366
+ }) : new import_indexed_db_stamper.IndexedDbStamper({
3287
3367
  dbName: `phantom-embedded-sdk-${config.appId}`,
3288
3368
  storeName: "crypto-keys",
3289
3369
  keyName: "signing-key"
@@ -3314,13 +3394,13 @@ var EmbeddedProvider = class extends import_embedded_provider_core.EmbeddedProvi
3314
3394
  name: platformName,
3315
3395
  // Use detected browser name and version for identification
3316
3396
  analyticsHeaders: {
3317
- [import_constants4.ANALYTICS_HEADERS.SDK_TYPE]: "browser",
3318
- [import_constants4.ANALYTICS_HEADERS.PLATFORM]: "ext-sdk",
3319
- [import_constants4.ANALYTICS_HEADERS.PLATFORM_VERSION]: version,
3320
- [import_constants4.ANALYTICS_HEADERS.CLIENT]: browserName,
3321
- [import_constants4.ANALYTICS_HEADERS.APP_ID]: config.appId,
3322
- [import_constants4.ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
3323
- [import_constants4.ANALYTICS_HEADERS.SDK_VERSION]: "1.0.4"
3397
+ [import_constants5.ANALYTICS_HEADERS.SDK_TYPE]: "browser",
3398
+ [import_constants5.ANALYTICS_HEADERS.PLATFORM]: "ext-sdk",
3399
+ [import_constants5.ANALYTICS_HEADERS.PLATFORM_VERSION]: version,
3400
+ [import_constants5.ANALYTICS_HEADERS.CLIENT]: browserName,
3401
+ [import_constants5.ANALYTICS_HEADERS.APP_ID]: config.appId,
3402
+ [import_constants5.ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
3403
+ [import_constants5.ANALYTICS_HEADERS.SDK_VERSION]: "1.0.6"
3324
3404
  // Replaced at build time
3325
3405
  }
3326
3406
  };
@@ -3337,7 +3417,7 @@ var EmbeddedProvider = class extends import_embedded_provider_core.EmbeddedProvi
3337
3417
 
3338
3418
  // src/ProviderManager.ts
3339
3419
  var import_embedded_provider_core2 = require("@phantom/embedded-provider-core");
3340
- var import_constants5 = require("@phantom/constants");
3420
+ var import_constants6 = require("@phantom/constants");
3341
3421
 
3342
3422
  // src/utils/auth-callback.ts
3343
3423
  function isAuthFailureCallback(searchParams) {
@@ -3728,8 +3808,8 @@ var ProviderManager = class {
3728
3808
  if (!this.config.appId) {
3729
3809
  throw new Error("appId is required for embedded provider");
3730
3810
  }
3731
- const apiBaseUrl = this.config.apiBaseUrl || import_constants5.DEFAULT_WALLET_API_URL;
3732
- const authUrl = this.config.authOptions?.authUrl || import_constants5.DEFAULT_AUTH_URL;
3811
+ const apiBaseUrl = this.config.apiBaseUrl || import_constants6.DEFAULT_WALLET_API_URL;
3812
+ const authUrl = this.config.authOptions?.authUrl || import_constants6.DEFAULT_AUTH_URL;
3733
3813
  provider = new EmbeddedProvider({
3734
3814
  apiBaseUrl,
3735
3815
  appId: this.config.appId,
@@ -3739,7 +3819,7 @@ var ProviderManager = class {
3739
3819
  redirectUrl: this.config.authOptions?.redirectUrl || this.getValidatedCurrentUrl()
3740
3820
  },
3741
3821
  unstable__auth2Options: this.config.unstable__auth2Options,
3742
- embeddedWalletType: embeddedWalletType || import_constants5.DEFAULT_EMBEDDED_WALLET_TYPE,
3822
+ embeddedWalletType: embeddedWalletType || import_constants6.DEFAULT_EMBEDDED_WALLET_TYPE,
3743
3823
  addressTypes: this.config.addressTypes || [import_client.AddressType.solana]
3744
3824
  });
3745
3825
  } else {
@@ -3775,7 +3855,7 @@ var ProviderManager = class {
3775
3855
 
3776
3856
  // src/BrowserSDK.ts
3777
3857
  var import_embedded_provider_core3 = require("@phantom/embedded-provider-core");
3778
- var import_constants6 = require("@phantom/constants");
3858
+ var import_constants7 = require("@phantom/constants");
3779
3859
  var BROWSER_SDK_PROVIDER_TYPES = [
3780
3860
  ...import_embedded_provider_core3.EMBEDDED_PROVIDER_AUTH_TYPES,
3781
3861
  "injected",
@@ -3811,7 +3891,7 @@ var BrowserSDK = class {
3811
3891
  });
3812
3892
  throw new Error("appId is required when using embedded providers (google, apple, phantom, etc.)");
3813
3893
  }
3814
- const embeddedWalletType = config.embeddedWalletType || import_constants6.DEFAULT_EMBEDDED_WALLET_TYPE;
3894
+ const embeddedWalletType = config.embeddedWalletType || import_constants7.DEFAULT_EMBEDDED_WALLET_TYPE;
3815
3895
  if (!["app-wallet", "user-wallet"].includes(embeddedWalletType)) {
3816
3896
  debug.error(DebugCategory.BROWSER_SDK, "Invalid embeddedWalletType", {
3817
3897
  embeddedWalletType: config.embeddedWalletType
@@ -4086,6 +4166,6 @@ var BrowserSDK = class {
4086
4166
  };
4087
4167
 
4088
4168
  // src/index.ts
4089
- var import_constants7 = require("@phantom/constants");
4090
- var import_client5 = require("@phantom/client");
4091
4169
  var import_constants8 = require("@phantom/constants");
4170
+ var import_client5 = require("@phantom/client");
4171
+ var import_constants9 = require("@phantom/constants");
package/dist/index.mjs CHANGED
@@ -2714,7 +2714,7 @@ var BrowserAuthProvider = class {
2714
2714
  // OAuth session management - defaults to allow refresh unless explicitly clearing after logout
2715
2715
  clear_previous_session: (phantomOptions.clearPreviousSession ?? false).toString(),
2716
2716
  allow_refresh: (phantomOptions.allowRefresh ?? true).toString(),
2717
- sdk_version: "1.0.4",
2717
+ sdk_version: "1.0.6",
2718
2718
  sdk_type: "browser",
2719
2719
  platform: detectBrowser().name,
2720
2720
  algorithm: phantomOptions.algorithm || DEFAULT_AUTHENTICATOR_ALGORITHM
@@ -2838,7 +2838,6 @@ var BrowserAuthProvider = class {
2838
2838
  // src/providers/embedded/adapters/Auth2AuthProvider.ts
2839
2839
  import {
2840
2840
  createCodeVerifier,
2841
- createSalt,
2842
2841
  createConnectStartUrl,
2843
2842
  exchangeAuthCode,
2844
2843
  Auth2KmsRpcClient
@@ -2860,8 +2859,8 @@ var Auth2AuthProvider = class {
2860
2859
  *
2861
2860
  * Called by EmbeddedProvider.handleRedirectAuth() after the stamper has
2862
2861
  * already been initialized and a pending Session has been saved to storage.
2863
- * We store the PKCE code_verifier and salt into that session so they survive
2864
- * the page redirect without ever touching sessionStorage.
2862
+ * We store the PKCE code_verifier into that session so it survives the page
2863
+ * redirect without ever touching sessionStorage.
2865
2864
  */
2866
2865
  async authenticate(options) {
2867
2866
  if (!this.stamper.getKeyInfo()) {
@@ -2872,12 +2871,11 @@ var Auth2AuthProvider = class {
2872
2871
  throw new Error("Stamper key pair not found.");
2873
2872
  }
2874
2873
  const codeVerifier = createCodeVerifier();
2875
- const salt = createSalt();
2876
2874
  const session = await this.storage.getSession();
2877
2875
  if (!session) {
2878
2876
  throw new Error("Session not found.");
2879
2877
  }
2880
- await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier, salt });
2878
+ await this.storage.saveSession({ ...session, pkceCodeVerifier: codeVerifier });
2881
2879
  const url = await createConnectStartUrl({
2882
2880
  keyPair,
2883
2881
  connectLoginUrl: this.auth2ProviderOptions.connectLoginUrl,
@@ -2886,7 +2884,8 @@ var Auth2AuthProvider = class {
2886
2884
  sessionId: options.sessionId,
2887
2885
  provider: options.provider,
2888
2886
  codeVerifier,
2889
- salt
2887
+ // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
2888
+ salt: ""
2890
2889
  });
2891
2890
  Auth2AuthProvider.navigate(url);
2892
2891
  }
@@ -2921,24 +2920,21 @@ var Auth2AuthProvider = class {
2921
2920
  const description = this.urlParamsAccessor.getParam("error_description");
2922
2921
  throw new Error(`Auth2 callback error: ${description ?? error}`);
2923
2922
  }
2924
- const { idToken, bearerToken, authUserId, expiresInMs } = await exchangeAuthCode({
2923
+ const { idToken, bearerToken, authUserId, expiresInMs, refreshToken } = await exchangeAuthCode({
2925
2924
  authApiBaseUrl: this.auth2ProviderOptions.authApiBaseUrl,
2926
2925
  clientId: this.auth2ProviderOptions.clientId,
2927
2926
  redirectUri: this.auth2ProviderOptions.redirectUri,
2928
2927
  code,
2929
2928
  codeVerifier
2930
2929
  });
2931
- this.stamper.idToken = idToken;
2932
- this.stamper.salt = session?.salt;
2930
+ await this.stamper.setTokens({ idToken, bearerToken, refreshToken, expiresInMs });
2933
2931
  await this.storage.saveSession({
2934
2932
  ...session,
2935
2933
  status: "completed",
2936
2934
  bearerToken,
2937
2935
  authUserId,
2938
- pkceCodeVerifier: void 0,
2936
+ pkceCodeVerifier: void 0
2939
2937
  // no longer needed after code exchange
2940
- salt: void 0
2941
- // no longer needed after nonce binding is complete
2942
2938
  });
2943
2939
  const { organizationId, walletId } = await this.kms.discoverOrganizationAndWalletId(bearerToken, authUserId);
2944
2940
  return {
@@ -2958,27 +2954,48 @@ var Auth2AuthProvider = class {
2958
2954
  import bs582 from "bs58";
2959
2955
  import { base64urlEncode } from "@phantom/base64url";
2960
2956
  import { Algorithm } from "@phantom/sdk-types";
2957
+ import { refreshToken as refreshTokenRequest } from "@phantom/auth2";
2958
+ import { TOKEN_REFRESH_BUFFER_MS } from "@phantom/constants";
2961
2959
  var STORE_NAME = "crypto-keys";
2962
2960
  var ACTIVE_KEY = "auth2-p256-signing-key";
2963
2961
  var Auth2Stamper = class {
2964
2962
  /**
2965
2963
  * @param dbName - IndexedDB database name (use a unique name per app to
2966
2964
  * avoid key collisions with other stampers, e.g. `phantom-auth2-<appId>`).
2965
+ * @param refreshConfig - When provided, the stamper will automatically refresh
2966
+ * the id_token using the refresh_token before it expires.
2967
2967
  */
2968
- constructor(dbName) {
2968
+ constructor(dbName, refreshConfig) {
2969
2969
  this.dbName = dbName;
2970
+ this.refreshConfig = refreshConfig;
2970
2971
  this.db = null;
2971
- this.keyPair = null;
2972
+ this._keyPair = null;
2972
2973
  this._keyInfo = null;
2974
+ this._idToken = null;
2975
+ this._bearerToken = null;
2976
+ this._refreshToken = null;
2977
+ this._tokenExpiresAt = null;
2973
2978
  this.algorithm = Algorithm.secp256r1;
2974
- this.type = "PKI";
2979
+ this.type = "OIDC";
2975
2980
  }
2976
2981
  async init() {
2977
2982
  await this.openDB();
2978
- const stored = await this.loadKeyPair();
2983
+ const stored = await this.loadRecord();
2979
2984
  if (stored) {
2980
- this.keyPair = stored.keyPair;
2985
+ this._keyPair = stored.keyPair;
2981
2986
  this._keyInfo = stored.keyInfo;
2987
+ if (stored.idToken) {
2988
+ this._idToken = stored.idToken;
2989
+ }
2990
+ if (stored.bearerToken) {
2991
+ this._bearerToken = stored.bearerToken;
2992
+ }
2993
+ if (stored.refreshToken) {
2994
+ this._refreshToken = stored.refreshToken;
2995
+ }
2996
+ if (stored.tokenExpiresAt) {
2997
+ this._tokenExpiresAt = stored.tokenExpiresAt;
2998
+ }
2982
2999
  return this._keyInfo;
2983
3000
  }
2984
3001
  return this.generateAndStore();
@@ -2987,41 +3004,99 @@ var Auth2Stamper = class {
2987
3004
  return this._keyInfo;
2988
3005
  }
2989
3006
  getCryptoKeyPair() {
2990
- return this.keyPair;
3007
+ return this._keyPair;
3008
+ }
3009
+ /**
3010
+ * Returns the current token state (refreshing proactively if near expiry),
3011
+ * or null if no token has been set yet.
3012
+ */
3013
+ async getTokens() {
3014
+ if (this.refreshConfig && this._refreshToken && this._tokenExpiresAt !== null && Date.now() >= this._tokenExpiresAt - TOKEN_REFRESH_BUFFER_MS) {
3015
+ const refreshed = await refreshTokenRequest({
3016
+ authApiBaseUrl: this.refreshConfig.authApiBaseUrl,
3017
+ clientId: this.refreshConfig.clientId,
3018
+ redirectUri: this.refreshConfig.redirectUri,
3019
+ refreshToken: this._refreshToken
3020
+ });
3021
+ await this.setTokens(refreshed);
3022
+ }
3023
+ if (!this._idToken || !this._bearerToken) {
3024
+ return null;
3025
+ }
3026
+ return {
3027
+ idToken: this._idToken,
3028
+ bearerToken: this._bearerToken,
3029
+ refreshToken: this._refreshToken ?? void 0
3030
+ };
3031
+ }
3032
+ /**
3033
+ * Arms the stamper with the OIDC token data for subsequent KMS stamp() calls.
3034
+ *
3035
+ * Persists the tokens to IndexedDB alongside the key pair so that
3036
+ * auto-connect can restore them on the next page load without a new login.
3037
+ *
3038
+ * @param refreshToken - When provided alongside a `refreshConfig`, enables
3039
+ * silent token refresh before the token expires.
3040
+ * @param expiresInMs - Token lifetime in milliseconds (from `expires_in * 1000`).
3041
+ * Used to compute the absolute expiry time for proactive refresh.
3042
+ */
3043
+ async setTokens({
3044
+ idToken,
3045
+ bearerToken,
3046
+ refreshToken,
3047
+ expiresInMs
3048
+ }) {
3049
+ if (!this.db) {
3050
+ await this.openDB();
3051
+ }
3052
+ this._idToken = idToken;
3053
+ this._bearerToken = bearerToken;
3054
+ this._refreshToken = refreshToken ?? null;
3055
+ this._tokenExpiresAt = expiresInMs != null ? Date.now() + expiresInMs : null;
3056
+ const existing = await this.loadRecord();
3057
+ if (existing) {
3058
+ await this.storeRecord({
3059
+ ...existing,
3060
+ idToken,
3061
+ bearerToken,
3062
+ refreshToken,
3063
+ tokenExpiresAt: this._tokenExpiresAt ?? void 0
3064
+ });
3065
+ }
2991
3066
  }
2992
3067
  async stamp(params) {
2993
- if (!this.keyPair || !this._keyInfo) {
3068
+ if (!this._keyPair || !this._keyInfo || this._idToken === null) {
2994
3069
  throw new Error("Auth2Stamper not initialized. Call init() first.");
2995
3070
  }
2996
3071
  const signatureRaw = await crypto.subtle.sign(
2997
3072
  { name: "ECDSA", hash: "SHA-256" },
2998
- this.keyPair.privateKey,
3073
+ this._keyPair.privateKey,
2999
3074
  new Uint8Array(params.data)
3000
3075
  );
3001
3076
  const rawPublicKey = bs582.decode(this._keyInfo.publicKey);
3002
- if (this.idToken === void 0 || this.salt === void 0) {
3003
- throw new Error("Auth2Stamper not initialized with idToken or salt.");
3004
- }
3005
3077
  const stampData = {
3006
- kind: "OIDC",
3007
- idToken: this.idToken,
3078
+ kind: this.type,
3079
+ idToken: this._idToken,
3008
3080
  publicKey: base64urlEncode(rawPublicKey),
3009
- algorithm: "Secp256r1",
3010
- salt: this.salt,
3081
+ algorithm: this.algorithm,
3082
+ // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
3083
+ salt: "",
3011
3084
  signature: base64urlEncode(new Uint8Array(signatureRaw))
3012
3085
  };
3013
3086
  return base64urlEncode(new TextEncoder().encode(JSON.stringify(stampData)));
3014
3087
  }
3015
3088
  async resetKeyPair() {
3016
- await this.clearStoredKey();
3017
- this.keyPair = null;
3018
- this._keyInfo = null;
3089
+ await this.clear();
3019
3090
  return this.generateAndStore();
3020
3091
  }
3021
3092
  async clear() {
3022
- await this.clearStoredKey();
3023
- this.keyPair = null;
3093
+ await this.clearStoredRecord();
3094
+ this._keyPair = null;
3024
3095
  this._keyInfo = null;
3096
+ this._idToken = null;
3097
+ this._bearerToken = null;
3098
+ this._refreshToken = null;
3099
+ this._tokenExpiresAt = null;
3025
3100
  }
3026
3101
  // Auth2 doesn't use key rotation; provide minimal no-op implementations.
3027
3102
  async rotateKeyPair() {
@@ -3046,13 +3121,13 @@ var Auth2Stamper = class {
3046
3121
  const publicKeyBase58 = bs582.encode(rawPublicKey);
3047
3122
  const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKey.buffer);
3048
3123
  const keyId = base64urlEncode(new Uint8Array(keyIdBuffer)).substring(0, 16);
3049
- this.keyPair = keyPair;
3124
+ this._keyPair = keyPair;
3050
3125
  this._keyInfo = {
3051
3126
  keyId,
3052
3127
  publicKey: publicKeyBase58,
3053
3128
  createdAt: Date.now()
3054
3129
  };
3055
- await this.storeKeyPair(keyPair, this._keyInfo);
3130
+ await this.storeRecord({ keyPair, keyInfo: this._keyInfo });
3056
3131
  return this._keyInfo;
3057
3132
  }
3058
3133
  async openDB() {
@@ -3071,7 +3146,7 @@ var Auth2Stamper = class {
3071
3146
  };
3072
3147
  });
3073
3148
  }
3074
- async loadKeyPair() {
3149
+ async loadRecord() {
3075
3150
  return new Promise((resolve, reject) => {
3076
3151
  if (!this.db) {
3077
3152
  throw new Error("Database not initialized");
@@ -3085,12 +3160,12 @@ var Auth2Stamper = class {
3085
3160
  };
3086
3161
  });
3087
3162
  }
3088
- async storeKeyPair(keyPair, keyInfo) {
3163
+ async storeRecord(record) {
3089
3164
  return new Promise((resolve, reject) => {
3090
3165
  if (!this.db) {
3091
3166
  throw new Error("Database not initialized");
3092
3167
  }
3093
- const request = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).put({ keyPair, keyInfo }, ACTIVE_KEY);
3168
+ const request = this.db.transaction([STORE_NAME], "readwrite").objectStore(STORE_NAME).put(record, ACTIVE_KEY);
3094
3169
  request.onsuccess = () => {
3095
3170
  resolve();
3096
3171
  };
@@ -3099,7 +3174,7 @@ var Auth2Stamper = class {
3099
3174
  };
3100
3175
  });
3101
3176
  }
3102
- async clearStoredKey() {
3177
+ async clearStoredRecord() {
3103
3178
  return new Promise((resolve, reject) => {
3104
3179
  if (!this.db) {
3105
3180
  throw new Error("Database not initialized");
@@ -3218,17 +3293,17 @@ var BrowserPhantomAppProvider = class {
3218
3293
 
3219
3294
  // src/providers/embedded/adapters/logger.ts
3220
3295
  var BrowserLogger = class {
3221
- info(category, message, data) {
3222
- debug.info(category, message, data);
3296
+ info(message, ...args) {
3297
+ debug.info(message, args.length > 0 ? String(args[0]) : "", args[1]);
3223
3298
  }
3224
- warn(category, message, data) {
3225
- debug.warn(category, message, data);
3299
+ warn(message, ...args) {
3300
+ debug.warn(message, args.length > 0 ? String(args[0]) : "", args[1]);
3226
3301
  }
3227
- error(category, message, data) {
3228
- debug.error(category, message, data);
3302
+ error(message, ...args) {
3303
+ debug.error(message, args.length > 0 ? String(args[0]) : "", args[1]);
3229
3304
  }
3230
- log(category, message, data) {
3231
- debug.log(category, message, data);
3305
+ debug(message, ...args) {
3306
+ debug.log(message, args.length > 0 ? String(args[0]) : "", args[1]);
3232
3307
  }
3233
3308
  };
3234
3309
 
@@ -3239,7 +3314,11 @@ var EmbeddedProvider = class extends CoreEmbeddedProvider {
3239
3314
  debug.log(DebugCategory.EMBEDDED_PROVIDER, "Initializing Browser EmbeddedProvider", { config });
3240
3315
  const urlParamsAccessor = new BrowserURLParamsAccessor();
3241
3316
  const storage = new BrowserStorage();
3242
- const stamper = config.unstable__auth2Options ? new Auth2Stamper(`phantom-auth2-${config.appId}`) : new IndexedDbStamper({
3317
+ const stamper = config.unstable__auth2Options ? new Auth2Stamper(`phantom-auth2-${config.appId}`, {
3318
+ authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl,
3319
+ clientId: config.unstable__auth2Options.clientId,
3320
+ redirectUri: config.authOptions?.redirectUrl ?? ""
3321
+ }) : new IndexedDbStamper({
3243
3322
  dbName: `phantom-embedded-sdk-${config.appId}`,
3244
3323
  storeName: "crypto-keys",
3245
3324
  keyName: "signing-key"
@@ -3276,7 +3355,7 @@ var EmbeddedProvider = class extends CoreEmbeddedProvider {
3276
3355
  [ANALYTICS_HEADERS.CLIENT]: browserName,
3277
3356
  [ANALYTICS_HEADERS.APP_ID]: config.appId,
3278
3357
  [ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
3279
- [ANALYTICS_HEADERS.SDK_VERSION]: "1.0.4"
3358
+ [ANALYTICS_HEADERS.SDK_VERSION]: "1.0.6"
3280
3359
  // Replaced at build time
3281
3360
  }
3282
3361
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phantom/browser-sdk",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Browser SDK for Phantom Wallet",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,16 +33,16 @@
33
33
  "prettier": "prettier --write \"src/**/*.{ts,tsx}\""
34
34
  },
35
35
  "dependencies": {
36
- "@phantom/auth2": "^1.0.0",
37
- "@phantom/base64url": "^1.0.4",
38
- "@phantom/browser-injected-sdk": "^1.0.4",
39
- "@phantom/chain-interfaces": "^1.0.4",
40
- "@phantom/client": "^1.0.4",
41
- "@phantom/constants": "^1.0.4",
42
- "@phantom/embedded-provider-core": "^1.0.4",
43
- "@phantom/indexed-db-stamper": "^1.0.4",
44
- "@phantom/parsers": "^1.0.4",
45
- "@phantom/sdk-types": "^1.0.4",
36
+ "@phantom/auth2": "^1.0.2",
37
+ "@phantom/base64url": "^1.0.6",
38
+ "@phantom/browser-injected-sdk": "^1.0.6",
39
+ "@phantom/chain-interfaces": "^1.0.6",
40
+ "@phantom/client": "^1.0.6",
41
+ "@phantom/constants": "^1.0.6",
42
+ "@phantom/embedded-provider-core": "^1.0.6",
43
+ "@phantom/indexed-db-stamper": "^1.0.6",
44
+ "@phantom/parsers": "^1.0.6",
45
+ "@phantom/sdk-types": "^1.0.6",
46
46
  "axios": "^1.10.0",
47
47
  "bs58": "^6.0.0",
48
48
  "buffer": "^6.0.3",