@shopify/shop-minis-react
Version:
React component library for Shopify Shop Minis with Tailwind CSS v4 support (source-only, requires TypeScript)
96 lines (79 loc) • 2.94 kB
text/typescript
import {useCallback, useRef} from 'react'
import {
GeneratedTokenData,
UserTokenGenerateUserErrors,
} from '@shopify/shop-minis-platform'
import {useHandleAction} from '../../internal/useHandleAction'
import {useShopActions} from '../../internal/useShopActions'
interface UseGenerateUserTokenReturns {
/**
* Generates a temporary token for the user.
* Tokens are cached in memory and reused if still valid (with a 5-minute expiry buffer).
* A new token is automatically generated when the cached token is expired or missing.
*/
generateUserToken: () => Promise<{
data: GeneratedTokenData
userErrors?: UserTokenGenerateUserErrors[]
}>
}
interface CachedTokenResponse {
data: GeneratedTokenData
userErrors?: UserTokenGenerateUserErrors[]
}
export function useGenerateUserToken(): UseGenerateUserTokenReturns {
const {generateUserToken: generateUserTokenAction} = useShopActions()
const wrappedGenerateToken = useHandleAction(generateUserTokenAction)
const cachedResponse = useRef<CachedTokenResponse | null>(null)
const pendingRequest = useRef<Promise<CachedTokenResponse> | null>(null)
const isTokenValid = useCallback(
(response: CachedTokenResponse | null): boolean => {
if (!response?.data?.token || !response.data.expiresAt) {
return false
}
try {
const expiryTime = new Date(response.data.expiresAt).getTime()
const now = Date.now()
// 5 minutes buffer to ensure token doesn't expire mid-request
const bufferTime = 5 * 60 * 1000
return expiryTime - bufferTime > now
} catch {
// If date parsing fails, consider token invalid
return false
}
},
[]
)
const generateUserToken =
useCallback(async (): Promise<CachedTokenResponse> => {
// Check if cached token exists and is still valid
if (cachedResponse.current && isTokenValid(cachedResponse.current)) {
return cachedResponse.current
}
// If there's already a pending request, return the same promise
if (pendingRequest.current) {
return pendingRequest.current
}
// Create new request and store the promise
pendingRequest.current = (async () => {
try {
const response = await wrappedGenerateToken()
// Only cache if we got a valid token
if (response.data?.token && response.data?.expiresAt) {
cachedResponse.current = response
}
return response
} catch (error) {
// Clear cache on error to ensure fresh token on next attempt
cachedResponse.current = null
throw error
} finally {
// Clear pending request after completion (success or failure)
pendingRequest.current = null
}
})()
return pendingRequest.current
}, [wrappedGenerateToken, isTokenValid])
return {
generateUserToken,
}
}