New source found from dndbeyond.com

This commit is contained in:
2026-07-08 01:00:09 -07:00
parent 9a983a6d7b
commit dcefa0d2f2
2865 changed files with 222467 additions and 49053 deletions
+12
View File
@@ -0,0 +1,12 @@
import { createCache } from "./lib/createCache.js";
import { makeAuth } from "./lib/makeAuth.js";
import { mutexifyAuth } from "./lib/mutexifyAuth.js";
import { version } from "./lib/version.js";
const STORAGE_KEY = "_kobold-token";
const key = (input) => Promise.resolve(`${STORAGE_KEY}-${version(input)}`);
const cache = createCache(globalThis.localStorage);
const auth = makeAuth(cache, key);
export const getAuth = mutexifyAuth(auth);
export const getUser = (input, init) => getAuth(input, init).then(({ token }) => token?.payload ?? null);
export const getJwt = (input, init) => getAuth(input, init).then(({ jwt }) => jwt);
//# sourceMappingURL=browserAuth.js.map
+30
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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