diff --git a/src/puter-js/src/lib/PuterClient.js b/src/puter-js/src/lib/PuterClient.js index 5367ad494..e1bdb6aab 100644 --- a/src/puter-js/src/lib/PuterClient.js +++ b/src/puter-js/src/lib/PuterClient.js @@ -17,7 +17,74 @@ * along with this program. If not, see . */ -import { createDeferred, resolveReauth } from './utils.js'; +const createDeferred = () => { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +}; + +/** + * Shared 401 reauth policy for a parsed response body. Drives the env-specific + * reauth flow on the Puter class and tells the caller what to do next, so the + * fetch replacement (`fetchUrl`) and the generic XHR path (`handle_resp` in + * utils.js) apply the exact same policy. The driver-call handler (`driverCall_`) + * keeps its own replay because it must preserve streaming/transform semantics + * on retry. + * + * Recognised backend signals: + * - `reauth_required` (v2 `authProbe`): legacy v1 tokens, revoked sessions, + * and expired sessions beyond the silent re-mint window. + * - `token_auth_failed` (legacy `APIError.create('token_auth_failed')`): + * token no longer valid, prompt re-login (web env only). + * + * @param {Object} resp - The parsed response body. + * @returns {Promise<{action: 'replay'}|{action: 'reject', error: Object}|null>} + * `replay` when the caller should re-issue the request once with the fresh + * token, `reject` with the error to surface, or `null` when this is not a + * reauth-recoverable 401 and the caller should handle it normally. + */ +async function resolveReauth (resp) { + if ( resp?.code === 'reauth_required' ) { + try { + await puter.triggerReauth({ + reason: resp.reason, + auth_id: resp.auth_id, + }); + return { action: 'replay' }; + } catch ( e ) { + return { + action: 'reject', + error: { + status: 401, + code: 'reauth_required', + reason: resp.reason, + auth_id: resp.auth_id, + message: e?.message || 'Reauthentication required', + }, + }; + } + } + if ( resp?.code === 'token_auth_failed' && puter.env === 'web' ) { + try { + puter.resetAuthToken(); + await puter.ui.authenticateWithPuter(); + } catch (e) { + return { + action: 'reject', + error: { + error: { + code: 'auth_canceled', message: 'Authentication canceled', + }, + }, + }; + } + } + return null; +} /** * The single HTTP core for puter.js. `fetchUrl` is an XHR-based replacement for @@ -277,4 +344,4 @@ function fetchUrl (url, opts = {}) { }); } -export { fetchUrl }; +export { fetchUrl, resolveReauth }; diff --git a/src/puter-js/src/lib/utils.js b/src/puter-js/src/lib/utils.js index 766345090..6f7b7d721 100644 --- a/src/puter-js/src/lib/utils.js +++ b/src/puter-js/src/lib/utils.js @@ -1,4 +1,5 @@ import { FileReaderPoly } from './polyfills/fileReaderPoly.js'; +import { resolveReauth } from './PuterClient.js'; import { showUsageLimitDialog } from '../modules/UsageLimitDialog.js'; import { showEmailConfirmationDialog } from '../modules/EmailConfirmationDialog.js'; @@ -155,64 +156,6 @@ function replayXhrAfterReauth (response, success_cb, error_cb, resolve_func, rej return true; } -/** - * Shared 401 reauth policy for a parsed response body. Drives the env-specific - * reauth flow on the Puter class and tells the caller what to do next, so the - * generic XHR path (`handle_resp`) and the fetch replacement (`fetchUrl`) apply - * the exact same policy. The driver-call handler (`driverCall_`) keeps its own - * replay because it must preserve streaming/transform semantics on retry. - * - * Recognised backend signals: - * - `reauth_required` (v2 `authProbe`): legacy v1 tokens, revoked sessions, - * and expired sessions beyond the silent re-mint window. - * - `token_auth_failed` (legacy `APIError.create('token_auth_failed')`): - * token no longer valid, prompt re-login (web env only). - * - * @param {Object} resp - The parsed response body. - * @returns {Promise<{action: 'replay'}|{action: 'reject', error: Object}|null>} - * `replay` when the caller should re-issue the request once with the fresh - * token, `reject` with the error to surface, or `null` when this is not a - * reauth-recoverable 401 and the caller should handle it normally. - */ -async function resolveReauth (resp) { - if ( resp?.code === 'reauth_required' ) { - try { - await puter.triggerReauth({ - reason: resp.reason, - auth_id: resp.auth_id, - }); - return { action: 'replay' }; - } catch ( e ) { - return { - action: 'reject', - error: { - status: 401, - code: 'reauth_required', - reason: resp.reason, - auth_id: resp.auth_id, - message: e?.message || 'Reauthentication required', - }, - }; - } - } - if ( resp?.code === 'token_auth_failed' && puter.env === 'web' ) { - try { - puter.resetAuthToken(); - await puter.ui.authenticateWithPuter(); - } catch (e) { - return { - action: 'reject', - error: { - error: { - code: 'auth_canceled', message: 'Authentication canceled', - }, - }, - }; - } - } - return null; -} - /** * Handles an HTTP response by invoking appropriate callback functions and resolving or rejecting a promise. * @@ -811,5 +754,5 @@ const isVideoInput = (url) => { }; export { - arrayBufferToDataUri, blob_to_url, blobToDataUri, createDeferred, driverCall, handle_error, handle_resp, initXhr, isVideoInput, make_driver_method, parseResponse, resolveReauth, setupXhrEventHandlers, uuidv4, + arrayBufferToDataUri, blob_to_url, blobToDataUri, driverCall, handle_error, handle_resp, initXhr, isVideoInput, make_driver_method, parseResponse, setupXhrEventHandlers, uuidv4, };