Auth & Fetch
The player’s authFetch pipeline handles all network requests made by the player and its built-in plugins.
Bearer tokens are injected automatically, 401 responses trigger a single refresh attempt, and 403 responses propagate immediately.
Token storage and security
Tokens are never stored in localStorage or sessionStorage by the core.
The player holds the auth config object in memory only, in the options property on the player instance.
When you pass a factory function for bearerToken, the function is called on every authenticated request.
The token value is not cached between requests.
This means:
- Page reload clears all tokens, so your app is responsible for restoring auth on mount.
- The core never reads from or writes to
localStorageas part of auth. AnylocalStorageinteraction is your app’s responsibility. - A
bearerTokenfactory that returns a stale token is the only way to accidentally replay an expired token, so use a factory that always fetches the current token from your auth SDK.
// Correct: factory always returns the live token
player.setup({
auth: {
bearerToken: () => myAuth.getAccessToken(), // called on every request
refreshOnUnauthenticated: async () => {
await myAuth.refresh(); // resolves when refresh is complete; bearerToken() is re-read automatically
},
},
});
// Risky: static string baked in at setup time
player.setup({
auth: { bearerToken: 'eyJ...' }, // expires, never refreshes automatically
});
Configuration
player.setup({
auth: {
bearerToken: () => myAuth.getAccessToken(),
refreshOnUnauthenticated: async () => {
await myAuth.refresh(); // bearerToken() is re-read automatically after this resolves
},
signRequest: (request) => myHmac.sign(request), // optional, custom signing
headers: { 'X-App-Version': '2.0.0' }, // optional, static headers
},
});
All fields are optional. Without auth, requests are unauthenticated.
Auth config reference
bearerToken
bearerToken?: string | (() => string | Promise<string>)
Token or factory. A factory is called on every authenticated request, use a factory when the token can expire. A static string is applied as-is.
The token is injected as Authorization: Bearer <token>.
refreshOnUnauthenticated
refreshOnUnauthenticated?: () => Promise<void>
Called once when a request returns 401.
Invoke your auth SDK’s refresh logic here. After this resolves, the player re-reads bearerToken from the config and retries the failed request exactly once with the new value.
If refresh fails, auth:failed fires and the error propagates to the caller.
signRequest
signRequest?: (request: Request) => Request | Promise<Request>
Custom request signer, called after the bearer token is applied. Use for HMAC, API key rotation, or any custom signing scheme.
headers
headers?: Record<string, string | (() => string) | (() => Promise<string>)>
Headers appended to every authenticated request. Each value can be a static string, a sync getter, or an async getter — same forms as bearerToken.
401 vs 403 handling
| Response | Behavior |
|---|---|
| 401 | refreshOnUnauthenticated called once → request retried. If refresh fails → auth:failed event + error propagates. |
| 403 | Error propagates immediately. refreshOnUnauthenticated is never called for 403. |
Do not lump 401 and 403 together in your error handling. A 403 is an access policy decision, retrying with a refreshed token will not help. Check the server’s entitlement configuration instead.
Auth events:
player.on('auth:refreshed', ({ tokenAcquiredAt }) => {
console.log('Token refreshed at', tokenAcquiredAt);
});
player.on('auth:failed', ({ error }) => {
console.error('Token refresh failed:', error);
});
Retry policy
authFetch retries failed requests using a built-in per-code policy, DEFAULT_RETRY_POLICY — an IRetryPolicy (a map of error-code matcher to RetryConfig). The default is conservative: most codes do not retry, transient ones (timeouts, 5xx, fragment loads) retry a few times with backoff, and 403 never retries.
There is no setup-level retry option. To override retry behaviour for a specific request, pass a RetryConfig as the retry option on this.fetch() inside a plugin:
// inside a plugin's use()
await this.fetch(url, {
retry: { attempts: 5, backoff: 'exponential', baseMs: 200, maxMs: 10_000 },
});
Auth refresh retries are separate from the general retry policy, so refreshOnUnauthenticated is called exactly once, not retried.
Inside plugins: this.fetch
Plugins should use this.fetch<T>(url, options?) instead of the global fetch.
The <T> parameter types the parsed response, no casts needed.
The options’ responseType ('text' | 'json' | 'arrayBuffer') controls how the body is decoded; pass a custom parser to transform a text body.
The player’s auth config and retry policy are applied automatically:
class MyPlugin extends Plugin<...> {
async use(): Promise<void> {
const data = await this.fetch<MyDataShape>(this.opts.endpoint, {
method: 'GET',
responseType: 'json',
});
this.applyData(data);
}
}
If you need the raw Request object before it is dispatched (for example, to add a signature), use signRequest in the auth config rather than building it manually in the plugin.
Updating auth at runtime
Replace auth config after setup, for example, when a long-lived token is refreshed by an external mechanism:
player.auth({
bearerToken: newToken,
});
This merges the provided partial config with the existing auth config. Only the fields you provide are updated.