@pooflabs/core 0.0.39 → 0.0.41

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.
@@ -3,7 +3,8 @@ export interface ClientConfig {
3
3
  name: string;
4
4
  logoUrl: string;
5
5
  apiKey: string;
6
- authMethod: 'none' | 'privy' | 'wallet' | 'rainbowkit' | 'coinbase-smart-wallet' | 'onboard' | 'phantom' | 'mobile-wallet-adapter';
6
+ /** Auth method. 'privy-expo' is client-only mapped to 'privy' before sending to the backend. */
7
+ authMethod: 'none' | 'privy' | 'privy-expo' | 'wallet' | 'rainbowkit' | 'coinbase-smart-wallet' | 'onboard' | 'phantom' | 'mobile-wallet-adapter';
7
8
  wsApiUrl: string;
8
9
  apiUrl: string;
9
10
  appId: string;
@@ -36,6 +37,8 @@ export interface ClientConfig {
36
37
  cluster?: string;
37
38
  theme?: 'light' | 'dark';
38
39
  };
40
+ /** Pre-created PrivyExpoProvider instance for React Native (required when authMethod is 'privy-expo'). */
41
+ privyExpoProvider?: AuthProvider;
39
42
  mockAuth?: boolean;
40
43
  }
41
44
  export declare let clientConfig: ClientConfig;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ export { subscribe, closeAllSubscriptions, clearCache, getCachedData, reconnectW
5
5
  export * from './types';
6
6
  export { getIdToken } from './utils/utils';
7
7
  export { WebSessionManager } from './utils/web-session-manager';
8
+ export { ReactNativeSessionManager } from './utils/rn-session-manager';
9
+ export type { RNStorageAdapter } from './utils/rn-session-manager';
8
10
  export { ServerSessionManager } from './utils/server-session-manager';
9
11
  export { createSessionWithPrivy, createSessionWithSignature, refreshSession, signSessionCreateMessage, genAuthNonce } from './utils/auth-api';
10
12
  export { Tarobase as Tarobase6 } from './utils/sol/taro6CvKqwrYrDc16ufYgzQ2NZcyyVKStffbtudrhRuDevnet-program';
package/dist/index.js CHANGED
@@ -62,7 +62,12 @@ async function createSessionWithSignature(address, message, signature) {
62
62
  return response.data;
63
63
  }
64
64
  async function createSessionWithPrivy(authToken, address, privyIdToken) {
65
- if (typeof window === 'undefined')
65
+ var _a;
66
+ // Block in true SSR/Node.js. Allow in browser (window exists) and React Native (no window,
67
+ // but not Node — detected via process.versions.node). Node 21+ defines navigator globally,
68
+ // so we can't rely on navigator alone.
69
+ const isNode = typeof process !== 'undefined' && !!((_a = process.versions) === null || _a === void 0 ? void 0 : _a.node);
70
+ if (typeof window === 'undefined' && isNode)
66
71
  return;
67
72
  const client = await getAxiosAuthClient();
68
73
  const config = await getConfig();
@@ -90,6 +95,19 @@ async function signSessionCreateMessage(_signMessageFunction) {
90
95
  }
91
96
 
92
97
  class WebSessionManager {
98
+ /**
99
+ * Decode a base64url-encoded string (used by JWT payloads).
100
+ * Normalises base64url → standard base64 before calling browser atob().
101
+ */
102
+ static decodeBase64Url(input) {
103
+ let b64 = input.replace(/-/g, '+').replace(/_/g, '/');
104
+ const pad = b64.length % 4;
105
+ if (pad === 2)
106
+ b64 += '==';
107
+ else if (pad === 3)
108
+ b64 += '=';
109
+ return atob(b64);
110
+ }
93
111
  /* ------------------------------------------------------------------ */
94
112
  /* STORE */
95
113
  /* ------------------------------------------------------------------ */
@@ -125,7 +143,7 @@ class WebSessionManager {
125
143
  /* ---------- check JWT expiration ---------- */
126
144
  try {
127
145
  const { accessToken } = sessionObj;
128
- const { exp } = JSON.parse(atob(accessToken.split(".")[1]));
146
+ const { exp } = JSON.parse(this.decodeBase64Url(accessToken.split(".")[1]));
129
147
  if (Date.now() > exp * 1000) {
130
148
  const { refreshToken } = sessionObj;
131
149
  if (!refreshToken)
@@ -2959,7 +2977,7 @@ function convertRemainingAccounts(remainingAccounts) {
2959
2977
  // ─────────────────────────────────────────────────────────────
2960
2978
  // Updated Transaction Builder: It now accepts a PublicKey for the payer
2961
2979
  // ─────────────────────────────────────────────────────────────
2962
- async function buildSetDocumentsTransaction(connection, idl, anchorProvider, payerPublicKey, args, remainingAccounts, lutKey, preInstructions, simulate) {
2980
+ async function buildSetDocumentsTransaction(connection, idl, anchorProvider, payerPublicKey, args, remainingAccounts, lutKey, preInstructions, simulate, additionalLutAddresses) {
2963
2981
  const computeBudgetIx = web3_js.ComputeBudgetProgram.setComputeUnitLimit({
2964
2982
  units: 1400000,
2965
2983
  });
@@ -3000,8 +3018,25 @@ async function buildSetDocumentsTransaction(connection, idl, anchorProvider, pay
3000
3018
  tx.feePayer = payerPublicKey;
3001
3019
  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
3002
3020
  tx.recentBlockhash = blockhash;
3003
- if (lutKey == null) {
3004
- const isSurfnet = anchorProvider.connection.rpcEndpoint == "https://surfpool.fly.dev";
3021
+ // Resolve LUTs: additionalLutAddresses (when present) is the authoritative set.
3022
+ // Falls back to lutKey alone for backwards compatibility.
3023
+ const lookupTables = [];
3024
+ const isSurfnet = anchorProvider.connection.rpcEndpoint == "https://surfpool.fly.dev";
3025
+ if (additionalLutAddresses && additionalLutAddresses.length > 0) {
3026
+ const results = await Promise.all(additionalLutAddresses.map(addr => connection.getAddressLookupTable(new web3_js.PublicKey(addr)).catch(() => null)));
3027
+ for (const result of results) {
3028
+ if (result === null || result === void 0 ? void 0 : result.value) {
3029
+ lookupTables.push(result.value);
3030
+ }
3031
+ }
3032
+ }
3033
+ else if (lutKey != null) {
3034
+ const { value: table } = await connection.getAddressLookupTable(new web3_js.PublicKey(lutKey));
3035
+ if (!table)
3036
+ throw new Error('LUT not found after creation/extend');
3037
+ lookupTables.push(table);
3038
+ }
3039
+ if (lookupTables.length === 0) {
3005
3040
  const computeUnits = isSurfnet ? 1400000 : await getSimulationComputeUnits(connection, tx.instructions, payerPublicKey, []);
3006
3041
  const computeBudgetIxOptimized = web3_js.ComputeBudgetProgram.setComputeUnitLimit({
3007
3042
  units: computeUnits ? computeUnits * 1.2 : 1400000,
@@ -3009,11 +3044,7 @@ async function buildSetDocumentsTransaction(connection, idl, anchorProvider, pay
3009
3044
  tx.instructions[0] = computeBudgetIxOptimized;
3010
3045
  return { tx, blockhash, lastValidBlockHeight };
3011
3046
  }
3012
- const { value: table } = await connection.getAddressLookupTable(new web3_js.PublicKey(lutKey));
3013
- if (!table)
3014
- throw new Error('LUT not found after creation/extend');
3015
- const isSurfnet = anchorProvider.connection.rpcEndpoint == "https://surfpool.fly.dev";
3016
- const computeUnits = isSurfnet ? 1400000 : await getSimulationComputeUnits(connection, tx.instructions, payerPublicKey, [table]);
3047
+ const computeUnits = isSurfnet ? 1400000 : await getSimulationComputeUnits(connection, tx.instructions, payerPublicKey, lookupTables);
3017
3048
  const computeBudgetIxOptimized = web3_js.ComputeBudgetProgram.setComputeUnitLimit({
3018
3049
  units: computeUnits ? computeUnits * 1.2 : 1400000,
3019
3050
  }); // 20% buffer
@@ -3022,7 +3053,7 @@ async function buildSetDocumentsTransaction(connection, idl, anchorProvider, pay
3022
3053
  payerKey: payerPublicKey,
3023
3054
  recentBlockhash: blockhash,
3024
3055
  instructions: tx.instructions,
3025
- }).compileToV0Message([table]);
3056
+ }).compileToV0Message(lookupTables);
3026
3057
  const vTx = new anchor__namespace.web3.VersionedTransaction(msgV0);
3027
3058
  return { tx: vTx, blockhash, lastValidBlockHeight };
3028
3059
  }
@@ -3139,6 +3170,41 @@ var serverSessionManager = /*#__PURE__*/Object.freeze({
3139
3170
  ServerSessionManager: ServerSessionManager
3140
3171
  });
3141
3172
 
3173
+ /**
3174
+ * Safe base64 helpers for tarobase-core.
3175
+ *
3176
+ * Uses the global atob/btoa when available (browser, RN with polyfill),
3177
+ * falls back to the 'buffer' package (Node.js / SSR / RN without polyfill).
3178
+ *
3179
+ * These are used instead of bare `atob()`/`btoa()` so the core package
3180
+ * works in React Native without requiring the consumer to polyfill globals.
3181
+ */
3182
+ function safeAtob(input) {
3183
+ if (typeof globalThis.atob === 'function') {
3184
+ return globalThis.atob(input);
3185
+ }
3186
+ return bufferExports.Buffer.from(input, 'base64').toString('binary');
3187
+ }
3188
+ function safeBtoa(input) {
3189
+ if (typeof globalThis.btoa === 'function') {
3190
+ return globalThis.btoa(input);
3191
+ }
3192
+ return bufferExports.Buffer.from(input, 'binary').toString('base64');
3193
+ }
3194
+ /**
3195
+ * Decode a base64url-encoded string (used by JWT payloads).
3196
+ * Normalises base64url → standard base64 before decoding.
3197
+ */
3198
+ function decodeBase64Url(input) {
3199
+ let b64 = input.replace(/-/g, '+').replace(/_/g, '/');
3200
+ const pad = b64.length % 4;
3201
+ if (pad === 2)
3202
+ b64 += '==';
3203
+ else if (pad === 3)
3204
+ b64 += '=';
3205
+ return safeAtob(b64);
3206
+ }
3207
+
3142
3208
  async function createBearerToken(isServer) {
3143
3209
  if (isServer) {
3144
3210
  const sessionMgr = ServerSessionManager.instance;
@@ -3638,7 +3704,7 @@ async function get(path, opts = {}) {
3638
3704
  }
3639
3705
  else {
3640
3706
  const path = encodeURIComponent(normalizedPath);
3641
- const promptQueryParam = (opts === null || opts === void 0 ? void 0 : opts.prompt) ? `&prompt=${btoa(opts.prompt)}` : "";
3707
+ const promptQueryParam = (opts === null || opts === void 0 ? void 0 : opts.prompt) ? `&prompt=${safeBtoa(opts.prompt)}` : "";
3642
3708
  const apiPath = `items?path=${path}${promptQueryParam}${includeSubPathsParam}${shapeParam}${limitParam}${cursorParam}`;
3643
3709
  response = await makeApiRequest('GET', apiPath, null, opts._overrides);
3644
3710
  }
@@ -3871,6 +3937,7 @@ async function setMany(many, options) {
3871
3937
  appId: config.appId,
3872
3938
  txArgs: [solTransactionData],
3873
3939
  lutKey: (_c = tx.lutAddress) !== null && _c !== void 0 ? _c : null,
3940
+ additionalLutAddresses: tx.additionalLutAddresses,
3874
3941
  network: tx.network,
3875
3942
  preInstructions: (_e = (_d = tx.preInstructions) === null || _d === void 0 ? void 0 : _d.map((ix) => {
3876
3943
  var _a;
@@ -4093,7 +4160,7 @@ function getCacheKey(path, prompt, shape, limit, cursor) {
4093
4160
  }
4094
4161
  function isTokenExpired(token) {
4095
4162
  try {
4096
- const payload = JSON.parse(atob(token.split('.')[1]));
4163
+ const payload = JSON.parse(decodeBase64Url(token.split('.')[1]));
4097
4164
  const expirationTime = payload.exp * 1000;
4098
4165
  const currentTime = Date.now();
4099
4166
  return currentTime > (expirationTime - 60000); // 60 second buffer
@@ -4105,7 +4172,7 @@ function isTokenExpired(token) {
4105
4172
  }
4106
4173
  function getTokenExpirationTime(token) {
4107
4174
  try {
4108
- const payload = JSON.parse(atob(token.split('.')[1]));
4175
+ const payload = JSON.parse(decodeBase64Url(token.split('.')[1]));
4109
4176
  return payload.exp ? payload.exp * 1000 : null;
4110
4177
  }
4111
4178
  catch (_a) {
@@ -4433,7 +4500,7 @@ function sendSubscribe(connection, subscription) {
4433
4500
  type: 'subscribe',
4434
4501
  subscriptionId: subscription.subscriptionId,
4435
4502
  path: subscription.path,
4436
- prompt: subscription.prompt ? btoa(subscription.prompt) : undefined,
4503
+ prompt: subscription.prompt ? safeBtoa(subscription.prompt) : undefined,
4437
4504
  includeSubPaths: subscription.includeSubPaths,
4438
4505
  shape: subscription.shape && Object.keys(subscription.shape).length > 0
4439
4506
  ? subscription.shape
@@ -4759,7 +4826,170 @@ async function reconnectWithNewAuth() {
4759
4826
  return reconnectWithNewAuthV2();
4760
4827
  }
4761
4828
 
4829
+ let _config = null;
4830
+ class ReactNativeSessionManager {
4831
+ /**
4832
+ * Must be called once before any other method.
4833
+ *
4834
+ * ```ts
4835
+ * import { ReactNativeSessionManager } from '@pooflabs/core';
4836
+ * import { MMKV } from 'react-native-mmkv';
4837
+ * import { decode } from 'base-64';
4838
+ *
4839
+ * const mmkv = new MMKV();
4840
+ * ReactNativeSessionManager.configure({
4841
+ * storage: {
4842
+ * getItem: (k) => mmkv.getString(k) ?? null,
4843
+ * setItem: (k, v) => mmkv.set(k, v),
4844
+ * removeItem: (k) => mmkv.delete(k),
4845
+ * },
4846
+ * atob: decode,
4847
+ * });
4848
+ * ```
4849
+ */
4850
+ static configure(cfg) {
4851
+ _config = cfg;
4852
+ }
4853
+ static getStorage() {
4854
+ if (!_config) {
4855
+ throw new Error("ReactNativeSessionManager.configure() must be called before using session methods.");
4856
+ }
4857
+ return _config.storage;
4858
+ }
4859
+ static decodeBase64(input) {
4860
+ if (!_config) {
4861
+ throw new Error("ReactNativeSessionManager.configure() must be called before using session methods.");
4862
+ }
4863
+ return _config.atob(input);
4864
+ }
4865
+ /**
4866
+ * Decode a base64url-encoded string (used by JWT payloads).
4867
+ * Normalises base64url → standard base64 before decoding.
4868
+ */
4869
+ static decodeBase64Url(input) {
4870
+ let b64 = input.replace(/-/g, '+').replace(/_/g, '/');
4871
+ const pad = b64.length % 4;
4872
+ if (pad === 2)
4873
+ b64 += '==';
4874
+ else if (pad === 3)
4875
+ b64 += '=';
4876
+ return this.decodeBase64(b64);
4877
+ }
4878
+ /* ------------------------------------------------------------------ */
4879
+ /* STORE */
4880
+ /* ------------------------------------------------------------------ */
4881
+ static async storeSession(address, accessToken, idToken, refreshToken) {
4882
+ const config = await getConfig();
4883
+ const currentAppId = config.appId;
4884
+ this.getStorage().setItem(this.TAROBASE_SESSION_STORAGE_KEY, JSON.stringify({
4885
+ address,
4886
+ accessToken,
4887
+ idToken,
4888
+ refreshToken,
4889
+ appId: currentAppId,
4890
+ }));
4891
+ }
4892
+ /* ------------------------------------------------------------------ */
4893
+ /* GET */
4894
+ /* ------------------------------------------------------------------ */
4895
+ static async getSession() {
4896
+ const session = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
4897
+ if (!session)
4898
+ return null;
4899
+ const sessionObj = JSON.parse(session);
4900
+ /* ---------- validate app-id ---------- */
4901
+ const config = await getConfig();
4902
+ if (sessionObj.appId && sessionObj.appId !== config.appId) {
4903
+ this.clearSession();
4904
+ return null;
4905
+ }
4906
+ /* ---------- check JWT expiration ---------- */
4907
+ try {
4908
+ const { accessToken } = sessionObj;
4909
+ const { exp } = JSON.parse(this.decodeBase64Url(accessToken.split(".")[1]));
4910
+ if (Date.now() > exp * 1000) {
4911
+ const { refreshToken } = sessionObj;
4912
+ if (!refreshToken)
4913
+ return null;
4914
+ const refreshed = await refreshSession(refreshToken);
4915
+ if ((refreshed === null || refreshed === void 0 ? void 0 : refreshed.idToken) && (refreshed === null || refreshed === void 0 ? void 0 : refreshed.accessToken)) {
4916
+ await this.updateIdTokenAndAccessToken(refreshed.idToken, refreshed.accessToken);
4917
+ const newSession = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
4918
+ if (!newSession)
4919
+ return null;
4920
+ const newObj = JSON.parse(newSession);
4921
+ return { address: newObj.address, session: newObj };
4922
+ }
4923
+ // Refresh failed — clear stale session to prevent retry loops
4924
+ this.clearSession();
4925
+ return null;
4926
+ }
4927
+ }
4928
+ catch (err) {
4929
+ // Token decode or refresh failed — clear stale session to prevent retry loops
4930
+ this.clearSession();
4931
+ return null;
4932
+ }
4933
+ return { address: sessionObj.address, session: sessionObj };
4934
+ }
4935
+ /* ------------------------------------------------------------------ */
4936
+ /* CLEAR */
4937
+ /* ------------------------------------------------------------------ */
4938
+ static clearSession() {
4939
+ this.getStorage().removeItem(this.TAROBASE_SESSION_STORAGE_KEY);
4940
+ }
4941
+ /* ------------------------------------------------------------------ */
4942
+ /* IS-AUTH */
4943
+ /* ------------------------------------------------------------------ */
4944
+ static isAuthenticated() {
4945
+ return !!this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
4946
+ }
4947
+ /* ------------------------------------------------------------------ */
4948
+ /* TOKEN HELPERS */
4949
+ /* ------------------------------------------------------------------ */
4950
+ static getIdToken() {
4951
+ try {
4952
+ const session = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
4953
+ return session ? JSON.parse(session).idToken : null;
4954
+ }
4955
+ catch (_a) {
4956
+ return null;
4957
+ }
4958
+ }
4959
+ static getRefreshToken() {
4960
+ try {
4961
+ const session = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
4962
+ return session ? JSON.parse(session).refreshToken : null;
4963
+ }
4964
+ catch (_a) {
4965
+ return null;
4966
+ }
4967
+ }
4968
+ /* ------------------------------------------------------------------ */
4969
+ /* UPDATE TOKENS */
4970
+ /* ------------------------------------------------------------------ */
4971
+ static async updateIdTokenAndAccessToken(idToken, accessToken) {
4972
+ var _a;
4973
+ const session = this.getStorage().getItem(this.TAROBASE_SESSION_STORAGE_KEY);
4974
+ if (!session)
4975
+ return;
4976
+ const sessionObj = JSON.parse(session);
4977
+ /* ---------- app-id guard ---------- */
4978
+ const config = await getConfig();
4979
+ if (sessionObj.appId && sessionObj.appId !== config.appId) {
4980
+ this.clearSession();
4981
+ return;
4982
+ }
4983
+ sessionObj.idToken = idToken;
4984
+ sessionObj.accessToken = accessToken;
4985
+ (_a = sessionObj.appId) !== null && _a !== void 0 ? _a : (sessionObj.appId = config.appId);
4986
+ this.getStorage().setItem(this.TAROBASE_SESSION_STORAGE_KEY, JSON.stringify(sessionObj));
4987
+ }
4988
+ }
4989
+ ReactNativeSessionManager.TAROBASE_SESSION_STORAGE_KEY = "tarobase_session_storage";
4990
+
4762
4991
  exports.InsufficientBalanceError = InsufficientBalanceError;
4992
+ exports.ReactNativeSessionManager = ReactNativeSessionManager;
4763
4993
  exports.ServerSessionManager = ServerSessionManager;
4764
4994
  exports.WebSessionManager = WebSessionManager;
4765
4995
  exports.aggregate = aggregate;