New source found from dndbeyond.com
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
||||
export class ResponseError extends Error {
|
||||
#status;
|
||||
get status() {
|
||||
return this.#status;
|
||||
}
|
||||
#statusText;
|
||||
get statusText() {
|
||||
return this.#statusText;
|
||||
}
|
||||
#url;
|
||||
get url() {
|
||||
return this.#url;
|
||||
}
|
||||
constructor(message, { status, statusText, url, }) {
|
||||
super(message);
|
||||
this.#status = status;
|
||||
this.#statusText = statusText;
|
||||
this.#url = url;
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.constructor.name,
|
||||
message: this.message,
|
||||
status: this.#status,
|
||||
statusText: this.#statusText,
|
||||
url: this.#url,
|
||||
};
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=ResponseError.js.map
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
const safeParse = (json) => {
|
||||
try {
|
||||
return JSON.parse(json ?? "null");
|
||||
}
|
||||
catch (e) { }
|
||||
};
|
||||
export const createCache = (storage) => {
|
||||
const jwtStore = new Map();
|
||||
const tidStore = new Map();
|
||||
const getJwt = (k) => {
|
||||
const now = Date.now();
|
||||
let { jwt, exp } = jwtStore.get(k) ?? {};
|
||||
if (jwt && exp && exp > now) {
|
||||
return jwt;
|
||||
}
|
||||
if (jwt) {
|
||||
jwtStore.delete(k);
|
||||
}
|
||||
({ jwt, exp } = safeParse(storage?.getItem(k)) ?? {});
|
||||
if (jwt && exp && exp > now) {
|
||||
jwtStore.set(k, { jwt, exp });
|
||||
return jwt;
|
||||
}
|
||||
if (jwt) {
|
||||
storage?.removeItem(k);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const setJwt = (k, jwt, exp) => {
|
||||
const now = Date.now();
|
||||
if (jwt === "" || exp < now) {
|
||||
return;
|
||||
}
|
||||
jwtStore.set(k, { jwt, exp });
|
||||
storage?.setItem(k, JSON.stringify({ jwt, exp }));
|
||||
clearTimeout(tidStore.get(k));
|
||||
tidStore.set(k, setTimeout(() => removeJwt(k), exp - now));
|
||||
};
|
||||
const removeJwt = (k) => {
|
||||
jwtStore.delete(k);
|
||||
clearTimeout(tidStore.get(k));
|
||||
tidStore.delete(k);
|
||||
storage?.removeItem(k);
|
||||
};
|
||||
const clear = () => {
|
||||
if (storage) {
|
||||
Array.from(jwtStore.keys()).forEach((k) => storage?.removeItem(k));
|
||||
}
|
||||
jwtStore.clear();
|
||||
Array.from(tidStore.values()).forEach(clearTimeout);
|
||||
tidStore.clear();
|
||||
};
|
||||
return {
|
||||
getJwt,
|
||||
setJwt,
|
||||
clear,
|
||||
};
|
||||
};
|
||||
//# sourceMappingURL=createCache.js.map
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { ResponseError } from "./ResponseError.js";
|
||||
export const fetchJwt = (input, init) => fetch(input, init).then((response) => response
|
||||
.json()
|
||||
.catch((e) => ({
|
||||
message: `${e.name} - ${e.message}`,
|
||||
}))
|
||||
.then(({ message, token: jwt }) => {
|
||||
if (!response.ok || (message && !jwt)) {
|
||||
throw new ResponseError(message, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
url: response.url,
|
||||
});
|
||||
}
|
||||
return jwt;
|
||||
}));
|
||||
//# sourceMappingURL=fetchJwt.js.map
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { fetchJwt } from "./fetchJwt.js";
|
||||
import { parseJwt } from "./parseJwt.js";
|
||||
const ms = (exp) => exp * 1_000;
|
||||
const nullAuth = () => ({ jwt: null, token: null });
|
||||
export const makeAuth = (cache, key) => async (input, { useCache = true, parseToken = true, ...init } = {}) => {
|
||||
let jwtSource = "cache";
|
||||
const k = await key(input, init);
|
||||
// cached jwt only lasts until jwt expires
|
||||
let jwt = useCache && cache.getJwt(k);
|
||||
if (jwt && !parseToken) {
|
||||
return { jwt, token: null };
|
||||
}
|
||||
if (!jwt) {
|
||||
// cache miss, fetch from network
|
||||
jwt = await fetchJwt(input, init);
|
||||
jwtSource = "network";
|
||||
}
|
||||
if (!jwt) {
|
||||
return nullAuth();
|
||||
}
|
||||
const token = parseJwt(jwt);
|
||||
if (!token) {
|
||||
return { jwt: null, token: null };
|
||||
}
|
||||
if (useCache && jwtSource === "network" && !token.isExpired) {
|
||||
const { exp } = token.payload;
|
||||
cache.setJwt(k, jwt, ms(exp));
|
||||
}
|
||||
return { jwt, token };
|
||||
};
|
||||
//# sourceMappingURL=makeAuth.js.map
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
export const mutexifyAuth = (authFn) => {
|
||||
const promises = new Map();
|
||||
return (input, init) => {
|
||||
const k = `${input.href} ${JSON.stringify(init)}`;
|
||||
const p = promises.get(k) ?? authFn(input, init).finally(() => promises.delete(k));
|
||||
if (!promises.has(k)) {
|
||||
promises.set(k, p);
|
||||
}
|
||||
return p;
|
||||
};
|
||||
};
|
||||
//# sourceMappingURL=mutexifyAuth.js.map
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
const XML_SOAP = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims";
|
||||
const MICROSOFT = "http://schemas.microsoft.com/ws/2008/06/identity/claims";
|
||||
const DND_BEYOND = "http://schemas.dndbeyond.com/ws/2019/08/identity/claims";
|
||||
const KEYS = {
|
||||
[`${XML_SOAP}/nameidentifier`]: "id",
|
||||
[`${XML_SOAP}/name`]: "name",
|
||||
[`${MICROSOFT}/role`]: "roles",
|
||||
[`${XML_SOAP}/emailaddress`]: "email",
|
||||
[`${DND_BEYOND}/subscriber`]: "isSubscriber",
|
||||
[`${DND_BEYOND}/subscriptiontier`]: "subscriptionTier",
|
||||
};
|
||||
const VALUES = {
|
||||
id: Number,
|
||||
roles: (roles) => [roles].flat(),
|
||||
isSubscriber: (isSubscriber) => isSubscriber === "True",
|
||||
};
|
||||
const identity = (x) => x;
|
||||
function mapKeys(key, value) {
|
||||
const k = KEYS[key] ?? key;
|
||||
const v = (VALUES[k] ?? identity)(value);
|
||||
if (k === key) {
|
||||
return v;
|
||||
}
|
||||
this[k] = v;
|
||||
}
|
||||
const safeParse = (text, reviver) => {
|
||||
try {
|
||||
return JSON.parse(text, reviver);
|
||||
}
|
||||
catch (e) { }
|
||||
};
|
||||
const regExp = (pattern, flags) => new RegExp(pattern, flags);
|
||||
const REPLACE = {
|
||||
["-"]: "+",
|
||||
["_"]: "/",
|
||||
};
|
||||
const rx = regExp(`[${Object.keys(REPLACE).join("")}]`, "g");
|
||||
const replacer = (m) => REPLACE[m];
|
||||
const decode = (s) => s.replace(rx, replacer);
|
||||
const safeDecode = (s) => {
|
||||
try {
|
||||
return atob(decode(s));
|
||||
}
|
||||
catch (e) { }
|
||||
};
|
||||
const isUnknownRecord = (x) => !!x && typeof x === "object";
|
||||
const isTokenHeader = (h) => isUnknownRecord(h) && "alg" in h && !!h.alg;
|
||||
const currentSeconds = () => Math.floor(Date.now() / 1_000);
|
||||
const isTokenPayload = (p) => isUnknownRecord(p) &&
|
||||
typeof p.id === "number" &&
|
||||
p.id > 0 &&
|
||||
typeof p.exp === "number";
|
||||
const isTokenPayloadExpired = (p) => isUnknownRecord(p) && typeof p.exp === "number" && p.exp <= currentSeconds();
|
||||
const isString = (x) => typeof x === "string";
|
||||
const isJwtParts = (xs) => xs?.length === 3 && xs.every(isString);
|
||||
const parseToken = ([h, p]) => {
|
||||
const header = safeParse(safeDecode(h) ?? "{}");
|
||||
const payload = safeParse(safeDecode(p) ?? p, mapKeys);
|
||||
return isTokenHeader(header) && isTokenPayload(payload)
|
||||
? { header, payload, isExpired: isTokenPayloadExpired(payload) }
|
||||
: null;
|
||||
};
|
||||
const parseJwtParts = (ps) => isJwtParts(ps) ? parseToken(ps) : null;
|
||||
export const parseJwt = (jwt) => parseJwtParts(jwt?.split("."));
|
||||
//# sourceMappingURL=parseJwt.js.map
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Pattern for extracting version from URL (i.e. /v1/ or /v2/)
|
||||
*/
|
||||
const VERSION_PATTERN = /\/v([1-9]*)(?:\/|$)/;
|
||||
const pipe = (fn, ...fns) => (...args) => fns.reduce((v, f) => f(v), fn(...args));
|
||||
const getHref = (url) => url?.href ?? "";
|
||||
const matchVersion = (href) => href.match(VERSION_PATTERN)?.[1] ?? "0";
|
||||
const parseNumber = (s) => parseInt(s, 10) || 0;
|
||||
/**
|
||||
* Retrieves the token version from a given URL
|
||||
*
|
||||
* @param {URL | undefined} url
|
||||
* @returns {number} token version if decipherable, or 0
|
||||
*/
|
||||
export const version = pipe(getHref, matchVersion, parseNumber);
|
||||
//# sourceMappingURL=version.js.map
|
||||
Reference in New Issue
Block a user