@nhost/nhost-js 4.5.0 → 4.7.0
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/fetch-DjLYNzv8.cjs +2 -0
- package/dist/fetch-DjLYNzv8.cjs.map +1 -0
- package/dist/fetch-DsJJyobH.js +2 -0
- package/dist/fetch-DsJJyobH.js.map +1 -0
- package/dist/graphql-BEFThamT.cjs +2 -0
- package/dist/graphql-BEFThamT.cjs.map +1 -0
- package/dist/graphql-CTZ9QikM.js +2 -0
- package/dist/graphql-CTZ9QikM.js.map +1 -0
- package/dist/nhost-js/auth.cjs +2 -2
- package/dist/nhost-js/auth.cjs.map +1 -1
- package/dist/nhost-js/auth.js +2 -1279
- package/dist/nhost-js/auth.js.map +1 -1
- package/dist/nhost-js/fetch.cjs +1 -2
- package/dist/nhost-js/fetch.js +1 -30
- package/dist/nhost-js/functions.cjs +2 -2
- package/dist/nhost-js/functions.cjs.map +1 -1
- package/dist/nhost-js/functions.js +2 -50
- package/dist/nhost-js/functions.js.map +1 -1
- package/dist/nhost-js/graphql.cjs +1 -2
- package/dist/nhost-js/graphql.js +1 -53
- package/dist/nhost-js/session.cjs +1 -2
- package/dist/nhost-js/session.js +1 -242
- package/dist/nhost-js/storage.cjs +2 -2
- package/dist/nhost-js/storage.cjs.map +1 -1
- package/dist/nhost-js/storage.js +2 -342
- package/dist/nhost-js/storage.js.map +1 -1
- package/dist/nhost-js.cjs +2 -2
- package/dist/nhost-js.cjs.map +1 -1
- package/dist/nhost-js.js +2 -241
- package/dist/nhost-js.js.map +1 -1
- package/dist/nhost-js.umd.js +2 -2
- package/dist/nhost-js.umd.js.map +1 -1
- package/dist/refreshSession-BvZuC-Or.js +2 -0
- package/dist/refreshSession-BvZuC-Or.js.map +1 -0
- package/dist/refreshSession-ZW-JniXX.cjs +2 -0
- package/dist/refreshSession-ZW-JniXX.cjs.map +1 -0
- package/dist/session-oECsmiRB.js +2 -0
- package/dist/session-oECsmiRB.js.map +1 -0
- package/dist/session-r_9IPgQp.cjs +2 -0
- package/dist/session-r_9IPgQp.cjs.map +1 -0
- package/dist/src/auth/client.d.ts +352 -13
- package/dist/src/auth/client.d.ts.map +1 -1
- package/dist/src/auth/client.js +167 -0
- package/dist/src/auth/client.js.map +1 -1
- package/dist/src/auth/index.d.ts +1 -0
- package/dist/src/auth/index.d.ts.map +1 -1
- package/dist/src/auth/index.js +1 -0
- package/dist/src/auth/index.js.map +1 -1
- package/dist/src/auth/pkce.d.ts +22 -0
- package/dist/src/auth/pkce.d.ts.map +1 -0
- package/dist/src/auth/pkce.js +40 -0
- package/dist/src/auth/pkce.js.map +1 -0
- package/dist/src/fetch/middlewareUpdateSessionFromResponse.d.ts.map +1 -1
- package/dist/src/fetch/middlewareUpdateSessionFromResponse.js +1 -0
- package/dist/src/fetch/middlewareUpdateSessionFromResponse.js.map +1 -1
- package/dist/src/graphql/client.d.ts +11 -0
- package/dist/src/graphql/client.d.ts.map +1 -1
- package/dist/src/graphql/client.js +31 -1
- package/dist/src/graphql/client.js.map +1 -1
- package/dist/src/graphql/index.d.ts +2 -1
- package/dist/src/graphql/index.d.ts.map +1 -1
- package/dist/src/graphql/index.js +1 -1
- package/dist/src/graphql/index.js.map +1 -1
- package/dist/src/storage/client.d.ts +4 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/dist/fetch-2PHmQBIJ.js +0 -61
- package/dist/fetch-2PHmQBIJ.js.map +0 -1
- package/dist/fetch-DWXwEnoe.cjs +0 -2
- package/dist/fetch-DWXwEnoe.cjs.map +0 -1
- package/dist/middlewareWithAdminSession-BeIk-9HO.js +0 -106
- package/dist/middlewareWithAdminSession-BeIk-9HO.js.map +0 -1
- package/dist/middlewareWithAdminSession-DDApoLkL.cjs +0 -2
- package/dist/middlewareWithAdminSession-DDApoLkL.cjs.map +0 -1
- package/dist/nhost-js/fetch.cjs.map +0 -1
- package/dist/nhost-js/fetch.js.map +0 -1
- package/dist/nhost-js/graphql.cjs.map +0 -1
- package/dist/nhost-js/graphql.js.map +0 -1
- package/dist/nhost-js/session.cjs.map +0 -1
- package/dist/nhost-js/session.js.map +0 -1
- package/dist/refreshSession-Bw715ZmX.cjs +0 -2
- package/dist/refreshSession-Bw715ZmX.cjs.map +0 -1
- package/dist/refreshSession-WwGlzgtM.js +0 -95
- package/dist/refreshSession-WwGlzgtM.js.map +0 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e="undefined"!=typeof navigator&&navigator.locks?navigator.locks:new class{async request(e,s,n){return n()}},s=async(e,s,r=60)=>{try{return await n(e,s,r)}catch(o){try{return console.warn("error refreshing session, retrying:",o),await n(e,s,r)}catch(o){return 401===o?.status&&(console.error("session probably expired"),s.remove()),null}}},n=async(s,n,o=60)=>{const{session:t,needsRefresh:i}=await e.request("nhostSessionLock",{mode:"shared"},(async()=>r(n,o)));return t?i?await e.request("nhostSessionLock",{mode:"exclusive"},(async()=>{const{session:e,needsRefresh:t,sessionExpired:i}=r(n,o);if(!e)return null;if(!t)return e;try{const r=await s.refreshToken({refreshToken:e.refreshToken});return n.set(r.body),r.body}catch(d){if(!i)return e;throw d}})):t:null},r=(e,s=60)=>{const n=e.get();if(!n)return{session:null,needsRefresh:!1,sessionExpired:!1};if(!n.decodedToken||!n.decodedToken.exp)return{session:n,needsRefresh:!0,sessionExpired:!0};if(0===s)return{session:n,needsRefresh:!0,sessionExpired:!1};const r=Date.now();return n.decodedToken.exp-r>1e3*s?{session:n,needsRefresh:!1,sessionExpired:!1}:{session:n,needsRefresh:!0,sessionExpired:n.decodedToken.exp<r}};export{s as t};
|
|
2
|
+
//# sourceMappingURL=refreshSession-BvZuC-Or.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refreshSession-BvZuC-Or.js","names":[],"sources":["../src/session/refreshSession.ts"],"sourcesContent":["import type { Client as AuthClient, ErrorResponse } from '../auth';\nimport type { FetchResponse } from '../fetch';\nimport type { Session } from './session';\nimport type { SessionStorage } from './storage';\n\nclass DummyLock implements Lock {\n async request(\n _name: string,\n _options: { mode: 'exclusive' | 'shared' },\n // biome-ignore lint/suspicious/noExplicitAny: any\n callback: () => Promise<any>,\n ) {\n return callback();\n }\n}\n\ninterface Lock {\n request: (\n name: string,\n options: { mode: 'exclusive' | 'shared' },\n // biome-ignore lint/suspicious/noExplicitAny: blah\n callback: () => Promise<any>,\n // biome-ignore lint/suspicious/noExplicitAny: blah\n ) => Promise<any>;\n}\n\nconst lock: Lock =\n typeof navigator !== 'undefined' && navigator.locks\n ? navigator.locks\n : new DummyLock();\n\n/**\n * Refreshes the authentication session if needed\n *\n * This function checks if the current session needs to be refreshed based on\n * the access token expiration time. If a refresh is needed, it will attempt to\n * refresh the token using the provided auth client.\n *\n * @param auth - The authentication client to use for token refresh\n * @param storage - The session storage implementation\n * @param marginSeconds - The number of seconds before the token expiration to refresh the session. If the token is still valid for this duration, it will not be refreshed. Set to 0 to force the refresh.\n * @returns A promise that resolves to the current session (refreshed if needed) or null if no session exists\n */\nexport const refreshSession = async (\n auth: AuthClient,\n storage: SessionStorage,\n marginSeconds = 60,\n): Promise<Session | null> => {\n try {\n return await _refreshSession(auth, storage, marginSeconds);\n } catch (error) {\n try {\n // we retry the refresh token in case of transient error\n // or race conditions\n console.warn('error refreshing session, retrying:', error);\n return await _refreshSession(auth, storage, marginSeconds);\n } catch (error) {\n const errResponse = error as FetchResponse<ErrorResponse>;\n if (errResponse?.status === 401) {\n // this probably means the refresh token is invalid\n console.error('session probably expired');\n storage.remove();\n }\n return null;\n }\n }\n};\n\n/**\n * Internal implementation of the refresh session logic\n *\n * @param auth - The authentication client to use for token refresh\n * @param storage - The session storage implementation\n * @param marginSeconds - How many seconds before expiration to trigger a refresh\n * @returns A promise that resolves to the current session (refreshed if needed) or null if no session exists\n * @private\n */\nconst _refreshSession = async (\n auth: AuthClient,\n storage: SessionStorage,\n marginSeconds = 60,\n): Promise<Session | null> => {\n const {\n session,\n needsRefresh,\n }: { session: Session | null; needsRefresh: boolean } = await lock.request(\n 'nhostSessionLock',\n { mode: 'shared' },\n async () => {\n return _needsRefresh(storage, marginSeconds);\n },\n );\n\n if (!session) {\n return null; // No session found\n }\n\n if (!needsRefresh) {\n return session; // No need to refresh\n }\n\n const refreshedSession: Session | null = await lock.request(\n 'nhostSessionLock',\n { mode: 'exclusive' },\n async () => {\n const { session, needsRefresh, sessionExpired } = _needsRefresh(\n storage,\n marginSeconds,\n );\n\n if (!session) {\n return null; // No session found\n }\n\n if (!needsRefresh) {\n return session; // No need to refresh\n }\n\n try {\n const response = await auth.refreshToken({\n refreshToken: session.refreshToken,\n });\n storage.set(response.body);\n\n return response.body;\n } catch (error) {\n if (!sessionExpired) {\n return session;\n }\n\n throw error;\n }\n },\n );\n\n return refreshedSession;\n};\n\n/**\n * Checks if the current session needs to be refreshed based on token expiration\n *\n * @param storage - The session storage implementation\n * @param marginSeconds - How many seconds before expiration to trigger a refresh\n * @returns An object containing the session, whether it needs refreshing, and whether it has expired\n * @private\n */\nconst _needsRefresh = (storage: SessionStorage, marginSeconds = 60) => {\n const session = storage.get();\n if (!session) {\n return { session: null, needsRefresh: false, sessionExpired: false };\n }\n\n if (!session.decodedToken || !session.decodedToken.exp) {\n // if the session does not have a valid decoded token, treat it as expired\n // as we can't determine its validity\n return { session, needsRefresh: true, sessionExpired: true };\n }\n\n // Force refresh if marginSeconds is 0\n if (marginSeconds === 0) {\n return { session, needsRefresh: true, sessionExpired: false };\n }\n\n const currentTime = Date.now();\n if (session.decodedToken.exp - currentTime > marginSeconds * 1000) {\n return { session, needsRefresh: false, sessionExpired: false };\n }\n\n return {\n session,\n needsRefresh: true,\n sessionExpired: session.decodedToken.exp < currentTime,\n };\n};\n"],"mappings":"AAKA,IAqBM,EACiB,oBAAd,WAA6B,UAAU,MAC1C,UAAU,MACV,IAxBN,MACE,aAAM,CACJ,EACA,EAEA,GAEA,OAAO,MA+BE,EAAiB,MAC5B,EACA,EACA,EAAgB,MAEhB,IACE,aAAa,EAAgB,EAAM,EAAS,SACrC,GACP,IAIE,OADA,QAAQ,KAAK,sCAAuC,SACvC,EAAgB,EAAM,EAAS,SACrC,GAOP,OAL4B,MADR,GACH,SAEf,QAAQ,MAAM,4BACd,EAAQ,UAEH,QAcP,EAAkB,MACtB,EACA,EACA,EAAgB,MAEhB,MAAM,QACJ,EAAA,aACA,SAC4D,EAAK,QACjE,mBACA,CAAE,KAAM,WACR,SACS,EAAc,EAAS,KAIlC,OAAK,EAIA,QAI0C,EAAK,QAClD,mBACA,CAAE,KAAM,cACR,UACE,MAAM,QAAE,EAAA,aAAS,EAAA,eAAc,GAAmB,EAChD,EACA,GAGF,IAAK,EACH,OAAO,KAGT,IAAK,EACH,OAAO,EAGT,IACE,MAAM,QAAiB,EAAK,aAAa,CACvC,aAAc,EAAQ,eAIxB,OAFA,EAAQ,IAAI,EAAS,MAEd,EAAS,WACT,GACP,IAAK,EACH,OAAO,EAGT,MAAM,MAhCH,EAJA,IAuCR,EAaG,EAAA,CAAiB,EAAyB,EAAgB,MAC9D,MAAM,EAAU,EAAQ,MACxB,IAAK,EACH,MAAO,CAAE,QAAS,KAAM,cAAc,EAAO,gBAAgB,GAG/D,IAAK,EAAQ,eAAiB,EAAQ,aAAa,IAGjD,MAAO,CAAE,UAAS,cAAc,EAAM,gBAAgB,GAIxD,GAAsB,IAAlB,EACF,MAAO,CAAE,UAAS,cAAc,EAAM,gBAAgB,GAGxD,MAAM,EAAc,KAAK,MACzB,OAAI,EAAQ,aAAa,IAAM,EAA8B,IAAhB,EACpC,CAAE,UAAS,cAAc,EAAO,gBAAgB,GAGlD,CACL,UACA,cAAc,EACd,eAAgB,EAAQ,aAAa,IAAM,EAC5C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e="undefined"!=typeof navigator&&navigator.locks?navigator.locks:new class{async request(e,s,r){return r()}},s=async(e,s,n=60)=>{try{return await r(e,s,n)}catch(o){try{return console.warn("error refreshing session, retrying:",o),await r(e,s,n)}catch(o){return 401===o?.status&&(console.error("session probably expired"),s.remove()),null}}},r=async(s,r,o=60)=>{const{session:t,needsRefresh:i}=await e.request("nhostSessionLock",{mode:"shared"},(async()=>n(r,o)));return t?i?await e.request("nhostSessionLock",{mode:"exclusive"},(async()=>{const{session:e,needsRefresh:t,sessionExpired:i}=n(r,o);if(!e)return null;if(!t)return e;try{const n=await s.refreshToken({refreshToken:e.refreshToken});return r.set(n.body),n.body}catch(d){if(!i)return e;throw d}})):t:null},n=(e,s=60)=>{const r=e.get();if(!r)return{session:null,needsRefresh:!1,sessionExpired:!1};if(!r.decodedToken||!r.decodedToken.exp)return{session:r,needsRefresh:!0,sessionExpired:!0};if(0===s)return{session:r,needsRefresh:!0,sessionExpired:!1};const n=Date.now();return r.decodedToken.exp-n>1e3*s?{session:r,needsRefresh:!1,sessionExpired:!1}:{session:r,needsRefresh:!0,sessionExpired:r.decodedToken.exp<n}};Object.defineProperty(exports,"refreshSession",{enumerable:!0,get:function(){return s}});
|
|
2
|
+
//# sourceMappingURL=refreshSession-ZW-JniXX.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refreshSession-ZW-JniXX.cjs","names":[],"sources":["../src/session/refreshSession.ts"],"sourcesContent":["import type { Client as AuthClient, ErrorResponse } from '../auth';\nimport type { FetchResponse } from '../fetch';\nimport type { Session } from './session';\nimport type { SessionStorage } from './storage';\n\nclass DummyLock implements Lock {\n async request(\n _name: string,\n _options: { mode: 'exclusive' | 'shared' },\n // biome-ignore lint/suspicious/noExplicitAny: any\n callback: () => Promise<any>,\n ) {\n return callback();\n }\n}\n\ninterface Lock {\n request: (\n name: string,\n options: { mode: 'exclusive' | 'shared' },\n // biome-ignore lint/suspicious/noExplicitAny: blah\n callback: () => Promise<any>,\n // biome-ignore lint/suspicious/noExplicitAny: blah\n ) => Promise<any>;\n}\n\nconst lock: Lock =\n typeof navigator !== 'undefined' && navigator.locks\n ? navigator.locks\n : new DummyLock();\n\n/**\n * Refreshes the authentication session if needed\n *\n * This function checks if the current session needs to be refreshed based on\n * the access token expiration time. If a refresh is needed, it will attempt to\n * refresh the token using the provided auth client.\n *\n * @param auth - The authentication client to use for token refresh\n * @param storage - The session storage implementation\n * @param marginSeconds - The number of seconds before the token expiration to refresh the session. If the token is still valid for this duration, it will not be refreshed. Set to 0 to force the refresh.\n * @returns A promise that resolves to the current session (refreshed if needed) or null if no session exists\n */\nexport const refreshSession = async (\n auth: AuthClient,\n storage: SessionStorage,\n marginSeconds = 60,\n): Promise<Session | null> => {\n try {\n return await _refreshSession(auth, storage, marginSeconds);\n } catch (error) {\n try {\n // we retry the refresh token in case of transient error\n // or race conditions\n console.warn('error refreshing session, retrying:', error);\n return await _refreshSession(auth, storage, marginSeconds);\n } catch (error) {\n const errResponse = error as FetchResponse<ErrorResponse>;\n if (errResponse?.status === 401) {\n // this probably means the refresh token is invalid\n console.error('session probably expired');\n storage.remove();\n }\n return null;\n }\n }\n};\n\n/**\n * Internal implementation of the refresh session logic\n *\n * @param auth - The authentication client to use for token refresh\n * @param storage - The session storage implementation\n * @param marginSeconds - How many seconds before expiration to trigger a refresh\n * @returns A promise that resolves to the current session (refreshed if needed) or null if no session exists\n * @private\n */\nconst _refreshSession = async (\n auth: AuthClient,\n storage: SessionStorage,\n marginSeconds = 60,\n): Promise<Session | null> => {\n const {\n session,\n needsRefresh,\n }: { session: Session | null; needsRefresh: boolean } = await lock.request(\n 'nhostSessionLock',\n { mode: 'shared' },\n async () => {\n return _needsRefresh(storage, marginSeconds);\n },\n );\n\n if (!session) {\n return null; // No session found\n }\n\n if (!needsRefresh) {\n return session; // No need to refresh\n }\n\n const refreshedSession: Session | null = await lock.request(\n 'nhostSessionLock',\n { mode: 'exclusive' },\n async () => {\n const { session, needsRefresh, sessionExpired } = _needsRefresh(\n storage,\n marginSeconds,\n );\n\n if (!session) {\n return null; // No session found\n }\n\n if (!needsRefresh) {\n return session; // No need to refresh\n }\n\n try {\n const response = await auth.refreshToken({\n refreshToken: session.refreshToken,\n });\n storage.set(response.body);\n\n return response.body;\n } catch (error) {\n if (!sessionExpired) {\n return session;\n }\n\n throw error;\n }\n },\n );\n\n return refreshedSession;\n};\n\n/**\n * Checks if the current session needs to be refreshed based on token expiration\n *\n * @param storage - The session storage implementation\n * @param marginSeconds - How many seconds before expiration to trigger a refresh\n * @returns An object containing the session, whether it needs refreshing, and whether it has expired\n * @private\n */\nconst _needsRefresh = (storage: SessionStorage, marginSeconds = 60) => {\n const session = storage.get();\n if (!session) {\n return { session: null, needsRefresh: false, sessionExpired: false };\n }\n\n if (!session.decodedToken || !session.decodedToken.exp) {\n // if the session does not have a valid decoded token, treat it as expired\n // as we can't determine its validity\n return { session, needsRefresh: true, sessionExpired: true };\n }\n\n // Force refresh if marginSeconds is 0\n if (marginSeconds === 0) {\n return { session, needsRefresh: true, sessionExpired: false };\n }\n\n const currentTime = Date.now();\n if (session.decodedToken.exp - currentTime > marginSeconds * 1000) {\n return { session, needsRefresh: false, sessionExpired: false };\n }\n\n return {\n session,\n needsRefresh: true,\n sessionExpired: session.decodedToken.exp < currentTime,\n };\n};\n"],"mappings":"AAKA,IAqBM,EACiB,oBAAd,WAA6B,UAAU,MAC1C,UAAU,MACV,IAxBN,MACE,aAAM,CACJ,EACA,EAEA,GAEA,OAAO,MA+BE,EAAiB,MAC5B,EACA,EACA,EAAgB,MAEhB,IACE,aAAa,EAAgB,EAAM,EAAS,SACrC,GACP,IAIE,OADA,QAAQ,KAAK,sCAAuC,SACvC,EAAgB,EAAM,EAAS,SACrC,GAOP,OAL4B,MADR,GACH,SAEf,QAAQ,MAAM,4BACd,EAAQ,UAEH,QAcP,EAAkB,MACtB,EACA,EACA,EAAgB,MAEhB,MAAM,QACJ,EAAA,aACA,SAC4D,EAAK,QACjE,mBACA,CAAE,KAAM,WACR,SACS,EAAc,EAAS,KAIlC,OAAK,EAIA,QAI0C,EAAK,QAClD,mBACA,CAAE,KAAM,cACR,UACE,MAAM,QAAE,EAAA,aAAS,EAAA,eAAc,GAAmB,EAChD,EACA,GAGF,IAAK,EACH,OAAO,KAGT,IAAK,EACH,OAAO,EAGT,IACE,MAAM,QAAiB,EAAK,aAAa,CACvC,aAAc,EAAQ,eAIxB,OAFA,EAAQ,IAAI,EAAS,MAEd,EAAS,WACT,GACP,IAAK,EACH,OAAO,EAGT,MAAM,MAhCH,EAJA,IAuCR,EAaG,EAAA,CAAiB,EAAyB,EAAgB,MAC9D,MAAM,EAAU,EAAQ,MACxB,IAAK,EACH,MAAO,CAAE,QAAS,KAAM,cAAc,EAAO,gBAAgB,GAG/D,IAAK,EAAQ,eAAiB,EAAQ,aAAa,IAGjD,MAAO,CAAE,UAAS,cAAc,EAAM,gBAAgB,GAIxD,GAAsB,IAAlB,EACF,MAAO,CAAE,UAAS,cAAc,EAAM,gBAAgB,GAGxD,MAAM,EAAc,KAAK,MACzB,OAAI,EAAQ,aAAa,IAAM,EAA8B,IAAhB,EACpC,CAAE,UAAS,cAAc,EAAO,gBAAgB,GAGlD,CACL,UACA,cAAc,EACd,eAAgB,EAAQ,aAAa,IAAM,EAC5C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=e=>{const o=e.split(".");if(3!==o.length||!o[1])throw new Error("Invalid access token format");const r=JSON.parse((e=>{let t=e.replace(/-/g,"+").replace(/_/g,"/");const s=t.length%4;s&&(t+="=".repeat(4-s));const o=atob(t),r=Uint8Array.from(o,(e=>e.charCodeAt(0)));return(new TextDecoder).decode(r)})(o[1])),i="number"==typeof r.iat?1e3*r.iat:void 0,n="number"==typeof r.exp?1e3*r.exp:void 0,a=r["https://hasura.io/jwt/claims"],c=a?Object.entries(a).reduce(((e,[o,r])=>("string"==typeof r&&t(r)?e[o]=s(r):e[o]=r,e)),{}):void 0;return{...r,iat:i,exp:n,"https://hasura.io/jwt/claims":c}},t=e=>e.startsWith("{")&&e.endsWith("}"),s=e=>e&&"{}"!==e?e.slice(1,-1).split(",").map((e=>e.trim().replace(/^"(.*)"$/,"$1"))):[],o="nhostSession",r=class{storageKey;constructor(e){this.storageKey=e?.storageKey||"nhostSession"}get(){try{const e=window.localStorage.getItem(this.storageKey);return e?JSON.parse(e):null}catch{return this.remove(),null}}set(e){window.localStorage.setItem(this.storageKey,JSON.stringify(e))}remove(){window.localStorage.removeItem(this.storageKey)}},i=class{session=null;get(){return this.session}set(e){this.session=e}remove(){this.session=null}},n=class{cookieName;expirationDays;secure;sameSite;constructor(e){this.cookieName=e?.cookieName||"nhostSession",this.expirationDays=e?.expirationDays??30,this.secure=e?.secure??!0,this.sameSite=e?.sameSite||"lax"}get(){const e=document.cookie.split(";");for(const t of e){const[e,s]=t.trim().split("=");if(e===this.cookieName)try{return JSON.parse(decodeURIComponent(s||""))}catch{return this.remove(),null}}return null}set(e){const t=/* @__PURE__ */new Date;t.setTime(t.getTime()+24*this.expirationDays*60*60*1e3);const s=encodeURIComponent(JSON.stringify(e)),o=`${this.cookieName}=${s}; expires=${t.toUTCString()}; path=/; ${this.secure?"secure; ":""}SameSite=${this.sameSite}`;document.cookie=o}remove(){document.cookie=`${this.cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; ${this.secure?"secure; ":""}SameSite=${this.sameSite}`}},a=class{storage;subscribers=/* @__PURE__ */new Set;constructor(e){this.storage=e}get(){return this.storage.get()}set(t){const s=e(t.accessToken),o={...t,decodedToken:s};this.storage.set(o),this.notifySubscribers(o)}remove(){this.storage.remove(),this.notifySubscribers(null)}onChange(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(e){for(const s of this.subscribers)try{s(e)}catch(t){console.error("Error notifying subscriber:",t)}}},c=()=>"undefined"!=typeof window?new r:new i;export{r as a,o as i,c as n,i as o,n as r,a as t};
|
|
2
|
+
//# sourceMappingURL=session-oECsmiRB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-oECsmiRB.js","names":[],"sources":["../src/session/session.ts","../src/session/storageBackend.ts","../src/session/storage.ts"],"sourcesContent":["import type { Session as AuthSession } from '../auth';\n\n/**\n * Decoded JWT token payload with processed timestamps and Hasura claims\n */\nexport interface DecodedToken {\n /** Token expiration time as Date object */\n exp?: number;\n /** Token issued at time as Date object */\n iat?: number;\n /** Token issuer */\n iss?: string;\n /** Token subject (user ID) */\n sub?: string;\n /** Hasura JWT claims with PostgreSQL arrays converted to JavaScript arrays */\n 'https://hasura.io/jwt/claims'?: Record<string, unknown>;\n /** Any other JWT claims */\n [key: string]: unknown;\n}\n\nexport interface Session extends AuthSession {\n /** Decoded JWT token payload with processed timestamps and Hasura claims */\n decodedToken: DecodedToken;\n}\n\n/**\n * Decodes a base64url-encoded string (RFC 4648 Section 5) to a UTF-8 string.\n *\n * JWTs use base64url encoding, which differs from standard base64 by using\n * `-` and `_` instead of `+` and `/`, and omitting padding. The browser's\n * native `atob()` does not support base64url, so we must handle the conversion.\n */\nconst decodeBase64Url = (input: string): string => {\n // Convert base64url to standard base64\n let base64 = input.replace(/-/g, '+').replace(/_/g, '/');\n const pad = base64.length % 4;\n if (pad) {\n base64 += '='.repeat(4 - pad);\n }\n\n // Use TextDecoder for proper UTF-8 support (atob alone mangles multi-byte characters)\n const binaryString = atob(base64);\n const bytes = Uint8Array.from(binaryString, (c) => c.charCodeAt(0));\n return new TextDecoder().decode(bytes);\n};\n\nexport const decodeUserSession = (accessToken: string): DecodedToken => {\n const s = accessToken.split('.');\n if (s.length !== 3 || !s[1]) {\n throw new Error('Invalid access token format');\n }\n\n const decodedToken = JSON.parse(decodeBase64Url(s[1])) as Record<\n string,\n unknown\n >;\n\n // Convert iat and exp to Date objects\n const iat =\n typeof decodedToken['iat'] === 'number'\n ? decodedToken['iat'] * 1000 // Convert seconds to milliseconds\n : undefined;\n const exp =\n typeof decodedToken['exp'] === 'number'\n ? decodedToken['exp'] * 1000 // Convert seconds to milliseconds\n : undefined;\n\n // Process Hasura claims - dynamically convert PostgreSQL array notation to arrays\n const hasuraClaims = decodedToken['https://hasura.io/jwt/claims'] as\n | Record<string, unknown>\n | undefined;\n const processedClaims = hasuraClaims\n ? Object.entries(hasuraClaims).reduce(\n (acc, [key, value]) => {\n if (typeof value === 'string' && isPostgresArray(value)) {\n acc[key] = parsePostgresArray(value);\n } else {\n acc[key] = value;\n }\n return acc;\n },\n {} as Record<string, unknown>,\n )\n : undefined;\n\n return {\n ...decodedToken,\n iat,\n exp,\n 'https://hasura.io/jwt/claims': processedClaims,\n };\n};\n\nconst isPostgresArray = (value: string): boolean => {\n return value.startsWith('{') && value.endsWith('}');\n};\n\nconst parsePostgresArray = (value: string): string[] => {\n if (!value || value === '{}') return [];\n // Remove curly braces and split by comma, handling quoted values\n return value\n .slice(1, -1)\n .split(',')\n .map((item) => item.trim().replace(/^\"(.*)\"$/, '$1'));\n};\n","/**\n * Storage implementations for session persistence in different environments.\n *\n * This module provides different storage adapters for persisting authentication sessions\n * across page reloads and browser sessions.\n */\n\nimport type { Session } from './session';\n\n/**\n * Session storage interface for session persistence.\n * This interface can be implemented to provide custom storage solutions.\n */\nexport interface SessionStorageBackend {\n /**\n * Get the current session from storage\n * @returns The stored session or null if not found\n */\n get(): Session | null;\n\n /**\n * Set the session in storage\n * @param value - The session to store\n */\n set(value: Session): void;\n\n /**\n * Remove the session from storage\n */\n remove(): void;\n}\n\n/**\n * Default storage key used for storing the Nhost session\n */\nexport const DEFAULT_SESSION_KEY = 'nhostSession';\n\n/**\n * Browser localStorage implementation of StorageInterface.\n * Persists the session across page reloads and browser restarts.\n */\nexport class LocalStorage implements SessionStorageBackend {\n private readonly storageKey: string;\n\n /**\n * Creates a new LocalStorage instance\n * @param options - Configuration options\n * @param options.storageKey - The key to use in localStorage (defaults to \"nhostSession\")\n */\n constructor(options?: { storageKey?: string }) {\n this.storageKey = options?.storageKey || DEFAULT_SESSION_KEY;\n }\n\n /**\n * Gets the session from localStorage\n * @returns The stored session or null if not found\n */\n get(): Session | null {\n try {\n const value = window.localStorage.getItem(this.storageKey);\n return value ? (JSON.parse(value) as Session) : null;\n } catch {\n this.remove();\n return null;\n }\n }\n\n /**\n * Sets the session in localStorage\n * @param value - The session to store\n */\n set(value: Session): void {\n window.localStorage.setItem(this.storageKey, JSON.stringify(value));\n }\n\n /**\n * Removes the session from localStorage\n */\n remove(): void {\n window.localStorage.removeItem(this.storageKey);\n }\n}\n\n/**\n * In-memory storage implementation for non-browser environments or when\n * persistent storage is not available or desirable.\n */\nexport class MemoryStorage implements SessionStorageBackend {\n private session: Session | null = null;\n\n /**\n * Gets the session from memory\n * @returns The stored session or null if not set\n */\n get(): Session | null {\n return this.session;\n }\n\n /**\n * Sets the session in memory\n * @param value - The session to store\n */\n set(value: Session): void {\n this.session = value;\n }\n\n /**\n * Clears the session from memory\n */\n remove(): void {\n this.session = null;\n }\n}\n\n/**\n * Cookie-based storage implementation.\n * This storage uses web browser cookies to store the session so it's not\n * available in server-side environments. It is useful though for synchronizing\n * sessions between client and server environments.\n */\nexport class CookieStorage implements SessionStorageBackend {\n private readonly cookieName: string;\n private readonly expirationDays: number;\n private readonly secure: boolean;\n private readonly sameSite: 'strict' | 'lax' | 'none';\n\n /**\n * Creates a new CookieStorage instance\n * @param options - Configuration options\n * @param options.cookieName - Name of the cookie to use (defaults to \"nhostSession\")\n * @param options.expirationDays - Number of days until the cookie expires (defaults to 30)\n * @param options.secure - Whether to set the Secure flag on the cookie (defaults to true)\n * @param options.sameSite - SameSite policy for the cookie (defaults to \"lax\")\n */\n constructor(options?: {\n cookieName?: string;\n expirationDays?: number;\n secure?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n }) {\n this.cookieName = options?.cookieName || DEFAULT_SESSION_KEY;\n this.expirationDays = options?.expirationDays ?? 30;\n this.secure = options?.secure ?? true;\n this.sameSite = options?.sameSite || 'lax';\n }\n\n /**\n * Gets the session from cookies\n * @returns The stored session or null if not found\n */\n get(): Session | null {\n const cookies = document.cookie.split(';');\n for (const cookie of cookies) {\n const [name, value] = cookie.trim().split('=');\n if (name === this.cookieName) {\n try {\n return JSON.parse(decodeURIComponent(value || '')) as Session;\n } catch {\n this.remove();\n return null;\n }\n }\n }\n return null;\n }\n\n /**\n * Sets the session in a cookie\n * @param value - The session to store\n */\n set(value: Session): void {\n const expires = new Date();\n expires.setTime(\n expires.getTime() + this.expirationDays * 24 * 60 * 60 * 1000,\n );\n\n const cookieValue = encodeURIComponent(JSON.stringify(value));\n const cookieString = `${this.cookieName}=${cookieValue}; expires=${expires.toUTCString()}; path=/; ${this.secure ? 'secure; ' : ''}SameSite=${this.sameSite}`;\n\n // biome-ignore lint/suspicious/noDocumentCookie: this is unnecessary\n document.cookie = cookieString;\n }\n\n /**\n * Removes the session cookie\n */\n remove(): void {\n // biome-ignore lint/suspicious/noDocumentCookie: this is unnecessary\n document.cookie = `${this.cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; ${this.secure ? 'secure; ' : ''}SameSite=${this.sameSite}`;\n }\n}\n","/**\n * Storage implementations for session persistence in different environments.\n *\n * This module provides different storage adapters for persisting authentication sessions\n * across page reloads and browser sessions.\n */\n\nimport type { Session as AuthSession } from '../auth';\nimport { decodeUserSession, type Session } from './session';\nimport {\n LocalStorage,\n MemoryStorage,\n type SessionStorageBackend,\n} from './storageBackend';\n\n/**\n * Callback function type for session change subscriptions\n */\nexport type SessionChangeCallback = (session: Session | null) => void;\n\n/**\n * A wrapper around any SessionStorageInterface implementation that adds\n * the ability to subscribe to session changes.\n */\nexport class SessionStorage {\n private readonly storage: SessionStorageBackend;\n private subscribers = new Set<SessionChangeCallback>();\n\n /**\n * Creates a new SessionStorage instance\n * @param storage - The underlying storage implementation to use\n */\n constructor(storage: SessionStorageBackend) {\n this.storage = storage;\n }\n\n /**\n * Gets the session from the underlying storage\n * @returns The stored session or null if not found\n */\n get(): Session | null {\n return this.storage.get();\n }\n\n /**\n * Sets the session in the underlying storage and notifies subscribers\n * @param value - The session to store\n */\n set(value: AuthSession): void {\n const decodedToken = decodeUserSession(value.accessToken);\n const decodedSession = {\n ...value,\n decodedToken: decodedToken,\n };\n\n this.storage.set(decodedSession);\n this.notifySubscribers(decodedSession);\n }\n\n /**\n * Removes the session from the underlying storage and notifies subscribers\n */\n remove(): void {\n this.storage.remove();\n this.notifySubscribers(null);\n }\n\n /**\n * Subscribe to session changes\n * @param callback - Function that will be called when the session changes\n * @returns An unsubscribe function to remove this subscription\n */\n onChange(callback: SessionChangeCallback) {\n this.subscribers.add(callback);\n\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n /**\n * Notify all subscribers of a session change\n * @param session - The new session value or null if removed\n */\n private notifySubscribers(session: Session | null): void {\n for (const subscriber of this.subscribers) {\n try {\n subscriber(session);\n } catch (error) {\n console.error('Error notifying subscriber:', error);\n }\n }\n }\n}\n\n/**\n * Detects the best available storage implementation for the current environment.\n *\n * The detection process follows this order:\n * 1. Try to use localStorage if we're in a browser environment\n * 2. Fall back to in-memory storage if localStorage isn't available\n *\n * @returns The best available storage implementation as a SessionStorageBackend\n */\nexport const detectStorage = (): SessionStorageBackend => {\n if (typeof window !== 'undefined') {\n return new LocalStorage();\n }\n return new MemoryStorage();\n};\n"],"mappings":"AAgCA,IAca,EAAqB,IAChC,MAAM,EAAI,EAAY,MAAM,KAC5B,GAAiB,IAAb,EAAE,SAAiB,EAAE,GACvB,MAAM,IAAI,MAAM,+BAGlB,MAAM,EAAe,KAAK,MApBtB,CAAmB,IAEvB,IAAI,EAAS,EAAM,QAAQ,KAAM,KAAK,QAAQ,KAAM,KACpD,MAAM,EAAM,EAAO,OAAS,EACxB,IACF,GAAU,IAAI,OAAO,EAAI,IAI3B,MAAM,EAAe,KAAK,GACpB,EAAQ,WAAW,KAAK,GAAe,GAAM,EAAE,WAAW,KAChE,OAAO,IAAI,aAAc,OAAO,EAAM,EASN,CAAgB,EAAE,KAM5C,EAC2B,iBAAxB,EAAa,IACM,IAAtB,EAAa,SACb,EACA,EAC2B,iBAAxB,EAAa,IACM,IAAtB,EAAa,SACb,EAGA,EAAe,EAAa,gCAG5B,EAAkB,EACpB,OAAO,QAAQ,GAAc,QAAA,CAC1B,GAAM,EAAK,MACW,iBAAV,GAAsB,EAAgB,GAC/C,EAAI,GAAO,EAAmB,GAE9B,EAAI,GAAO,EAEN,IAET,CAAA,QAEF,EAEJ,MAAO,IACF,EACH,MACA,MACA,+BAAgC,EACjC,EAGG,EAAmB,GAChB,EAAM,WAAW,MAAQ,EAAM,SAAS,KAG3C,EAAsB,GACrB,GAAmB,OAAV,EAEP,EACJ,MAAM,GAAG,GACT,MAAM,KACN,KAAK,GAAS,EAAK,OAAO,QAAQ,WAAY,QALZ,GC/D1B,EAAsB,eAMtB,EAAb,MACE,WAOA,WAAA,CAAY,GACV,KAAK,WAAa,GAAS,YAAA,eAO7B,GAAA,GACE,IACE,MAAM,EAAQ,OAAO,aAAa,QAAQ,KAAK,YAC/C,OAAO,EAAS,KAAK,MAAM,GAAqB,WAGhD,OADA,KAAK,SACE,MAQX,GAAA,CAAI,GACF,OAAO,aAAa,QAAQ,KAAK,WAAY,KAAK,UAAU,IAM9D,MAAA,GACE,OAAO,aAAa,WAAW,KAAK,cAQ3B,EAAb,MACE,QAAkC,KAMlC,GAAA,GACE,OAAO,KAAK,QAOd,GAAA,CAAI,GACF,KAAK,QAAU,EAMjB,MAAA,GACE,KAAK,QAAU,OAUN,EAAb,MACE,WACA,eACA,OACA,SAUA,WAAA,CAAY,GAMV,KAAK,WAAa,GAAS,YAAA,eAC3B,KAAK,eAAiB,GAAS,gBAAkB,GACjD,KAAK,OAAS,GAAS,SAAU,EACjC,KAAK,SAAW,GAAS,UAAY,MAOvC,GAAA,GACE,MAAM,EAAU,SAAS,OAAO,MAAM,KACtC,IAAK,MAAM,KAAU,EAAS,CAC5B,MAAO,EAAM,GAAS,EAAO,OAAO,MAAM,KAC1C,GAAI,IAAS,KAAK,WAChB,IACE,OAAO,KAAK,MAAM,mBAAmB,GAAS,WAG9C,OADA,KAAK,SACE,MAIb,OAAO,KAOT,GAAA,CAAI,GACF,MAAM,iBAAU,IAAI,KACpB,EAAQ,QACN,EAAQ,UAAkC,GAAtB,KAAK,eAAsB,GAAK,GAAK,KAG3D,MAAM,EAAc,mBAAmB,KAAK,UAAU,IAChD,EAAe,GAAG,KAAK,cAAc,cAAwB,EAAQ,0BAA0B,KAAK,OAAS,WAAa,cAAc,KAAK,WAGnJ,SAAS,OAAS,EAMpB,MAAA,GAEE,SAAS,OAAS,GAAG,KAAK,+DAA+D,KAAK,OAAS,WAAa,cAAc,KAAK,aCpK9H,EAAb,MACE,QACA,2BAAsB,IAAI,IAM1B,WAAA,CAAY,GACV,KAAK,QAAU,EAOjB,GAAA,GACE,OAAO,KAAK,QAAQ,MAOtB,GAAA,CAAI,GACF,MAAM,EAAe,EAAkB,EAAM,aACvC,EAAiB,IAClB,EACW,gBAGhB,KAAK,QAAQ,IAAI,GACjB,KAAK,kBAAkB,GAMzB,MAAA,GACE,KAAK,QAAQ,SACb,KAAK,kBAAkB,MAQzB,QAAA,CAAS,GAGP,OAFA,KAAK,YAAY,IAAI,GAErB,KACE,KAAK,YAAY,OAAO,EAAS,EAQrC,iBAAA,CAA0B,GACxB,IAAK,MAAM,KAAc,KAAK,YAC5B,IACE,EAAW,SACJ,GACP,QAAQ,MAAM,8BAA+B,MAexC,EAAA,IACW,oBAAX,OACF,IAAI,EAEN,IAAI"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=e=>{const r=e.split(".");if(3!==r.length||!r[1])throw new Error("Invalid access token format");const o=JSON.parse((e=>{let t=e.replace(/-/g,"+").replace(/_/g,"/");const s=t.length%4;s&&(t+="=".repeat(4-s));const r=atob(t),o=Uint8Array.from(r,(e=>e.charCodeAt(0)));return(new TextDecoder).decode(o)})(r[1])),i="number"==typeof o.iat?1e3*o.iat:void 0,n="number"==typeof o.exp?1e3*o.exp:void 0,c=o["https://hasura.io/jwt/claims"],a=c?Object.entries(c).reduce(((e,[r,o])=>("string"==typeof o&&t(o)?e[r]=s(o):e[r]=o,e)),{}):void 0;return{...o,iat:i,exp:n,"https://hasura.io/jwt/claims":a}},t=e=>e.startsWith("{")&&e.endsWith("}"),s=e=>e&&"{}"!==e?e.slice(1,-1).split(",").map((e=>e.trim().replace(/^"(.*)"$/,"$1"))):[],r=class{storageKey;constructor(e){this.storageKey=e?.storageKey||"nhostSession"}get(){try{const e=window.localStorage.getItem(this.storageKey);return e?JSON.parse(e):null}catch{return this.remove(),null}}set(e){window.localStorage.setItem(this.storageKey,JSON.stringify(e))}remove(){window.localStorage.removeItem(this.storageKey)}},o=class{session=null;get(){return this.session}set(e){this.session=e}remove(){this.session=null}},i=class{cookieName;expirationDays;secure;sameSite;constructor(e){this.cookieName=e?.cookieName||"nhostSession",this.expirationDays=e?.expirationDays??30,this.secure=e?.secure??!0,this.sameSite=e?.sameSite||"lax"}get(){const e=document.cookie.split(";");for(const t of e){const[e,s]=t.trim().split("=");if(e===this.cookieName)try{return JSON.parse(decodeURIComponent(s||""))}catch{return this.remove(),null}}return null}set(e){const t=new Date;t.setTime(t.getTime()+24*this.expirationDays*60*60*1e3);const s=encodeURIComponent(JSON.stringify(e)),r=`${this.cookieName}=${s}; expires=${t.toUTCString()}; path=/; ${this.secure?"secure; ":""}SameSite=${this.sameSite}`;document.cookie=r}remove(){document.cookie=`${this.cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; ${this.secure?"secure; ":""}SameSite=${this.sameSite}`}},n=class{storage;subscribers=new Set;constructor(e){this.storage=e}get(){return this.storage.get()}set(t){const s=e(t.accessToken),r={...t,decodedToken:s};this.storage.set(r),this.notifySubscribers(r)}remove(){this.storage.remove(),this.notifySubscribers(null)}onChange(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(e){for(const s of this.subscribers)try{s(e)}catch(t){console.error("Error notifying subscriber:",t)}}},c=()=>"undefined"!=typeof window?new r:new o;Object.defineProperty(exports,"CookieStorage",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"DEFAULT_SESSION_KEY",{enumerable:!0,get:function(){return"nhostSession"}}),Object.defineProperty(exports,"LocalStorage",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"MemoryStorage",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"SessionStorage",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"detectStorage",{enumerable:!0,get:function(){return c}});
|
|
2
|
+
//# sourceMappingURL=session-r_9IPgQp.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-r_9IPgQp.cjs","names":[],"sources":["../src/session/session.ts","../src/session/storageBackend.ts","../src/session/storage.ts"],"sourcesContent":["import type { Session as AuthSession } from '../auth';\n\n/**\n * Decoded JWT token payload with processed timestamps and Hasura claims\n */\nexport interface DecodedToken {\n /** Token expiration time as Date object */\n exp?: number;\n /** Token issued at time as Date object */\n iat?: number;\n /** Token issuer */\n iss?: string;\n /** Token subject (user ID) */\n sub?: string;\n /** Hasura JWT claims with PostgreSQL arrays converted to JavaScript arrays */\n 'https://hasura.io/jwt/claims'?: Record<string, unknown>;\n /** Any other JWT claims */\n [key: string]: unknown;\n}\n\nexport interface Session extends AuthSession {\n /** Decoded JWT token payload with processed timestamps and Hasura claims */\n decodedToken: DecodedToken;\n}\n\n/**\n * Decodes a base64url-encoded string (RFC 4648 Section 5) to a UTF-8 string.\n *\n * JWTs use base64url encoding, which differs from standard base64 by using\n * `-` and `_` instead of `+` and `/`, and omitting padding. The browser's\n * native `atob()` does not support base64url, so we must handle the conversion.\n */\nconst decodeBase64Url = (input: string): string => {\n // Convert base64url to standard base64\n let base64 = input.replace(/-/g, '+').replace(/_/g, '/');\n const pad = base64.length % 4;\n if (pad) {\n base64 += '='.repeat(4 - pad);\n }\n\n // Use TextDecoder for proper UTF-8 support (atob alone mangles multi-byte characters)\n const binaryString = atob(base64);\n const bytes = Uint8Array.from(binaryString, (c) => c.charCodeAt(0));\n return new TextDecoder().decode(bytes);\n};\n\nexport const decodeUserSession = (accessToken: string): DecodedToken => {\n const s = accessToken.split('.');\n if (s.length !== 3 || !s[1]) {\n throw new Error('Invalid access token format');\n }\n\n const decodedToken = JSON.parse(decodeBase64Url(s[1])) as Record<\n string,\n unknown\n >;\n\n // Convert iat and exp to Date objects\n const iat =\n typeof decodedToken['iat'] === 'number'\n ? decodedToken['iat'] * 1000 // Convert seconds to milliseconds\n : undefined;\n const exp =\n typeof decodedToken['exp'] === 'number'\n ? decodedToken['exp'] * 1000 // Convert seconds to milliseconds\n : undefined;\n\n // Process Hasura claims - dynamically convert PostgreSQL array notation to arrays\n const hasuraClaims = decodedToken['https://hasura.io/jwt/claims'] as\n | Record<string, unknown>\n | undefined;\n const processedClaims = hasuraClaims\n ? Object.entries(hasuraClaims).reduce(\n (acc, [key, value]) => {\n if (typeof value === 'string' && isPostgresArray(value)) {\n acc[key] = parsePostgresArray(value);\n } else {\n acc[key] = value;\n }\n return acc;\n },\n {} as Record<string, unknown>,\n )\n : undefined;\n\n return {\n ...decodedToken,\n iat,\n exp,\n 'https://hasura.io/jwt/claims': processedClaims,\n };\n};\n\nconst isPostgresArray = (value: string): boolean => {\n return value.startsWith('{') && value.endsWith('}');\n};\n\nconst parsePostgresArray = (value: string): string[] => {\n if (!value || value === '{}') return [];\n // Remove curly braces and split by comma, handling quoted values\n return value\n .slice(1, -1)\n .split(',')\n .map((item) => item.trim().replace(/^\"(.*)\"$/, '$1'));\n};\n","/**\n * Storage implementations for session persistence in different environments.\n *\n * This module provides different storage adapters for persisting authentication sessions\n * across page reloads and browser sessions.\n */\n\nimport type { Session } from './session';\n\n/**\n * Session storage interface for session persistence.\n * This interface can be implemented to provide custom storage solutions.\n */\nexport interface SessionStorageBackend {\n /**\n * Get the current session from storage\n * @returns The stored session or null if not found\n */\n get(): Session | null;\n\n /**\n * Set the session in storage\n * @param value - The session to store\n */\n set(value: Session): void;\n\n /**\n * Remove the session from storage\n */\n remove(): void;\n}\n\n/**\n * Default storage key used for storing the Nhost session\n */\nexport const DEFAULT_SESSION_KEY = 'nhostSession';\n\n/**\n * Browser localStorage implementation of StorageInterface.\n * Persists the session across page reloads and browser restarts.\n */\nexport class LocalStorage implements SessionStorageBackend {\n private readonly storageKey: string;\n\n /**\n * Creates a new LocalStorage instance\n * @param options - Configuration options\n * @param options.storageKey - The key to use in localStorage (defaults to \"nhostSession\")\n */\n constructor(options?: { storageKey?: string }) {\n this.storageKey = options?.storageKey || DEFAULT_SESSION_KEY;\n }\n\n /**\n * Gets the session from localStorage\n * @returns The stored session or null if not found\n */\n get(): Session | null {\n try {\n const value = window.localStorage.getItem(this.storageKey);\n return value ? (JSON.parse(value) as Session) : null;\n } catch {\n this.remove();\n return null;\n }\n }\n\n /**\n * Sets the session in localStorage\n * @param value - The session to store\n */\n set(value: Session): void {\n window.localStorage.setItem(this.storageKey, JSON.stringify(value));\n }\n\n /**\n * Removes the session from localStorage\n */\n remove(): void {\n window.localStorage.removeItem(this.storageKey);\n }\n}\n\n/**\n * In-memory storage implementation for non-browser environments or when\n * persistent storage is not available or desirable.\n */\nexport class MemoryStorage implements SessionStorageBackend {\n private session: Session | null = null;\n\n /**\n * Gets the session from memory\n * @returns The stored session or null if not set\n */\n get(): Session | null {\n return this.session;\n }\n\n /**\n * Sets the session in memory\n * @param value - The session to store\n */\n set(value: Session): void {\n this.session = value;\n }\n\n /**\n * Clears the session from memory\n */\n remove(): void {\n this.session = null;\n }\n}\n\n/**\n * Cookie-based storage implementation.\n * This storage uses web browser cookies to store the session so it's not\n * available in server-side environments. It is useful though for synchronizing\n * sessions between client and server environments.\n */\nexport class CookieStorage implements SessionStorageBackend {\n private readonly cookieName: string;\n private readonly expirationDays: number;\n private readonly secure: boolean;\n private readonly sameSite: 'strict' | 'lax' | 'none';\n\n /**\n * Creates a new CookieStorage instance\n * @param options - Configuration options\n * @param options.cookieName - Name of the cookie to use (defaults to \"nhostSession\")\n * @param options.expirationDays - Number of days until the cookie expires (defaults to 30)\n * @param options.secure - Whether to set the Secure flag on the cookie (defaults to true)\n * @param options.sameSite - SameSite policy for the cookie (defaults to \"lax\")\n */\n constructor(options?: {\n cookieName?: string;\n expirationDays?: number;\n secure?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n }) {\n this.cookieName = options?.cookieName || DEFAULT_SESSION_KEY;\n this.expirationDays = options?.expirationDays ?? 30;\n this.secure = options?.secure ?? true;\n this.sameSite = options?.sameSite || 'lax';\n }\n\n /**\n * Gets the session from cookies\n * @returns The stored session or null if not found\n */\n get(): Session | null {\n const cookies = document.cookie.split(';');\n for (const cookie of cookies) {\n const [name, value] = cookie.trim().split('=');\n if (name === this.cookieName) {\n try {\n return JSON.parse(decodeURIComponent(value || '')) as Session;\n } catch {\n this.remove();\n return null;\n }\n }\n }\n return null;\n }\n\n /**\n * Sets the session in a cookie\n * @param value - The session to store\n */\n set(value: Session): void {\n const expires = new Date();\n expires.setTime(\n expires.getTime() + this.expirationDays * 24 * 60 * 60 * 1000,\n );\n\n const cookieValue = encodeURIComponent(JSON.stringify(value));\n const cookieString = `${this.cookieName}=${cookieValue}; expires=${expires.toUTCString()}; path=/; ${this.secure ? 'secure; ' : ''}SameSite=${this.sameSite}`;\n\n // biome-ignore lint/suspicious/noDocumentCookie: this is unnecessary\n document.cookie = cookieString;\n }\n\n /**\n * Removes the session cookie\n */\n remove(): void {\n // biome-ignore lint/suspicious/noDocumentCookie: this is unnecessary\n document.cookie = `${this.cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; ${this.secure ? 'secure; ' : ''}SameSite=${this.sameSite}`;\n }\n}\n","/**\n * Storage implementations for session persistence in different environments.\n *\n * This module provides different storage adapters for persisting authentication sessions\n * across page reloads and browser sessions.\n */\n\nimport type { Session as AuthSession } from '../auth';\nimport { decodeUserSession, type Session } from './session';\nimport {\n LocalStorage,\n MemoryStorage,\n type SessionStorageBackend,\n} from './storageBackend';\n\n/**\n * Callback function type for session change subscriptions\n */\nexport type SessionChangeCallback = (session: Session | null) => void;\n\n/**\n * A wrapper around any SessionStorageInterface implementation that adds\n * the ability to subscribe to session changes.\n */\nexport class SessionStorage {\n private readonly storage: SessionStorageBackend;\n private subscribers = new Set<SessionChangeCallback>();\n\n /**\n * Creates a new SessionStorage instance\n * @param storage - The underlying storage implementation to use\n */\n constructor(storage: SessionStorageBackend) {\n this.storage = storage;\n }\n\n /**\n * Gets the session from the underlying storage\n * @returns The stored session or null if not found\n */\n get(): Session | null {\n return this.storage.get();\n }\n\n /**\n * Sets the session in the underlying storage and notifies subscribers\n * @param value - The session to store\n */\n set(value: AuthSession): void {\n const decodedToken = decodeUserSession(value.accessToken);\n const decodedSession = {\n ...value,\n decodedToken: decodedToken,\n };\n\n this.storage.set(decodedSession);\n this.notifySubscribers(decodedSession);\n }\n\n /**\n * Removes the session from the underlying storage and notifies subscribers\n */\n remove(): void {\n this.storage.remove();\n this.notifySubscribers(null);\n }\n\n /**\n * Subscribe to session changes\n * @param callback - Function that will be called when the session changes\n * @returns An unsubscribe function to remove this subscription\n */\n onChange(callback: SessionChangeCallback) {\n this.subscribers.add(callback);\n\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n /**\n * Notify all subscribers of a session change\n * @param session - The new session value or null if removed\n */\n private notifySubscribers(session: Session | null): void {\n for (const subscriber of this.subscribers) {\n try {\n subscriber(session);\n } catch (error) {\n console.error('Error notifying subscriber:', error);\n }\n }\n }\n}\n\n/**\n * Detects the best available storage implementation for the current environment.\n *\n * The detection process follows this order:\n * 1. Try to use localStorage if we're in a browser environment\n * 2. Fall back to in-memory storage if localStorage isn't available\n *\n * @returns The best available storage implementation as a SessionStorageBackend\n */\nexport const detectStorage = (): SessionStorageBackend => {\n if (typeof window !== 'undefined') {\n return new LocalStorage();\n }\n return new MemoryStorage();\n};\n"],"mappings":"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"}
|