@stacksjs/browser 0.70.293 → 0.70.296
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.
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import type { AuthComposable } from '../types/dashboard';
|
|
2
|
+
/**
|
|
3
|
+
* Refresh the session, collapsing concurrent callers onto one exchange.
|
|
4
|
+
*
|
|
5
|
+
* Never rejects: an exchange that could not be attempted resolves `false`
|
|
6
|
+
* without clearing state, so a caller can tell "signed out" from "could not
|
|
7
|
+
* reach the server" by checking whether `token.value` survived.
|
|
8
|
+
*/
|
|
9
|
+
export declare function refreshSession(): Promise<boolean>;
|
|
10
|
+
/**
|
|
11
|
+
* `fetch` with the access token attached, retrying once through a refresh on a
|
|
12
|
+
* 401.
|
|
13
|
+
*
|
|
14
|
+
* The retry is attempted only when a refresh token exists and the first
|
|
15
|
+
* response was a 401 — a 403 is an authorization decision, not an expiry, and
|
|
16
|
+
* refreshing would not change it.
|
|
17
|
+
*/
|
|
18
|
+
export declare function authFetch(input: string, init?: RequestInit): Promise<Response>;
|
|
2
19
|
export declare function useAuth(): AuthComposable;
|
|
3
20
|
// Strict auth guard middleware
|
|
4
21
|
// Usage: call in setup() of page/component, or in router beforeEach
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useStorage}from"@stacksjs/composables";import{ref}from"@stacksjs/stx";import{withCsrfHeader}from"./csrf";const token=useStorage("token",""),stacksConfig=globalThis.__STACKS_CONFIG__||{},baseUrl=stacksConfig.API_URL||(typeof window<"u"?window.location.origin:""),user=ref(null),isAuthenticated=ref(!1);
|
|
1
|
+
import{readSessionHandoff,stripSessionHandoff,useStorage}from"@stacksjs/composables";import{ref}from"@stacksjs/stx";import{withCsrfHeader}from"./csrf";const token=useStorage("token",""),refreshToken=useStorage("refresh_token",""),stacksConfig=globalThis.__STACKS_CONFIG__||{},baseUrl=stacksConfig.API_URL||(typeof window<"u"?window.location.origin:""),ME_PATH=stacksConfig.AUTH_ME_PATH||"/api/me",REFRESH_PATH=stacksConfig.AUTH_REFRESH_PATH||"/auth/refresh",user=ref(null),isAuthenticated=ref(!1);let inFlightRefresh=null;function applySessionHandoff(pack){const resolved=pack??(typeof window<"u"?readSessionHandoff(window.location.hash):null);if(!resolved)return!1;token.value=resolved.token;if(resolved.refreshToken)refreshToken.value=resolved.refreshToken;if(resolved.user!==void 0&&resolved.user!==null)user.value=resolved.user;isAuthenticated.value=!0;if(typeof window<"u"&&!pack){const rest=stripSessionHandoff(window.location.hash);window.history.replaceState(null,"",`${window.location.pathname}${window.location.search}${rest}`)}return!0}function clearSession(){token.value="";refreshToken.value="";user.value=null;isAuthenticated.value=!1}async function performRefresh(){if(!refreshToken.value)return!1;const response=await fetch(`${baseUrl}${REFRESH_PATH}`,{method:"POST",credentials:"same-origin",headers:withCsrfHeader({"Content-Type":"application/json",Accept:"application/json"}),body:JSON.stringify({refresh_token:refreshToken.value})});if(!response.ok){clearSession();return!1}const data=await response.json(),nextAccess=data.access_token??data.token;if(!nextAccess){clearSession();return!1}token.value=nextAccess;if(data.refresh_token)refreshToken.value=data.refresh_token;return!0}export async function refreshSession(){if(inFlightRefresh)return inFlightRefresh;inFlightRefresh=performRefresh().catch(()=>{return!1}).finally(()=>{inFlightRefresh=null});return inFlightRefresh}export async function authFetch(input,init={}){const send=()=>fetch(input.startsWith("http")?input:`${baseUrl}${input}`,{...init,headers:{...init.headers,...token.value?{Authorization:`Bearer ${token.value}`}:{},Accept:"application/json"}}),response=await send();if(response.status!==401||!refreshToken.value)return response;if(!await refreshSession())return response;return await send()}export function useAuth(){async function fetchAuthUser(){try{if(!token.value){isAuthenticated.value=!1;user.value=null;return null}const response=await authFetch(ME_PATH);if(!response.ok){clearSession();return null}const data=await response.json();user.value=data;isAuthenticated.value=!0;return data}catch(error){console.error("Error fetching user:",error);isAuthenticated.value=!1;user.value=null;return null}}async function checkAuthentication(){try{return await fetchAuthUser()!==null}catch(error){console.error("Error checking authentication:",error);return!1}}async function register(user){const url=`${baseUrl}/register`,data=await(await fetch(url,{method:"POST",credentials:"same-origin",headers:withCsrfHeader({"Content-Type":"application/json"}),body:JSON.stringify(user)})).json();if(isRegisterError(data))return data;if(isRegisterResponse(data)){token.value=data.token;const refreshed=data.refresh_token;if(refreshed)refreshToken.value=refreshed;return data}return data}function isRegisterError(data){return"errors"in data}function isRegisterResponse(data){return"token"in data&&"user"in data}async function login(user){try{const url=`${baseUrl}/login`,response=await fetch(url,{method:"POST",credentials:"same-origin",headers:withCsrfHeader({"Content-Type":"application/json"}),body:JSON.stringify(user)});if(!response.ok){const errorData=await response.json();throw Error(errorData.message||`HTTP error! status: ${response.status}`)}const data=await response.json();token.value=data.token;const refreshed=data.refresh_token;if(refreshed)refreshToken.value=refreshed;await fetchAuthUser();return data}catch(error){return error}}async function logout(){try{const currentToken=token.value;if(currentToken)await fetch(`${baseUrl}/logout`,{method:"POST",headers:{Authorization:`Bearer ${currentToken}`,Accept:"application/json"}})}catch(error){console.error("Error during logout:",error)}finally{clearSession()}}async function completeSocialLogin(pack){const applied=applySessionHandoff(pack),confirmed=await fetchAuthUser();if(confirmed||applied)return confirmed;return null}return{user,isAuthenticated,token,getToken:()=>token.value,register,login,logout,fetchAuthUser,completeSocialLogin,checkAuthentication,refreshToken,getRefreshToken:()=>refreshToken.value,refreshSession,authFetch}}export function authGuard(options={}){const guest=options.guest??!1,{isAuthenticated}=useAuth();if(guest){if(isAuthenticated.value)window.location.replace("/");return}if(!isAuthenticated.value)window.location.replace("/login")}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Ref } from '@stacksjs/stx';
|
|
2
|
+
import type { SessionHandoffPack } from '@stacksjs/composables';
|
|
2
3
|
export declare function isGeneralError(error: ResponseError): error is { error: string };
|
|
3
4
|
export declare interface ValidationError {
|
|
4
5
|
[key: string]: {
|
|
@@ -57,10 +58,15 @@ export declare interface AuthComposable {
|
|
|
57
58
|
login: (user: AuthUser) => Promise<LoginResponse | LoginError>
|
|
58
59
|
register: (user: AuthUser) => Promise<RegisterResponse | RegisterError>
|
|
59
60
|
fetchAuthUser: () => Promise<UserData | null>
|
|
61
|
+
completeSocialLogin: (pack?: SessionHandoffPack | null) => Promise<UserData | null>
|
|
60
62
|
checkAuthentication: () => Promise<boolean>
|
|
61
63
|
logout: () => void
|
|
62
64
|
getToken: () => string | null
|
|
63
65
|
token: Ref<string | null>
|
|
66
|
+
refreshToken: Ref<string | null>
|
|
67
|
+
getRefreshToken: () => string | null
|
|
68
|
+
refreshSession: () => Promise<boolean>
|
|
69
|
+
authFetch: (input: string, init?: RequestInit) => Promise<globalThis.Response>
|
|
64
70
|
}
|
|
65
71
|
export type ResponseError = {
|
|
66
72
|
error: string
|
package/dist/utils/vendors.d.ts
CHANGED
|
@@ -16,9 +16,18 @@ export {
|
|
|
16
16
|
useDark,
|
|
17
17
|
useDateFormat,
|
|
18
18
|
useFetch,
|
|
19
|
+
// Reachable from a client script at last (stacksjs/stacks#1940, stx#1843).
|
|
20
|
+
// It shipped with per-field validation, `inputProps()` carrying
|
|
21
|
+
// aria-invalid / aria-describedby, isSubmitting, touched/dirty and
|
|
22
|
+
// setErrors for 422 mapping — and appeared in neither auto-import surface,
|
|
23
|
+
// so there was no way to find it without already knowing the package path.
|
|
24
|
+
// Two production apps hand-rolled N signals plus manual error flags and
|
|
25
|
+
// manual focus per form rather than use it.
|
|
26
|
+
useForm,
|
|
19
27
|
useNow,
|
|
20
28
|
useOnline,
|
|
21
29
|
usePreferredDark,
|
|
30
|
+
useScrollLock,
|
|
22
31
|
useStorage,
|
|
23
32
|
useToggle,
|
|
24
33
|
} from '@stacksjs/composables';
|
package/dist/utils/vendors.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{useDark,useDateFormat,useFetch,useNow,useOnline,usePreferredDark,useStorage,useToggle}from"@stacksjs/composables";export{useHead as createHead,useHead as Head,renderHead as renderHeadToString}from"@stacksjs/stx";const DECIMAL_UNITS=["B","KB","MB","GB","TB","PB","EB","ZB","YB"],BINARY_UNITS=["B","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"];export function readableSize(bytes,options={}){const{precision=1,binary=!1,space=!0,locale="en-US",minimumFractionDigits=0,maximumFractionDigits=precision}=options;if(!Number.isFinite(bytes))throw TypeError(`Expected a finite number, got ${typeof bytes}: ${bytes}`);const isNegative=bytes<0,prefix=isNegative?"-":"";if(isNegative)bytes=-bytes;if(bytes<1){const numberString=bytes.toLocaleString(locale,{minimumFractionDigits,maximumFractionDigits});return`${prefix}${numberString}${space?" ":""}B`}const base=binary?1024:1000,units=binary?BINARY_UNITS:DECIMAL_UNITS,exponent=Math.min(Math.floor(Math.log(bytes)/Math.log(base)),units.length-1),numberString=(bytes/base**exponent).toLocaleString(locale,{minimumFractionDigits,maximumFractionDigits});return`${prefix}${numberString}${space?" ":""}${units[exponent]}`}
|
|
1
|
+
export{useDark,useDateFormat,useFetch,useForm,useNow,useOnline,usePreferredDark,useScrollLock,useStorage,useToggle}from"@stacksjs/composables";export{useHead as createHead,useHead as Head,renderHead as renderHeadToString}from"@stacksjs/stx";const DECIMAL_UNITS=["B","KB","MB","GB","TB","PB","EB","ZB","YB"],BINARY_UNITS=["B","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"];export function readableSize(bytes,options={}){const{precision=1,binary=!1,space=!0,locale="en-US",minimumFractionDigits=0,maximumFractionDigits=precision}=options;if(!Number.isFinite(bytes))throw TypeError(`Expected a finite number, got ${typeof bytes}: ${bytes}`);const isNegative=bytes<0,prefix=isNegative?"-":"";if(isNegative)bytes=-bytes;if(bytes<1){const numberString=bytes.toLocaleString(locale,{minimumFractionDigits,maximumFractionDigits});return`${prefix}${numberString}${space?" ":""}B`}const base=binary?1024:1000,units=binary?BINARY_UNITS:DECIMAL_UNITS,exponent=Math.min(Math.floor(Math.log(bytes)/Math.log(base)),units.length-1),numberString=(bytes/base**exponent).toLocaleString(locale,{minimumFractionDigits,maximumFractionDigits});return`${prefix}${numberString}${space?" ":""}${units[exponent]}`}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/browser",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.296",
|
|
6
6
|
"description": "Stacks core frontend/browser functionalities.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"prepublishOnly": "bun run build"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@stacksjs/composables": "0.70.
|
|
66
|
+
"@stacksjs/composables": "0.70.296",
|
|
67
67
|
"@stacksjs/stx": "^0.2.148",
|
|
68
68
|
"bun-query-builder": "^0.2.22"
|
|
69
69
|
},
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"better-dx": "^0.2.17",
|
|
80
|
-
"@stacksjs/utils": "0.70.
|
|
80
|
+
"@stacksjs/utils": "0.70.296",
|
|
81
81
|
"@stripe/stripe-js": "^9.10.0"
|
|
82
82
|
}
|
|
83
83
|
}
|