"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAlgorithm = exports.validateExp = exports.parseResponseAPIVersion = exports.getCodeChallengeAndMethod = exports.generatePKCEChallenge = exports.generatePKCEVerifier = exports.retryable = exports.sleep = exports.decodeJWT = exports.Deferred = exports.removeItemAsync = exports.getItemAsync = exports.setItemAsync = exports.looksLikeFetchResponse = exports.resolveFetch = exports.parseParametersFromURL = exports.supportsLocalStorage = exports.isBrowser = exports.uuid = exports.expiresAt = void 0; const constants_1 = require("./constants"); const errors_1 = require("./errors"); const base64url_1 = require("./base64url"); function expiresAt(expiresIn) { const timeNow = Math.round(Date.now() / 1000); return timeNow + expiresIn; } exports.expiresAt = expiresAt; function uuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { const r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } exports.uuid = uuid; const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined'; exports.isBrowser = isBrowser; const localStorageWriteTests = { tested: false, writable: false, }; /** * Checks whether localStorage is supported on this browser. */ const supportsLocalStorage = () => { if (!(0, exports.isBrowser)()) { return false; } try { if (typeof globalThis.localStorage !== 'object') { return false; } } catch (e) { // DOM exception when accessing `localStorage` return false; } if (localStorageWriteTests.tested) { return localStorageWriteTests.writable; } const randomKey = `lswt-${Math.random()}${Math.random()}`; try { globalThis.localStorage.setItem(randomKey, randomKey); globalThis.localStorage.removeItem(randomKey); localStorageWriteTests.tested = true; localStorageWriteTests.writable = true; } catch (e) { // localStorage can't be written to // https://www.chromium.org/for-testers/bug-reporting-guidelines/uncaught-securityerror-failed-to-read-the-localstorage-property-from-window-access-is-denied-for-this-document localStorageWriteTests.tested = true; localStorageWriteTests.writable = false; } return localStorageWriteTests.writable; }; exports.supportsLocalStorage = supportsLocalStorage; /** * Extracts parameters encoded in the URL both in the query and fragment. */ function parseParametersFromURL(href) { const result = {}; const url = new URL(href); if (url.hash && url.hash[0] === '#') { try { const hashSearchParams = new URLSearchParams(url.hash.substring(1)); hashSearchParams.forEach((value, key) => { result[key] = value; }); } catch (e) { // hash is not a query string } } // search parameters take precedence over hash parameters url.searchParams.forEach((value, key) => { result[key] = value; }); return result; } exports.parseParametersFromURL = parseParametersFromURL; const resolveFetch = (customFetch) => { let _fetch; if (customFetch) { _fetch = customFetch; } else if (typeof fetch === 'undefined') { _fetch = (...args) => Promise.resolve().then(() => __importStar(require('@supabase/node-fetch'))).then(({ default: fetch }) => fetch(...args)); } else { _fetch = fetch; } return (...args) => _fetch(...args); }; exports.resolveFetch = resolveFetch; const looksLikeFetchResponse = (maybeResponse) => { return (typeof maybeResponse === 'object' && maybeResponse !== null && 'status' in maybeResponse && 'ok' in maybeResponse && 'json' in maybeResponse && typeof maybeResponse.json === 'function'); }; exports.looksLikeFetchResponse = looksLikeFetchResponse; // Storage helpers const setItemAsync = async (storage, key, data) => { await storage.setItem(key, JSON.stringify(data)); }; exports.setItemAsync = setItemAsync; const getItemAsync = async (storage, key) => { const value = await storage.getItem(key); if (!value) { return null; } try { return JSON.parse(value); } catch (_a) { return value; } }; exports.getItemAsync = getItemAsync; const removeItemAsync = async (storage, key) => { await storage.removeItem(key); }; exports.removeItemAsync = removeItemAsync; /** * A deferred represents some asynchronous work that is not yet finished, which * may or may not culminate in a value. * Taken from: https://github.com/mike-north/types/blob/master/src/async.ts */ class Deferred { constructor() { // eslint-disable-next-line @typescript-eslint/no-extra-semi ; this.promise = new Deferred.promiseConstructor((res, rej) => { // eslint-disable-next-line @typescript-eslint/no-extra-semi ; this.resolve = res; this.reject = rej; }); } } exports.Deferred = Deferred; Deferred.promiseConstructor = Promise; function decodeJWT(token) { const parts = token.split('.'); if (parts.length !== 3) { throw new errors_1.AuthInvalidJwtError('Invalid JWT structure'); } // Regex checks for base64url format for (let i = 0; i < parts.length; i++) { if (!constants_1.BASE64URL_REGEX.test(parts[i])) { throw new errors_1.AuthInvalidJwtError('JWT not in base64url format'); } } const data = { // using base64url lib header: JSON.parse((0, base64url_1.stringFromBase64URL)(parts[0])), payload: JSON.parse((0, base64url_1.stringFromBase64URL)(parts[1])), signature: (0, base64url_1.base64UrlToUint8Array)(parts[2]), raw: { header: parts[0], payload: parts[1], }, }; return data; } exports.decodeJWT = decodeJWT; /** * Creates a promise that resolves to null after some time. */ async function sleep(time) { return await new Promise((accept) => { setTimeout(() => accept(null), time); }); } exports.sleep = sleep; /** * Converts the provided async function into a retryable function. Each result * or thrown error is sent to the isRetryable function which should return true * if the function should run again. */ function retryable(fn, isRetryable) { const promise = new Promise((accept, reject) => { // eslint-disable-next-line @typescript-eslint/no-extra-semi ; (async () => { for (let attempt = 0; attempt < Infinity; attempt++) { try { const result = await fn(attempt); if (!isRetryable(attempt, null, result)) { accept(result); return; } } catch (e) { if (!isRetryable(attempt, e)) { reject(e); return; } } } })(); }); return promise; } exports.retryable = retryable; function dec2hex(dec) { return ('0' + dec.toString(16)).substr(-2); } // Functions below taken from: https://stackoverflow.com/questions/63309409/creating-a-code-verifier-and-challenge-for-pkce-auth-on-spotify-api-in-reactjs function generatePKCEVerifier() { const verifierLength = 56; const array = new Uint32Array(verifierLength); if (typeof crypto === 'undefined') { const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; const charSetLen = charSet.length; let verifier = ''; for (let i = 0; i < verifierLength; i++) { verifier += charSet.charAt(Math.floor(Math.random() * charSetLen)); } return verifier; } crypto.getRandomValues(array); return Array.from(array, dec2hex).join(''); } exports.generatePKCEVerifier = generatePKCEVerifier; async function sha256(randomString) { const encoder = new TextEncoder(); const encodedData = encoder.encode(randomString); const hash = await crypto.subtle.digest('SHA-256', encodedData); const bytes = new Uint8Array(hash); return Array.from(bytes) .map((c) => String.fromCharCode(c)) .join(''); } async function generatePKCEChallenge(verifier) { const hasCryptoSupport = typeof crypto !== 'undefined' && typeof crypto.subtle !== 'undefined' && typeof TextEncoder !== 'undefined'; if (!hasCryptoSupport) { console.warn('WebCrypto API is not supported. Code challenge method will default to use plain instead of sha256.'); return verifier; } const hashed = await sha256(verifier); return btoa(hashed).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } exports.generatePKCEChallenge = generatePKCEChallenge; async function getCodeChallengeAndMethod(storage, storageKey, isPasswordRecovery = false) { const codeVerifier = generatePKCEVerifier(); let storedCodeVerifier = codeVerifier; if (isPasswordRecovery) { storedCodeVerifier += '/PASSWORD_RECOVERY'; } await (0, exports.setItemAsync)(storage, `${storageKey}-code-verifier`, storedCodeVerifier); const codeChallenge = await generatePKCEChallenge(codeVerifier); const codeChallengeMethod = codeVerifier === codeChallenge ? 'plain' : 's256'; return [codeChallenge, codeChallengeMethod]; } exports.getCodeChallengeAndMethod = getCodeChallengeAndMethod; /** Parses the API version which is 2YYY-MM-DD. */ const API_VERSION_REGEX = /^2[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-9]|3[0-1])$/i; function parseResponseAPIVersion(response) { const apiVersion = response.headers.get(constants_1.API_VERSION_HEADER_NAME); if (!apiVersion) { return null; } if (!apiVersion.match(API_VERSION_REGEX)) { return null; } try { const date = new Date(`${apiVersion}T00:00:00.0Z`); return date; } catch (e) { return null; } } exports.parseResponseAPIVersion = parseResponseAPIVersion; function validateExp(exp) { if (!exp) { throw new Error('Missing exp claim'); } const timeNow = Math.floor(Date.now() / 1000); if (exp <= timeNow) { throw new Error('JWT has expired'); } } exports.validateExp = validateExp; function getAlgorithm(alg) { switch (alg) { case 'RS256': return { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256' }, }; case 'ES256': return { name: 'ECDSA', namedCurve: 'P-256', hash: { name: 'SHA-256' }, }; default: throw new Error('Invalid alg claim'); } } exports.getAlgorithm = getAlgorithm; //# sourceMappingURL=helpers.js.map