import { APIError } from "./APIError.ts"; import { S3S_NAMESPACE } from "./constant.ts"; import { base64, uuid } from "../deps.ts"; import { Env } from "./env.ts"; export function urlBase64Encode(data: ArrayBuffer) { return base64.encode(data) .replaceAll("+", "-") .replaceAll("/", "_") .replaceAll("=", ""); } export function urlBase64Decode(data: string) { return base64.decode( data .replaceAll("_", "/") .replaceAll("-", "+"), ); } type PromiseReturnType = T extends () => Promise ? R : never; export async function retry Promise>( f: F, times = 2, ): Promise> { let lastError; for (let i = 0; i < times; i++) { try { return await f() as PromiseReturnType; } catch (e) { lastError = e; } } throw lastError; } const GLOBAL_CACHE: Record = {}; export function cache Promise>( f: F, { key = f.name, expireIn = 3600 }: { key?: string; expireIn?: number } = {}, ): () => Promise> { return async () => { const cached = GLOBAL_CACHE[key]; if (cached && cached.ts + expireIn * 1000 > Date.now()) { return cached.value as PromiseReturnType; } const value = await f(); GLOBAL_CACHE[key] = { ts: Date.now(), value, }; return value as PromiseReturnType; }; } export async function showError(env: Env, p: Promise): Promise { try { return await p; } catch (e) { if (e instanceof APIError) { env.logger.error( `\n\nAPIError: ${e.message}`, "\nResponse: ", e.response, "\nBody: ", e.json, ); } else { env.logger.error(e); } throw e; } } /** * @param id id of VsHistoryDetail or CoopHistoryDetail * @param namespace uuid namespace * @returns */ export function gameId( id: string, namespace = S3S_NAMESPACE, ): Promise { const fullId = base64.decode(id); const tsUuid = fullId.slice(fullId.length - 52, fullId.length); return uuid.v5.generate(namespace, tsUuid); } /** * @param id VsHistoryDetail id or CoopHistoryDetail id */ export function parseHistoryDetailId(id: string) { const plainText = new TextDecoder().decode(base64.decode(id)); const vsRE = /VsHistoryDetail-([a-z0-9-]+):(\w+):(\d{8}T\d{6})_([0-9a-f-]{36})/; const coopRE = /CoopHistoryDetail-([a-z0-9-]+):(\d{8}T\d{6})_([0-9a-f-]{36})/; if (vsRE.test(plainText)) { const [, uid, listType, timestamp, uuid] = plainText.match(vsRE)!; return { type: "VsHistoryDetail", uid, listType, timestamp, uuid, }; } else if (coopRE.test(plainText)) { const [, uid, timestamp, uuid] = plainText.match(coopRE)!; return { type: "CoopHistoryDetail", uid, timestamp, uuid, }; } else { throw new Error(`Invalid ID: ${plainText}`); } } export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));