@epic-web/workshop-utils 6.32.3 â 6.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/db.server.d.ts +56 -0
- package/dist/esm/db.server.d.ts.map +1 -1
- package/dist/esm/db.server.js +1 -1
- package/dist/esm/db.server.js.map +1 -1
- package/dist/esm/epic-api.server.d.ts.map +1 -1
- package/dist/esm/epic-api.server.js +20 -19
- package/dist/esm/epic-api.server.js.map +1 -1
- package/dist/esm/logger.d.ts +5 -4
- package/dist/esm/logger.d.ts.map +1 -1
- package/dist/esm/logger.js +13 -5
- package/dist/esm/logger.js.map +1 -1
- package/dist/esm/utils.server.d.ts.map +1 -1
- package/dist/esm/utils.server.js +38 -5
- package/dist/esm/utils.server.js.map +1 -1
- package/package.json +1 -1
package/dist/esm/db.server.d.ts
CHANGED
|
@@ -381,6 +381,62 @@ declare const DataSchema: z.ZodObject<{
|
|
|
381
381
|
export declare function getClientId(): Promise<string>;
|
|
382
382
|
export declare function logout(): Promise<void>;
|
|
383
383
|
export declare function deleteDb(): Promise<null | undefined>;
|
|
384
|
+
export declare function readDb(): Promise<{
|
|
385
|
+
onboarding: {
|
|
386
|
+
tourVideosWatched: string[];
|
|
387
|
+
} & {
|
|
388
|
+
[k: string]: unknown;
|
|
389
|
+
};
|
|
390
|
+
preferences: {
|
|
391
|
+
player: {
|
|
392
|
+
subtitle: {
|
|
393
|
+
mode: "disabled" | "hidden" | "showing" | null;
|
|
394
|
+
id: string | null;
|
|
395
|
+
};
|
|
396
|
+
minResolution?: number | undefined;
|
|
397
|
+
maxResolution?: number | undefined;
|
|
398
|
+
volumeRate?: number | undefined;
|
|
399
|
+
playbackRate?: number | undefined;
|
|
400
|
+
autoplay?: boolean | undefined;
|
|
401
|
+
muted?: boolean | undefined;
|
|
402
|
+
theater?: boolean | undefined;
|
|
403
|
+
defaultView?: string | undefined;
|
|
404
|
+
activeSidebarTab?: number | undefined;
|
|
405
|
+
};
|
|
406
|
+
presence: {
|
|
407
|
+
optOut: boolean;
|
|
408
|
+
};
|
|
409
|
+
exerciseWarning: {
|
|
410
|
+
dismissed: boolean;
|
|
411
|
+
};
|
|
412
|
+
playground?: {
|
|
413
|
+
persist: boolean;
|
|
414
|
+
} | undefined;
|
|
415
|
+
fontSize?: number | undefined;
|
|
416
|
+
};
|
|
417
|
+
authInfo?: {
|
|
418
|
+
id: string;
|
|
419
|
+
email: string;
|
|
420
|
+
tokenSet: {
|
|
421
|
+
access_token: string;
|
|
422
|
+
token_type: string;
|
|
423
|
+
scope: string;
|
|
424
|
+
};
|
|
425
|
+
name?: string | null | undefined;
|
|
426
|
+
} | undefined;
|
|
427
|
+
authInfos?: Record<string, {
|
|
428
|
+
id: string;
|
|
429
|
+
email: string;
|
|
430
|
+
tokenSet: {
|
|
431
|
+
access_token: string;
|
|
432
|
+
token_type: string;
|
|
433
|
+
scope: string;
|
|
434
|
+
};
|
|
435
|
+
name?: string | null | undefined;
|
|
436
|
+
}> | undefined;
|
|
437
|
+
clientId?: string | undefined;
|
|
438
|
+
mutedNotifications?: string[] | undefined;
|
|
439
|
+
} | null>;
|
|
384
440
|
export declare function getAuthInfo(): Promise<{
|
|
385
441
|
id: string;
|
|
386
442
|
email: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.server.d.ts","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAA;AAKtB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAOvB,QAAA,MAAM,cAAc;;;;;;;;;;;;EAIlB,CAAA;AACF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAyBvB,CAAA;AAkBb,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCd,CAAA;AAEF,wBAAsB,WAAW,oBAOhC;AAED,wBAAsB,MAAM,kBAY3B;AAED,wBAAsB,QAAQ,8BAW7B;
|
|
1
|
+
{"version":3,"file":"db.server.d.ts","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAA;AAKtB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAOvB,QAAA,MAAM,cAAc;;;;;;;;;;;;EAIlB,CAAA;AACF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAyBvB,CAAA;AAkBb,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCd,CAAA;AAEF,wBAAsB,WAAW,oBAOhC;AAED,wBAAsB,MAAM,kBAY3B;AAED,wBAAsB,QAAQ,8BAW7B;AAED,wBAAsB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA6D3B;AAED,wBAAsB,WAAW;;;;;;;;;sBAuBhC;AAED,wBAAsB,eAAe,CAAC,EACrC,OAAO,EACP,UAAU,GACV,EAAE;IACF,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;;;;;;;;;GAeA;AAED,wBAAsB,WAAW,CAAC,EACjC,EAAE,EACF,QAAQ,EACR,KAA6B,EAC7B,IAAI,GACJ,EAAE;IACF,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC,CAAA;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;;;;;;;;;GAgBA;AAED,wBAAsB,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;UAGnC;AAED,wBAAsB,cAAc,CACnC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC,aAAa,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;GAwBtD;AAED,wBAAsB,qBAAqB,sBAG1C;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,qBAWhD;AAED,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,+BAQvE;AAED,wBAAsB,qBAAqB,2BAG1C;AAED,wBAAsB,kBAAkB;;;;WAGvC;AAED,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM;;GAchE;AAED,wBAAsB,4BAA4B,CAAC,QAAQ,EAAE,MAAM;;GAYlE;AAED,wBAAsB,6BAA6B,CAClD,gBAAgB,EAAE,MAAM,EAAE,oBAK1B"}
|
package/dist/esm/db.server.js
CHANGED
|
@@ -119,7 +119,7 @@ export async function deleteDb() {
|
|
|
119
119
|
console.error(`Error deleting the database`, error);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
async function readDb() {
|
|
122
|
+
export async function readDb() {
|
|
123
123
|
if (process.env.EPICSHOP_DEPLOYED)
|
|
124
124
|
return null;
|
|
125
125
|
const maxRetries = 3;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.server.js","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"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","sourcesContent":["import './init-env.js'\n\nimport { createId as cuid } from '@paralleldrive/cuid2'\nimport fsExtra from 'fs-extra'\nimport { redirect } from 'react-router'\nimport { z } from 'zod'\nimport { getWorkshopConfig } from './config.server.js'\nimport { saveJSON, loadJSON, migrateLegacyData } from './data-storage.server.js'\n\n// Attempt migration from legacy ~/.epicshop\nawait migrateLegacyData().catch(() => {})\n\nconst TokenSetSchema = z.object({\n\taccess_token: z.string(),\n\ttoken_type: z.string(),\n\tscope: z.string(),\n})\nexport const PlayerPreferencesSchema = z\n\t.object({\n\t\tminResolution: z.number().optional(),\n\t\tmaxResolution: z.number().optional(),\n\t\tvolumeRate: z.number().optional(),\n\t\tplaybackRate: z.number().optional(),\n\t\tautoplay: z.boolean().optional(),\n\t\tsubtitle: z\n\t\t\t.object({\n\t\t\t\tid: z.string().nullable().default(null),\n\t\t\t\tmode: z\n\t\t\t\t\t.literal('disabled')\n\t\t\t\t\t.or(z.literal('hidden'))\n\t\t\t\t\t.or(z.literal('showing'))\n\t\t\t\t\t.nullable()\n\t\t\t\t\t.default('disabled'),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.default({}),\n\t\tmuted: z.boolean().optional(),\n\t\ttheater: z.boolean().optional(),\n\t\tdefaultView: z.string().optional(),\n\t\tactiveSidebarTab: z.number().optional(),\n\t})\n\t.optional()\n\t.default({})\n\nconst PresencePreferencesSchema = z\n\t.object({\n\t\toptOut: z.boolean(),\n\t})\n\t.optional()\n\t.default({ optOut: false })\n\nconst AuthInfoSchema = z.object({\n\tid: z.string(),\n\ttokenSet: TokenSetSchema,\n\temail: z.string(),\n\tname: z.string().nullable().optional(),\n})\n\nconst MutedNotificationSchema = z.array(z.string()).default([])\n\nconst DataSchema = z.object({\n\tonboarding: z\n\t\t.object({\n\t\t\ttourVideosWatched: z.array(z.string()).default([]),\n\t\t})\n\t\t.passthrough()\n\t\t.optional()\n\t\t.default({ tourVideosWatched: [] }),\n\tpreferences: z\n\t\t.object({\n\t\t\tplayer: PlayerPreferencesSchema,\n\t\t\tpresence: PresencePreferencesSchema,\n\t\t\tplayground: z\n\t\t\t\t.object({\n\t\t\t\t\tpersist: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tfontSize: z.number().optional(),\n\t\t\texerciseWarning: z\n\t\t\t\t.object({\n\t\t\t\t\tdismissed: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional()\n\t\t\t\t.default({ dismissed: false }),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\t// deprecated. Probably safe to remove in May 2026:\n\tauthInfo: AuthInfoSchema.optional(),\n\t// new:\n\tauthInfos: z.record(z.string(), AuthInfoSchema).optional(),\n\tclientId: z.string().optional(),\n\tmutedNotifications: MutedNotificationSchema.optional(),\n})\n\nexport async function getClientId() {\n\tconst data = await readDb()\n\tif (data?.clientId) return data.clientId\n\n\tconst clientId = cuid()\n\tawait saveJSON({ ...data, clientId })\n\treturn clientId\n}\n\nexport async function logout() {\n\tconst config = getWorkshopConfig()\n\tconst host = config.product.host\n\tif (host) {\n\t\tconst data = await readDb()\n\t\tconst newAuthInfos = { ...data?.authInfos }\n\t\tdelete newAuthInfos[host]\n\t\tawait saveJSON({\n\t\t\t...data,\n\t\t\tauthInfos: newAuthInfos,\n\t\t})\n\t}\n}\n\nexport async function deleteDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tconst { path: dbPath } = await loadJSON()\n\t\tif (dbPath && (await fsExtra.exists(dbPath))) {\n\t\t\tawait fsExtra.remove(dbPath)\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Error deleting the database`, error)\n\t}\n}\n\nasync function readDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\tconst maxRetries = 3\n\tconst baseDelay = 10\n\n\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\ttry {\n\t\t\tconst { data, path: dbPath } = await loadJSON()\n\t\t\tif (data && dbPath) {\n\t\t\t\tconst db = DataSchema.parse(data)\n\t\t\t\treturn db\n\t\t\t}\n\t\t\treturn null\n\t\t} catch (error) {\n\t\t\t// If this is a retry attempt, it might be a race condition\n\t\t\tif (attempt < maxRetries) {\n\t\t\t\tconst delay = baseDelay * Math.pow(2, attempt)\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Database read error on attempt ${attempt + 1}/${maxRetries + 1}, retrying in ${delay}ms...`,\n\t\t\t\t)\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Final attempt failed, handle as corrupted file\n\t\t\tconsole.error(\n\t\t\t\t`Error reading the database after ${attempt + 1} attempts, moving it to a .bkp file to avoid parsing errors in the future`,\n\t\t\t\terror,\n\t\t\t)\n\n\t\t\t// Log to Sentry if available\n\t\t\tif (process.env.SENTRY_DSN && process.env.EPICSHOP_IS_PUBLISHED) {\n\t\t\t\ttry {\n\t\t\t\t\tconst Sentry = await import('@sentry/react-router')\n\t\t\t\t\tSentry.captureException(error, {\n\t\t\t\t\t\ttags: {\n\t\t\t\t\t\t\terror_type: 'corrupted_database_file',\n\t\t\t\t\t\t\tretry_attempts: attempt.toString(),\n\t\t\t\t\t\t},\n\t\t\t\t\t\textra: {\n\t\t\t\t\t\t\terrorMessage:\n\t\t\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\tretryAttempts: attempt,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t} catch (sentryError) {\n\t\t\t\t\tconsole.error('Failed to log to Sentry:', sentryError)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Try to move corrupted file to backup if we can determine the path\n\t\t\ttry {\n\t\t\t\tconst { path: dbPath } = await loadJSON()\n\t\t\t\tif (dbPath && (await fsExtra.exists(dbPath))) {\n\t\t\t\t\tvoid fsExtra.move(dbPath, `${dbPath}.bkp`).catch(() => {})\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\treturn null\n}\n\nexport async function getAuthInfo() {\n\tconst config = getWorkshopConfig()\n\tconst data = await readDb()\n\tif (config.product.host && typeof data?.authInfos === 'object') {\n\t\tif (config.product.host in data.authInfos) {\n\t\t\treturn data.authInfos[config.product.host]\n\t\t}\n\t}\n\n\t// special case for non-epicweb/epicreact workshops\n\tif (\n\t\t!config.product.host ||\n\t\tconfig.product.host === 'epicweb.dev' ||\n\t\tconfig.product.host === 'epicreact.dev'\n\t) {\n\t\t// upgrade from old authInfo to new authInfos\n\t\tif (data?.authInfo && config.product.host) {\n\t\t\tawait setAuthInfo(data.authInfo)\n\t\t}\n\t\treturn data?.authInfo ?? null\n\t}\n\n\treturn null\n}\n\nexport async function requireAuthInfo({\n\trequest,\n\tredirectTo,\n}: {\n\trequest: Request\n\tredirectTo?: string | null\n}) {\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) {\n\t\tconst requestUrl = new URL(request.url)\n\t\tredirectTo =\n\t\t\tredirectTo === null\n\t\t\t\t? null\n\t\t\t\t: (redirectTo ?? `${requestUrl.pathname}${requestUrl.search}`)\n\t\tconst loginParams = redirectTo ? new URLSearchParams({ redirectTo }) : null\n\t\tconst loginRedirect = ['/login', loginParams?.toString()]\n\t\t\t.filter(Boolean)\n\t\t\t.join('?')\n\t\tthrow redirect(loginRedirect)\n\t}\n\treturn authInfo\n}\n\nexport async function setAuthInfo({\n\tid,\n\ttokenSet,\n\temail = 'unknown@example.com',\n\tname,\n}: {\n\tid: string\n\ttokenSet: Partial<z.infer<typeof TokenSetSchema>>\n\temail?: string | null\n\tname?: string | null\n}) {\n\tconst data = await readDb()\n\tconst authInfo = AuthInfoSchema.parse({ id, tokenSet, email, name })\n\tconst config = getWorkshopConfig()\n\tif (config.product.host) {\n\t\tawait saveJSON({\n\t\t\t...data,\n\t\t\tauthInfos: {\n\t\t\t\t...data?.authInfos,\n\t\t\t\t[config.product.host]: authInfo,\n\t\t\t},\n\t\t})\n\t} else {\n\t\tawait saveJSON({ ...data, authInfo })\n\t}\n\treturn authInfo\n}\n\nexport async function getPreferences() {\n\tconst data = await readDb()\n\treturn data?.preferences ?? null\n}\n\nexport async function setPreferences(\n\tpreferences: z.input<typeof DataSchema>['preferences'],\n) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: {\n\t\t\t...data?.preferences,\n\t\t\t...preferences,\n\t\t\tplayer: {\n\t\t\t\t...data?.preferences?.player,\n\t\t\t\t...preferences?.player,\n\t\t\t},\n\t\t\tpresence: {\n\t\t\t\t...data?.preferences?.presence,\n\t\t\t\t...preferences?.presence,\n\t\t\t},\n\t\t\texerciseWarning: {\n\t\t\t\t...data?.preferences?.exerciseWarning,\n\t\t\t\t...preferences?.exerciseWarning,\n\t\t\t},\n\t\t},\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.preferences\n}\n\nexport async function getMutedNotifications() {\n\tconst data = await readDb()\n\treturn data?.mutedNotifications ?? []\n}\n\nexport async function muteNotification(id: string) {\n\tconst data = await readDb()\n\tconst mutedNotifications = Array.from(\n\t\tnew Set([...(data?.mutedNotifications ?? []), id]),\n\t)\n\tconst updatedData = {\n\t\t...data,\n\t\tmutedNotifications,\n\t}\n\tawait saveJSON(updatedData)\n\treturn mutedNotifications\n}\n\nexport async function setFontSizePreference(fontSize: number | undefined) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: { ...data?.preferences, fontSize },\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.preferences.fontSize\n}\n\nexport async function getFontSizePreference() {\n\tconst data = await readDb()\n\treturn data?.preferences?.fontSize ?? null\n}\n\nexport async function readOnboardingData() {\n\tconst data = await readDb()\n\treturn data?.onboarding ?? null\n}\n\nexport async function markOnboardingVideoWatched(videoUrl: string) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tonboarding: {\n\t\t\t...data?.onboarding,\n\t\t\ttourVideosWatched: [\n\t\t\t\t...(data?.onboarding.tourVideosWatched ?? []),\n\t\t\t\tvideoUrl,\n\t\t\t].filter(Boolean),\n\t\t},\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.onboarding\n}\n\nexport async function unmarkOnboardingVideoWatched(videoUrl: string) {\n\tconst data = await readDb()\n\tconst watchedVideos = data?.onboarding?.tourVideosWatched ?? []\n\tconst updatedData = {\n\t\t...data,\n\t\tonboarding: {\n\t\t\t...data?.onboarding,\n\t\t\ttourVideosWatched: watchedVideos.filter((url) => url !== videoUrl),\n\t\t},\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.onboarding\n}\n\nexport async function areAllOnboardingVideosWatched(\n\tonboardingVideos: string[],\n) {\n\tconst data = await readDb()\n\tconst watchedVideos = data?.onboarding?.tourVideosWatched ?? []\n\treturn onboardingVideos.every((video) => watchedVideos.includes(video))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"db.server.js","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAA;AAEtB,OAAO,EAAE,QAAQ,IAAI,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,OAAO,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAEhF,4CAA4C;AAC5C,MAAM,iBAAiB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;AAEzC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;CACjB,CAAC,CAAA;AACF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACtC,MAAM,CAAC;IACP,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAChC,QAAQ,EAAE,CAAC;SACT,MAAM,CAAC;QACP,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QACvC,IAAI,EAAE,CAAC;aACL,OAAO,CAAC,UAAU,CAAC;aACnB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;aACvB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;aACxB,QAAQ,EAAE;aACV,OAAO,CAAC,UAAU,CAAC;KACrB,CAAC;SACD,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;IACb,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC7B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC/B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACvC,CAAC;KACD,QAAQ,EAAE;KACV,OAAO,CAAC,EAAE,CAAC,CAAA;AAEb,MAAM,yBAAyB,GAAG,CAAC;KACjC,MAAM,CAAC;IACP,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE;CACnB,CAAC;KACD,QAAQ,EAAE;KACV,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;AAE5B,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,QAAQ,EAAE,cAAc;IACxB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CACtC,CAAC,CAAA;AAEF,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;AAE/D,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,UAAU,EAAE,CAAC;SACX,MAAM,CAAC;QACP,iBAAiB,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;KAClD,CAAC;SACD,WAAW,EAAE;SACb,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC;IACpC,WAAW,EAAE,CAAC;SACZ,MAAM,CAAC;QACP,MAAM,EAAE,uBAAuB;QAC/B,QAAQ,EAAE,yBAAyB;QACnC,UAAU,EAAE,CAAC;aACX,MAAM,CAAC;YACP,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,CAAC;aACD,QAAQ,EAAE;QACZ,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC/B,eAAe,EAAE,CAAC;aAChB,MAAM,CAAC;YACP,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;SACrC,CAAC;aACD,QAAQ,EAAE;aACV,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;KAC/B,CAAC;SACD,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;IACb,mDAAmD;IACnD,QAAQ,EAAE,cAAc,CAAC,QAAQ,EAAE;IACnC,OAAO;IACP,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,QAAQ,EAAE;IAC1D,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,kBAAkB,EAAE,uBAAuB,CAAC,QAAQ,EAAE;CACtD,CAAC,CAAA;AAEF,MAAM,CAAC,KAAK,UAAU,WAAW;IAChC,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,IAAI,IAAI,EAAE,QAAQ;QAAE,OAAO,IAAI,CAAC,QAAQ,CAAA;IAExC,MAAM,QAAQ,GAAG,IAAI,EAAE,CAAA;IACvB,MAAM,QAAQ,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;IACrC,OAAO,QAAQ,CAAA;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM;IAC3B,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAA;IAChC,IAAI,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;QAC3B,MAAM,YAAY,GAAG,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,CAAA;QAC3C,OAAO,YAAY,CAAC,IAAI,CAAC,CAAA;QACzB,MAAM,QAAQ,CAAC;YACd,GAAG,IAAI;YACP,SAAS,EAAE,YAAY;SACvB,CAAC,CAAA;IACH,CAAC;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAAE,OAAO,IAAI,CAAA;IAE9C,IAAI,CAAC;QACJ,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,EAAE,CAAA;QACzC,IAAI,MAAM,IAAI,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YAC9C,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAC7B,CAAC;IACF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAA;IACpD,CAAC;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM;IAC3B,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAAE,OAAO,IAAI,CAAA;IAE9C,MAAM,UAAU,GAAG,CAAC,CAAA;IACpB,MAAM,SAAS,GAAG,EAAE,CAAA;IAEpB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACxD,IAAI,CAAC;YACJ,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,EAAE,CAAA;YAC/C,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;gBACpB,MAAM,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACjC,OAAO,EAAE,CAAA;YACV,CAAC;YACD,OAAO,IAAI,CAAA;QACZ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,2DAA2D;YAC3D,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;gBAC1B,MAAM,KAAK,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;gBAC9C,OAAO,CAAC,IAAI,CACX,kCAAkC,OAAO,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,iBAAiB,KAAK,OAAO,CAC5F,CAAA;gBACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAA;gBAC1D,SAAQ;YACT,CAAC;YAED,iDAAiD;YACjD,OAAO,CAAC,KAAK,CACZ,oCAAoC,OAAO,GAAG,CAAC,2EAA2E,EAC1H,KAAK,CACL,CAAA;YAED,6BAA6B;YAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,CAAC;gBACjE,IAAI,CAAC;oBACJ,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAA;oBACnD,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE;wBAC9B,IAAI,EAAE;4BACL,UAAU,EAAE,yBAAyB;4BACrC,cAAc,EAAE,OAAO,CAAC,QAAQ,EAAE;yBAClC;wBACD,KAAK,EAAE;4BACN,YAAY,EACX,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;4BACvD,aAAa,EAAE,OAAO;yBACtB;qBACD,CAAC,CAAA;gBACH,CAAC;gBAAC,OAAO,WAAW,EAAE,CAAC;oBACtB,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,WAAW,CAAC,CAAA;gBACvD,CAAC;YACF,CAAC;YAED,oEAAoE;YACpE,IAAI,CAAC;gBACJ,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,EAAE,CAAA;gBACzC,IAAI,MAAM,IAAI,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;oBAC9C,KAAK,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;gBAC3D,CAAC;YACF,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACX,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAChC,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,IAAI,EAAE,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChE,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3C,CAAC;IACF,CAAC;IAED,mDAAmD;IACnD,IACC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI;QACpB,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,aAAa;QACrC,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,eAAe,EACtC,CAAC;QACF,6CAA6C;QAC7C,IAAI,IAAI,EAAE,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC3C,MAAM,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACjC,CAAC;QACD,OAAO,IAAI,EAAE,QAAQ,IAAI,IAAI,CAAA;IAC9B,CAAC;IAED,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EACrC,OAAO,EACP,UAAU,GAIV;IACA,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAA;IACpC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACvC,UAAU;YACT,UAAU,KAAK,IAAI;gBAClB,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,CAAC,UAAU,IAAI,GAAG,UAAU,CAAC,QAAQ,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,CAAA;QAChE,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC3E,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;aACvD,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,GAAG,CAAC,CAAA;QACX,MAAM,QAAQ,CAAC,aAAa,CAAC,CAAA;IAC9B,CAAC;IACD,OAAO,QAAQ,CAAA;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EACjC,EAAE,EACF,QAAQ,EACR,KAAK,GAAG,qBAAqB,EAC7B,IAAI,GAMJ;IACA,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACpE,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACzB,MAAM,QAAQ,CAAC;YACd,GAAG,IAAI;YACP,SAAS,EAAE;gBACV,GAAG,IAAI,EAAE,SAAS;gBAClB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ;aAC/B;SACD,CAAC,CAAA;IACH,CAAC;SAAM,CAAC;QACP,MAAM,QAAQ,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;IACtC,CAAC;IACD,OAAO,QAAQ,CAAA;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IACnC,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,OAAO,IAAI,EAAE,WAAW,IAAI,IAAI,CAAA;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,WAAsD;IAEtD,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,MAAM,WAAW,GAAG;QACnB,GAAG,IAAI;QACP,WAAW,EAAE;YACZ,GAAG,IAAI,EAAE,WAAW;YACpB,GAAG,WAAW;YACd,MAAM,EAAE;gBACP,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM;gBAC5B,GAAG,WAAW,EAAE,MAAM;aACtB;YACD,QAAQ,EAAE;gBACT,GAAG,IAAI,EAAE,WAAW,EAAE,QAAQ;gBAC9B,GAAG,WAAW,EAAE,QAAQ;aACxB;YACD,eAAe,EAAE;gBAChB,GAAG,IAAI,EAAE,WAAW,EAAE,eAAe;gBACrC,GAAG,WAAW,EAAE,eAAe;aAC/B;SACD;KACD,CAAA;IACD,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC3B,OAAO,WAAW,CAAC,WAAW,CAAA;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB;IAC1C,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,OAAO,IAAI,EAAE,kBAAkB,IAAI,EAAE,CAAA;AACtC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EAAU;IAChD,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,MAAM,kBAAkB,GAAG,KAAK,CAAC,IAAI,CACpC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,kBAAkB,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAClD,CAAA;IACD,MAAM,WAAW,GAAG;QACnB,GAAG,IAAI;QACP,kBAAkB;KAClB,CAAA;IACD,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC3B,OAAO,kBAAkB,CAAA;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,QAA4B;IACvE,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,MAAM,WAAW,GAAG;QACnB,GAAG,IAAI;QACP,WAAW,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE;KAC/C,CAAA;IACD,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC3B,OAAO,WAAW,CAAC,WAAW,CAAC,QAAQ,CAAA;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB;IAC1C,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,OAAO,IAAI,EAAE,WAAW,EAAE,QAAQ,IAAI,IAAI,CAAA;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACvC,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,OAAO,IAAI,EAAE,UAAU,IAAI,IAAI,CAAA;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,QAAgB;IAChE,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,MAAM,WAAW,GAAG;QACnB,GAAG,IAAI;QACP,UAAU,EAAE;YACX,GAAG,IAAI,EAAE,UAAU;YACnB,iBAAiB,EAAE;gBAClB,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,iBAAiB,IAAI,EAAE,CAAC;gBAC7C,QAAQ;aACR,CAAC,MAAM,CAAC,OAAO,CAAC;SACjB;KACD,CAAA;IACD,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC3B,OAAO,WAAW,CAAC,UAAU,CAAA;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,QAAgB;IAClE,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,MAAM,aAAa,GAAG,IAAI,EAAE,UAAU,EAAE,iBAAiB,IAAI,EAAE,CAAA;IAC/D,MAAM,WAAW,GAAG;QACnB,GAAG,IAAI;QACP,UAAU,EAAE;YACX,GAAG,IAAI,EAAE,UAAU;YACnB,iBAAiB,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,QAAQ,CAAC;SAClE;KACD,CAAA;IACD,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC3B,OAAO,WAAW,CAAC,UAAU,CAAA;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B,CAClD,gBAA0B;IAE1B,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAA;IAC3B,MAAM,aAAa,GAAG,IAAI,EAAE,UAAU,EAAE,iBAAiB,IAAI,EAAE,CAAA;IAC/D,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;AACxE,CAAC","sourcesContent":["import './init-env.js'\n\nimport { createId as cuid } from '@paralleldrive/cuid2'\nimport fsExtra from 'fs-extra'\nimport { redirect } from 'react-router'\nimport { z } from 'zod'\nimport { getWorkshopConfig } from './config.server.js'\nimport { saveJSON, loadJSON, migrateLegacyData } from './data-storage.server.js'\n\n// Attempt migration from legacy ~/.epicshop\nawait migrateLegacyData().catch(() => {})\n\nconst TokenSetSchema = z.object({\n\taccess_token: z.string(),\n\ttoken_type: z.string(),\n\tscope: z.string(),\n})\nexport const PlayerPreferencesSchema = z\n\t.object({\n\t\tminResolution: z.number().optional(),\n\t\tmaxResolution: z.number().optional(),\n\t\tvolumeRate: z.number().optional(),\n\t\tplaybackRate: z.number().optional(),\n\t\tautoplay: z.boolean().optional(),\n\t\tsubtitle: z\n\t\t\t.object({\n\t\t\t\tid: z.string().nullable().default(null),\n\t\t\t\tmode: z\n\t\t\t\t\t.literal('disabled')\n\t\t\t\t\t.or(z.literal('hidden'))\n\t\t\t\t\t.or(z.literal('showing'))\n\t\t\t\t\t.nullable()\n\t\t\t\t\t.default('disabled'),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.default({}),\n\t\tmuted: z.boolean().optional(),\n\t\ttheater: z.boolean().optional(),\n\t\tdefaultView: z.string().optional(),\n\t\tactiveSidebarTab: z.number().optional(),\n\t})\n\t.optional()\n\t.default({})\n\nconst PresencePreferencesSchema = z\n\t.object({\n\t\toptOut: z.boolean(),\n\t})\n\t.optional()\n\t.default({ optOut: false })\n\nconst AuthInfoSchema = z.object({\n\tid: z.string(),\n\ttokenSet: TokenSetSchema,\n\temail: z.string(),\n\tname: z.string().nullable().optional(),\n})\n\nconst MutedNotificationSchema = z.array(z.string()).default([])\n\nconst DataSchema = z.object({\n\tonboarding: z\n\t\t.object({\n\t\t\ttourVideosWatched: z.array(z.string()).default([]),\n\t\t})\n\t\t.passthrough()\n\t\t.optional()\n\t\t.default({ tourVideosWatched: [] }),\n\tpreferences: z\n\t\t.object({\n\t\t\tplayer: PlayerPreferencesSchema,\n\t\t\tpresence: PresencePreferencesSchema,\n\t\t\tplayground: z\n\t\t\t\t.object({\n\t\t\t\t\tpersist: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tfontSize: z.number().optional(),\n\t\t\texerciseWarning: z\n\t\t\t\t.object({\n\t\t\t\t\tdismissed: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional()\n\t\t\t\t.default({ dismissed: false }),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\t// deprecated. Probably safe to remove in May 2026:\n\tauthInfo: AuthInfoSchema.optional(),\n\t// new:\n\tauthInfos: z.record(z.string(), AuthInfoSchema).optional(),\n\tclientId: z.string().optional(),\n\tmutedNotifications: MutedNotificationSchema.optional(),\n})\n\nexport async function getClientId() {\n\tconst data = await readDb()\n\tif (data?.clientId) return data.clientId\n\n\tconst clientId = cuid()\n\tawait saveJSON({ ...data, clientId })\n\treturn clientId\n}\n\nexport async function logout() {\n\tconst config = getWorkshopConfig()\n\tconst host = config.product.host\n\tif (host) {\n\t\tconst data = await readDb()\n\t\tconst newAuthInfos = { ...data?.authInfos }\n\t\tdelete newAuthInfos[host]\n\t\tawait saveJSON({\n\t\t\t...data,\n\t\t\tauthInfos: newAuthInfos,\n\t\t})\n\t}\n}\n\nexport async function deleteDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tconst { path: dbPath } = await loadJSON()\n\t\tif (dbPath && (await fsExtra.exists(dbPath))) {\n\t\t\tawait fsExtra.remove(dbPath)\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Error deleting the database`, error)\n\t}\n}\n\nexport async function readDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\tconst maxRetries = 3\n\tconst baseDelay = 10\n\n\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\ttry {\n\t\t\tconst { data, path: dbPath } = await loadJSON()\n\t\t\tif (data && dbPath) {\n\t\t\t\tconst db = DataSchema.parse(data)\n\t\t\t\treturn db\n\t\t\t}\n\t\t\treturn null\n\t\t} catch (error) {\n\t\t\t// If this is a retry attempt, it might be a race condition\n\t\t\tif (attempt < maxRetries) {\n\t\t\t\tconst delay = baseDelay * Math.pow(2, attempt)\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Database read error on attempt ${attempt + 1}/${maxRetries + 1}, retrying in ${delay}ms...`,\n\t\t\t\t)\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Final attempt failed, handle as corrupted file\n\t\t\tconsole.error(\n\t\t\t\t`Error reading the database after ${attempt + 1} attempts, moving it to a .bkp file to avoid parsing errors in the future`,\n\t\t\t\terror,\n\t\t\t)\n\n\t\t\t// Log to Sentry if available\n\t\t\tif (process.env.SENTRY_DSN && process.env.EPICSHOP_IS_PUBLISHED) {\n\t\t\t\ttry {\n\t\t\t\t\tconst Sentry = await import('@sentry/react-router')\n\t\t\t\t\tSentry.captureException(error, {\n\t\t\t\t\t\ttags: {\n\t\t\t\t\t\t\terror_type: 'corrupted_database_file',\n\t\t\t\t\t\t\tretry_attempts: attempt.toString(),\n\t\t\t\t\t\t},\n\t\t\t\t\t\textra: {\n\t\t\t\t\t\t\terrorMessage:\n\t\t\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\tretryAttempts: attempt,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t} catch (sentryError) {\n\t\t\t\t\tconsole.error('Failed to log to Sentry:', sentryError)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Try to move corrupted file to backup if we can determine the path\n\t\t\ttry {\n\t\t\t\tconst { path: dbPath } = await loadJSON()\n\t\t\t\tif (dbPath && (await fsExtra.exists(dbPath))) {\n\t\t\t\t\tvoid fsExtra.move(dbPath, `${dbPath}.bkp`).catch(() => {})\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\treturn null\n}\n\nexport async function getAuthInfo() {\n\tconst config = getWorkshopConfig()\n\tconst data = await readDb()\n\tif (config.product.host && typeof data?.authInfos === 'object') {\n\t\tif (config.product.host in data.authInfos) {\n\t\t\treturn data.authInfos[config.product.host]\n\t\t}\n\t}\n\n\t// special case for non-epicweb/epicreact workshops\n\tif (\n\t\t!config.product.host ||\n\t\tconfig.product.host === 'epicweb.dev' ||\n\t\tconfig.product.host === 'epicreact.dev'\n\t) {\n\t\t// upgrade from old authInfo to new authInfos\n\t\tif (data?.authInfo && config.product.host) {\n\t\t\tawait setAuthInfo(data.authInfo)\n\t\t}\n\t\treturn data?.authInfo ?? null\n\t}\n\n\treturn null\n}\n\nexport async function requireAuthInfo({\n\trequest,\n\tredirectTo,\n}: {\n\trequest: Request\n\tredirectTo?: string | null\n}) {\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) {\n\t\tconst requestUrl = new URL(request.url)\n\t\tredirectTo =\n\t\t\tredirectTo === null\n\t\t\t\t? null\n\t\t\t\t: (redirectTo ?? `${requestUrl.pathname}${requestUrl.search}`)\n\t\tconst loginParams = redirectTo ? new URLSearchParams({ redirectTo }) : null\n\t\tconst loginRedirect = ['/login', loginParams?.toString()]\n\t\t\t.filter(Boolean)\n\t\t\t.join('?')\n\t\tthrow redirect(loginRedirect)\n\t}\n\treturn authInfo\n}\n\nexport async function setAuthInfo({\n\tid,\n\ttokenSet,\n\temail = 'unknown@example.com',\n\tname,\n}: {\n\tid: string\n\ttokenSet: Partial<z.infer<typeof TokenSetSchema>>\n\temail?: string | null\n\tname?: string | null\n}) {\n\tconst data = await readDb()\n\tconst authInfo = AuthInfoSchema.parse({ id, tokenSet, email, name })\n\tconst config = getWorkshopConfig()\n\tif (config.product.host) {\n\t\tawait saveJSON({\n\t\t\t...data,\n\t\t\tauthInfos: {\n\t\t\t\t...data?.authInfos,\n\t\t\t\t[config.product.host]: authInfo,\n\t\t\t},\n\t\t})\n\t} else {\n\t\tawait saveJSON({ ...data, authInfo })\n\t}\n\treturn authInfo\n}\n\nexport async function getPreferences() {\n\tconst data = await readDb()\n\treturn data?.preferences ?? null\n}\n\nexport async function setPreferences(\n\tpreferences: z.input<typeof DataSchema>['preferences'],\n) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: {\n\t\t\t...data?.preferences,\n\t\t\t...preferences,\n\t\t\tplayer: {\n\t\t\t\t...data?.preferences?.player,\n\t\t\t\t...preferences?.player,\n\t\t\t},\n\t\t\tpresence: {\n\t\t\t\t...data?.preferences?.presence,\n\t\t\t\t...preferences?.presence,\n\t\t\t},\n\t\t\texerciseWarning: {\n\t\t\t\t...data?.preferences?.exerciseWarning,\n\t\t\t\t...preferences?.exerciseWarning,\n\t\t\t},\n\t\t},\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.preferences\n}\n\nexport async function getMutedNotifications() {\n\tconst data = await readDb()\n\treturn data?.mutedNotifications ?? []\n}\n\nexport async function muteNotification(id: string) {\n\tconst data = await readDb()\n\tconst mutedNotifications = Array.from(\n\t\tnew Set([...(data?.mutedNotifications ?? []), id]),\n\t)\n\tconst updatedData = {\n\t\t...data,\n\t\tmutedNotifications,\n\t}\n\tawait saveJSON(updatedData)\n\treturn mutedNotifications\n}\n\nexport async function setFontSizePreference(fontSize: number | undefined) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: { ...data?.preferences, fontSize },\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.preferences.fontSize\n}\n\nexport async function getFontSizePreference() {\n\tconst data = await readDb()\n\treturn data?.preferences?.fontSize ?? null\n}\n\nexport async function readOnboardingData() {\n\tconst data = await readDb()\n\treturn data?.onboarding ?? null\n}\n\nexport async function markOnboardingVideoWatched(videoUrl: string) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tonboarding: {\n\t\t\t...data?.onboarding,\n\t\t\ttourVideosWatched: [\n\t\t\t\t...(data?.onboarding.tourVideosWatched ?? []),\n\t\t\t\tvideoUrl,\n\t\t\t].filter(Boolean),\n\t\t},\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.onboarding\n}\n\nexport async function unmarkOnboardingVideoWatched(videoUrl: string) {\n\tconst data = await readDb()\n\tconst watchedVideos = data?.onboarding?.tourVideosWatched ?? []\n\tconst updatedData = {\n\t\t...data,\n\t\tonboarding: {\n\t\t\t...data?.onboarding,\n\t\t\ttourVideosWatched: watchedVideos.filter((url) => url !== videoUrl),\n\t\t},\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.onboarding\n}\n\nexport async function areAllOnboardingVideosWatched(\n\tonboardingVideos: string[],\n) {\n\tconst data = await readDb()\n\tconst watchedVideos = data?.onboarding?.tourVideosWatched ?? []\n\treturn onboardingVideos.every((video) => watchedVideos.includes(video))\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"epic-api.server.d.ts","sourceRoot":"","sources":["../../src/epic-api.server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAWvB,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAoDjD,MAAM,MAAM,cAAc,GAAG,MAAM,CAClC,MAAM,EACN,OAAO,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAC5C,CAAA;
|
|
1
|
+
{"version":3,"file":"epic-api.server.d.ts","sourceRoot":"","sources":["../../src/epic-api.server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAWvB,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAoDjD,MAAM,MAAM,cAAc,GAAG,MAAM,CAClC,MAAM,EACN,OAAO,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAC5C,CAAA;AAID,wBAAsB,iBAAiB,CACtC,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,EAClC,EAAE,OAAO,EAAE,OAAO,EAAE,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,2BA2BnE;AAED,iBAAe,gBAAgB,CAAC,EAC/B,cAAc,EACd,WAAW,EACX,OAAO,EACP,OAAO,GACP,EAAE;IACF,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB;;;;;;;;;;;;;;;;;;;UA0HA;AAkHD,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AACtE,wBAAsB,WAAW,CAAC,EACjC,OAAO,EACP,OAAO,GACP,GAAE;IACF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;CACZ;mBA2BW,MAAM;oBACL,MAAM;qBACL,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;UAIsB,SAAS;mBAqC9D;AAqDD,wBAAsB,cAAc,CACnC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,EACpE,EACC,OAAO,EACP,OAAO,GACP,GAAE;IACF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;CACZ;;;;;;;;;;;;GAkDN;AAoBD,wBAAsB,eAAe,CACpC,IAAI,EAAE,MAAM,EACZ,EACC,OAAO,EACP,OAAO,EACP,UAAU,GACV,GAAE;IACF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;CACf;;;;;;;;;;;;GAoDN;AAED,wBAAsB,uBAAuB,CAAC,EAC7C,OAAO,EACP,OAAO,EACP,UAAU,GACV,GAAE;IACF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;CACf,oBAsDL;AAED,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqCjB,CAAA;AAwCH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAGrD,wBAAsB,WAAW,CAAC,EACjC,OAAO,EACP,OAAO,EACP,UAAU,GACV,GAAE;IACF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;CACf;;;;;;;;;;;;;;;;UA0EL;AAED,wBAAsB,SAAS,kBAE9B"}
|
|
@@ -50,15 +50,16 @@ const CachedEpicVideoInfoSchema = z
|
|
|
50
50
|
restrictedCountry: z.string(),
|
|
51
51
|
}))
|
|
52
52
|
.or(z.null());
|
|
53
|
+
const videoInfoLog = log.logger('video-info');
|
|
53
54
|
export async function getEpicVideoInfos(epicWebUrls, { request, timings } = {}) {
|
|
54
55
|
if (!epicWebUrls) {
|
|
55
|
-
|
|
56
|
+
videoInfoLog.warn('no epic web URLs provided, returning empty object');
|
|
56
57
|
return {};
|
|
57
58
|
}
|
|
58
59
|
const authInfo = await getAuthInfo();
|
|
59
60
|
if (getEnv().EPICSHOP_DEPLOYED)
|
|
60
61
|
return {};
|
|
61
|
-
|
|
62
|
+
videoInfoLog(`fetching epic video infos for ${epicWebUrls.length} URLs`);
|
|
62
63
|
const epicVideoInfos = {};
|
|
63
64
|
for (const epicVideoEmbed of epicWebUrls) {
|
|
64
65
|
const epicVideoInfo = await getEpicVideoInfo({
|
|
@@ -71,13 +72,13 @@ export async function getEpicVideoInfos(epicWebUrls, { request, timings } = {})
|
|
|
71
72
|
epicVideoInfos[epicVideoEmbed] = epicVideoInfo;
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
videoInfoLog(`successfully fetched ${Object.keys(epicVideoInfos).length} epic video infos`);
|
|
75
76
|
return epicVideoInfos;
|
|
76
77
|
}
|
|
77
78
|
async function getEpicVideoInfo({ epicVideoEmbed, accessToken, request, timings, }) {
|
|
78
79
|
const tokenPortion = accessToken ? md5(accessToken) : 'unauthenticated';
|
|
79
80
|
const key = `epic-video-info:${tokenPortion}:${epicVideoEmbed}`;
|
|
80
|
-
|
|
81
|
+
videoInfoLog(`fetching video info for URL: ${epicVideoEmbed}`);
|
|
81
82
|
return cachified({
|
|
82
83
|
key,
|
|
83
84
|
request,
|
|
@@ -92,7 +93,7 @@ async function getEpicVideoInfo({ epicVideoEmbed, accessToken, request, timings,
|
|
|
92
93
|
if (epicUrl.host !== 'www.epicweb.dev' &&
|
|
93
94
|
epicUrl.host !== 'www.epicreact.dev' &&
|
|
94
95
|
epicUrl.host !== 'www.epicai.pro') {
|
|
95
|
-
|
|
96
|
+
videoInfoLog.error(`unsupported host for video URL: ${epicUrl.host}`);
|
|
96
97
|
return null;
|
|
97
98
|
}
|
|
98
99
|
// this may be temporary until the /tutorials/ endpoint supports /api
|
|
@@ -103,12 +104,12 @@ async function getEpicVideoInfo({ epicVideoEmbed, accessToken, request, timings,
|
|
|
103
104
|
const apiUrl = epicUrl.host === 'www.epicai.pro'
|
|
104
105
|
? getEpicAIVideoAPIUrl(epicVideoEmbed)
|
|
105
106
|
: `https://${epicUrl.host}/api${epicUrl.pathname}`;
|
|
106
|
-
|
|
107
|
+
videoInfoLog(`making API request to: ${apiUrl}`);
|
|
107
108
|
const infoResponse = await fetch(apiUrl, accessToken
|
|
108
109
|
? { headers: { authorization: `Bearer ${accessToken}` } }
|
|
109
110
|
: undefined);
|
|
110
111
|
const { status, statusText } = infoResponse;
|
|
111
|
-
|
|
112
|
+
videoInfoLog(`API response: ${status} ${statusText}`);
|
|
112
113
|
if (infoResponse.status >= 200 && infoResponse.status < 300) {
|
|
113
114
|
let rawInfo = await infoResponse.json();
|
|
114
115
|
// another special case for epicai.pro videos
|
|
@@ -117,7 +118,7 @@ async function getEpicVideoInfo({ epicVideoEmbed, accessToken, request, timings,
|
|
|
117
118
|
}
|
|
118
119
|
const infoResult = EpicVideoInfoSchema.safeParse(rawInfo);
|
|
119
120
|
if (infoResult.success) {
|
|
120
|
-
|
|
121
|
+
videoInfoLog(`successfully parsed video info for ${epicVideoEmbed}`);
|
|
121
122
|
return {
|
|
122
123
|
status: 'success',
|
|
123
124
|
statusCode: status,
|
|
@@ -131,7 +132,7 @@ async function getEpicVideoInfo({ epicVideoEmbed, accessToken, request, timings,
|
|
|
131
132
|
context.metadata.swr = 0;
|
|
132
133
|
const restrictedResult = EpicVideoRegionRestrictedErrorSchema.safeParse(rawInfo);
|
|
133
134
|
if (restrictedResult.success) {
|
|
134
|
-
|
|
135
|
+
videoInfoLog.warn(`video is region restricted: ${epicVideoEmbed}`);
|
|
135
136
|
return {
|
|
136
137
|
status: 'error',
|
|
137
138
|
statusCode: status,
|
|
@@ -141,12 +142,11 @@ async function getEpicVideoInfo({ epicVideoEmbed, accessToken, request, timings,
|
|
|
141
142
|
};
|
|
142
143
|
}
|
|
143
144
|
else {
|
|
144
|
-
|
|
145
|
+
videoInfoLog.error(`API response parsing failed for ${epicVideoEmbed}`, {
|
|
145
146
|
url: epicUrl.pathname,
|
|
146
147
|
rawInfo,
|
|
147
148
|
parseError: infoResult.error,
|
|
148
149
|
});
|
|
149
|
-
console.warn(`Response from EpicWeb for "${epicUrl.pathname}" does not match expectation`, infoResult.error);
|
|
150
150
|
return {
|
|
151
151
|
status: 'error',
|
|
152
152
|
statusCode: 500,
|
|
@@ -160,7 +160,7 @@ async function getEpicVideoInfo({ epicVideoEmbed, accessToken, request, timings,
|
|
|
160
160
|
// don't cache errors for long...
|
|
161
161
|
context.metadata.ttl = 1000 * 2;
|
|
162
162
|
context.metadata.swr = 0;
|
|
163
|
-
|
|
163
|
+
videoInfoLog.error(`video API request failed for ${epicVideoEmbed}`, {
|
|
164
164
|
status,
|
|
165
165
|
statusText,
|
|
166
166
|
url: apiUrl,
|
|
@@ -174,7 +174,7 @@ async function getEpicVideoInfo({ epicVideoEmbed, accessToken, request, timings,
|
|
|
174
174
|
}
|
|
175
175
|
},
|
|
176
176
|
}).catch((e) => {
|
|
177
|
-
|
|
177
|
+
videoInfoLog.error(`failed to fetch epic video info for ${epicVideoEmbed}:`, e);
|
|
178
178
|
throw e;
|
|
179
179
|
});
|
|
180
180
|
}
|
|
@@ -554,6 +554,7 @@ function resolveDiscordAvatar(user, { size }) {
|
|
|
554
554
|
url.searchParams.set('size', size.toString());
|
|
555
555
|
return url.toString();
|
|
556
556
|
}
|
|
557
|
+
const userinfoLog = log.logger('userinfo');
|
|
557
558
|
export async function getUserInfo({ timings, request, forceFresh, } = {}) {
|
|
558
559
|
const authInfo = await getAuthInfo();
|
|
559
560
|
if (!authInfo)
|
|
@@ -562,7 +563,7 @@ export async function getUserInfo({ timings, request, forceFresh, } = {}) {
|
|
|
562
563
|
const { product: { host }, } = getWorkshopConfig();
|
|
563
564
|
const accessToken = tokenSet.access_token;
|
|
564
565
|
const url = `https://${host}/oauth/userinfo`;
|
|
565
|
-
|
|
566
|
+
userinfoLog(`calling cachified to get user info from: ${url}`);
|
|
566
567
|
const userInfo = await cachified({
|
|
567
568
|
key: `${url}:${md5(accessToken)}`,
|
|
568
569
|
cache: epicApiCache,
|
|
@@ -574,13 +575,13 @@ export async function getUserInfo({ timings, request, forceFresh, } = {}) {
|
|
|
574
575
|
offlineFallbackValue: null,
|
|
575
576
|
checkValue: UserInfoSchema,
|
|
576
577
|
async getFreshValue() {
|
|
577
|
-
|
|
578
|
+
userinfoLog(`getting fresh value for user info from: ${url}`);
|
|
578
579
|
const response = await fetch(url, {
|
|
579
580
|
headers: { authorization: `Bearer ${accessToken}` },
|
|
580
581
|
}).catch((e) => new Response(getErrorMessage(e), { status: 500 }));
|
|
581
|
-
|
|
582
|
+
userinfoLog(`user info API response: ${response.status} ${response.statusText}`);
|
|
582
583
|
if (!response.ok) {
|
|
583
|
-
|
|
584
|
+
userinfoLog(`user info API request failed: ${response.status} ${response.statusText}`);
|
|
584
585
|
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
585
586
|
const data = await response.json();
|
|
586
587
|
throw new Error(`Failed to fetch user info: ${JSON.stringify(data)}`);
|
|
@@ -592,11 +593,11 @@ export async function getUserInfo({ timings, request, forceFresh, } = {}) {
|
|
|
592
593
|
}
|
|
593
594
|
const data = await response.json();
|
|
594
595
|
const parsedUserInfo = UserInfoSchema.parse(data);
|
|
595
|
-
|
|
596
|
+
userinfoLog(`successfully fetched user info for user: ${parsedUserInfo.id} (${parsedUserInfo.email})`);
|
|
596
597
|
return parsedUserInfo;
|
|
597
598
|
},
|
|
598
599
|
}).catch((e) => {
|
|
599
|
-
|
|
600
|
+
userinfoLog.error(`failed to get user info:`, e);
|
|
600
601
|
return null;
|
|
601
602
|
});
|
|
602
603
|
// we used to md5 hash the email to get the id
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"epic-api.server.js","sourceRoot":"","sources":["../../src/epic-api.server.ts"],"names":[],"mappings":"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","sourcesContent":["import { invariant } from '@epic-web/invariant'\nimport * as cookie from 'cookie'\n\nimport md5 from 'md5-hex'\nimport { z } from 'zod'\nimport {\n\tgetExercises,\n\tgetWorkshopFinished,\n\tgetWorkshopInstructions,\n} from './apps.server.js'\nimport { cachified, epicApiCache } from './cache.server.js'\nimport { getWorkshopConfig } from './config.server.js'\nimport { getAuthInfo, setAuthInfo } from './db.server.js'\nimport { getEnv } from './init-env.js'\nimport { logger } from './logger.js'\nimport { type Timings } from './timing.server.js'\nimport { getErrorMessage } from './utils.js'\n\n// Module-level logger for epic-api operations\nconst log = logger('epic:api')\n\nconst Transcript = z\n\t.string()\n\t.nullable()\n\t.optional()\n\t.transform((s) => s ?? 'Transcripts not available')\nconst EpicVideoInfoSchema = z.object({\n\ttitle: z.string().nullable().optional(),\n\ttranscript: Transcript,\n\tmuxPlaybackId: z.string(),\n})\n\nconst EpicVideoRegionRestrictedErrorSchema = z.object({\n\trequestCountry: z.string(),\n\trestrictedCountry: z.string(),\n\tisRegionRestricted: z.literal(true),\n})\n\nconst CachedEpicVideoInfoSchema = z\n\t.object({\n\t\ttitle: z.string().nullable().optional(),\n\t\ttranscript: Transcript,\n\t\tmuxPlaybackId: z.string(),\n\t\tstatus: z.literal('success'),\n\t\tstatusCode: z.number(),\n\t\tstatusText: z.string(),\n\t})\n\t.or(\n\t\tz.object({\n\t\t\tstatus: z.literal('error'),\n\t\t\tstatusCode: z.number(),\n\t\t\tstatusText: z.string(),\n\t\t\ttype: z.literal('unknown'),\n\t\t}),\n\t)\n\t.or(\n\t\tz.object({\n\t\t\tstatus: z.literal('error'),\n\t\t\tstatusCode: z.number(),\n\t\t\tstatusText: z.string(),\n\t\t\ttype: z.literal('region-restricted'),\n\t\t\trequestCountry: z.string(),\n\t\t\trestrictedCountry: z.string(),\n\t\t}),\n\t)\n\t.or(z.null())\n\nexport type EpicVideoInfos = Record<\n\tstring,\n\tAwaited<ReturnType<typeof getEpicVideoInfo>>\n>\n\nexport async function getEpicVideoInfos(\n\tepicWebUrls?: Array<string> | null,\n\t{ request, timings }: { request?: Request; timings?: Timings } = {},\n) {\n\tif (!epicWebUrls) {\n\t\tlog('no epic web URLs provided, returning empty object')\n\t\treturn {}\n\t}\n\n\tconst authInfo = await getAuthInfo()\n\tif (getEnv().EPICSHOP_DEPLOYED) return {}\n\n\tlog(`fetching epic video infos for ${epicWebUrls.length} URLs`)\n\tconst epicVideoInfos: EpicVideoInfos = {}\n\tfor (const epicVideoEmbed of epicWebUrls) {\n\t\tconst epicVideoInfo = await getEpicVideoInfo({\n\t\t\tepicVideoEmbed,\n\t\t\taccessToken: authInfo?.tokenSet.access_token,\n\t\t\trequest,\n\t\t\ttimings,\n\t\t})\n\t\tif (epicVideoInfo) {\n\t\t\tepicVideoInfos[epicVideoEmbed] = epicVideoInfo\n\t\t}\n\t}\n\tlog(\n\t\t`successfully fetched ${Object.keys(epicVideoInfos).length} epic video infos`,\n\t)\n\treturn epicVideoInfos\n}\n\nasync function getEpicVideoInfo({\n\tepicVideoEmbed,\n\taccessToken,\n\trequest,\n\ttimings,\n}: {\n\tepicVideoEmbed: string\n\taccessToken?: string\n\trequest?: Request\n\ttimings?: Timings\n}) {\n\tconst tokenPortion = accessToken ? md5(accessToken) : 'unauthenticated'\n\tconst key = `epic-video-info:${tokenPortion}:${epicVideoEmbed}`\n\n\tlog(`fetching video info for URL: ${epicVideoEmbed}`)\n\treturn cachified({\n\t\tkey,\n\t\trequest,\n\t\tcache: epicApiCache,\n\t\ttimings,\n\t\tttl: 1000 * 60 * 60,\n\t\tswr: 1000 * 60 * 60 * 24 * 365 * 10,\n\t\tofflineFallbackValue: null,\n\t\tcheckValue: CachedEpicVideoInfoSchema,\n\t\tasync getFreshValue(\n\t\t\tcontext,\n\t\t): Promise<z.infer<typeof CachedEpicVideoInfoSchema>> {\n\t\t\tconst epicUrl = new URL(epicVideoEmbed)\n\t\t\tif (\n\t\t\t\tepicUrl.host !== 'www.epicweb.dev' &&\n\t\t\t\tepicUrl.host !== 'www.epicreact.dev' &&\n\t\t\t\tepicUrl.host !== 'www.epicai.pro'\n\t\t\t) {\n\t\t\t\tlog(`unsupported host for video URL: ${epicUrl.host}`)\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// this may be temporary until the /tutorials/ endpoint supports /api\n\t\t\tif (epicUrl.pathname.startsWith('/tutorials/')) {\n\t\t\t\tepicUrl.pathname = epicUrl.pathname.replace(\n\t\t\t\t\t/^\\/tutorials\\//,\n\t\t\t\t\t'/workshops/',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// special case for epicai.pro videos\n\t\t\tconst apiUrl =\n\t\t\t\tepicUrl.host === 'www.epicai.pro'\n\t\t\t\t\t? getEpicAIVideoAPIUrl(epicVideoEmbed)\n\t\t\t\t\t: `https://${epicUrl.host}/api${epicUrl.pathname}`\n\n\t\t\tlog(`making API request to: ${apiUrl}`)\n\t\t\tconst infoResponse = await fetch(\n\t\t\t\tapiUrl,\n\t\t\t\taccessToken\n\t\t\t\t\t? { headers: { authorization: `Bearer ${accessToken}` } }\n\t\t\t\t\t: undefined,\n\t\t\t)\n\t\t\tconst { status, statusText } = infoResponse\n\t\t\tlog(`API response: ${status} ${statusText}`)\n\n\t\t\tif (infoResponse.status >= 200 && infoResponse.status < 300) {\n\t\t\t\tlet rawInfo = await infoResponse.json()\n\t\t\t\t// another special case for epicai.pro videos\n\t\t\t\tif (epicUrl.host === 'www.epicai.pro') {\n\t\t\t\t\trawInfo = preprocessEpicAIVideoAPIResult(rawInfo)\n\t\t\t\t}\n\t\t\t\tconst infoResult = EpicVideoInfoSchema.safeParse(rawInfo)\n\t\t\t\tif (infoResult.success) {\n\t\t\t\t\tlog(`successfully parsed video info for ${epicVideoEmbed}`)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'success',\n\t\t\t\t\t\tstatusCode: status,\n\t\t\t\t\t\tstatusText,\n\t\t\t\t\t\t...infoResult.data,\n\t\t\t\t\t} as const\n\t\t\t\t} else {\n\t\t\t\t\t// don't cache errors for long...\n\t\t\t\t\tcontext.metadata.ttl = 1000 * 2\n\t\t\t\t\tcontext.metadata.swr = 0\n\t\t\t\t\tconst restrictedResult =\n\t\t\t\t\t\tEpicVideoRegionRestrictedErrorSchema.safeParse(rawInfo)\n\t\t\t\t\tif (restrictedResult.success) {\n\t\t\t\t\t\tlog.warn(`video is region restricted: ${epicVideoEmbed}`)\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\t\tstatusCode: status,\n\t\t\t\t\t\t\tstatusText,\n\t\t\t\t\t\t\ttype: 'region-restricted',\n\t\t\t\t\t\t\t...restrictedResult.data,\n\t\t\t\t\t\t} as const\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlog.error(`API response parsing failed for ${epicVideoEmbed}`, {\n\t\t\t\t\t\t\turl: epicUrl.pathname,\n\t\t\t\t\t\t\trawInfo,\n\t\t\t\t\t\t\tparseError: infoResult.error,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t`Response from EpicWeb for \"${epicUrl.pathname}\" does not match expectation`,\n\t\t\t\t\t\t\tinfoResult.error,\n\t\t\t\t\t\t)\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\t\tstatusCode: 500,\n\t\t\t\t\t\t\tstatusText: 'API Data Type Mismatch',\n\t\t\t\t\t\t\ttype: 'unknown',\n\t\t\t\t\t\t} as const\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// don't cache errors for long...\n\t\t\t\tcontext.metadata.ttl = 1000 * 2\n\t\t\t\tcontext.metadata.swr = 0\n\t\t\t\tlog.error(`video API request failed for ${epicVideoEmbed}`, {\n\t\t\t\t\tstatus,\n\t\t\t\t\tstatusText,\n\t\t\t\t\turl: apiUrl,\n\t\t\t\t})\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 'error',\n\t\t\t\t\tstatusCode: status,\n\t\t\t\t\tstatusText,\n\t\t\t\t\ttype: 'unknown',\n\t\t\t\t} as const\n\t\t\t}\n\t\t},\n\t}).catch((e) => {\n\t\tlog.error(`failed to fetch epic video info for ${epicVideoEmbed}:`, e)\n\t\tthrow e\n\t})\n}\n\nfunction getEpicAIVideoAPIUrl(epicVideoEmbed: string) {\n\tconst epicUrl = new URL(epicVideoEmbed)\n\tconst pathSegments = epicUrl.pathname.split('/').filter(Boolean)\n\n\tif (epicUrl.pathname.endsWith('/solution')) {\n\t\t// slug is right before 'solution'\n\t\tconst slug = pathSegments.at(-2)\n\t\tinvariant(slug, 'Expected slug before /solution in pathname')\n\t\treturn `https://www.epicai.pro/api/lessons/${slug}/solution`\n\t} else if (epicUrl.pathname.includes('/workshops')) {\n\t\tconst slug = pathSegments.at(-1)\n\t\tinvariant(slug, 'Expected slug at end of /workshops pathname')\n\t\treturn `https://www.epicai.pro/api/lessons?slugOrId=${slug}`\n\t} else {\n\t\tconst slug = pathSegments.at(-1)\n\t\tinvariant(slug, 'Expected slug at end of pathname')\n\t\treturn `https://www.epicai.pro/api/posts?slugOrId=${slug}`\n\t}\n}\n\nfunction preprocessEpicAIVideoAPIResult(result: any) {\n\tconst PostVideoResourceSchema = z.object({\n\t\tresource: z.object({\n\t\t\ttype: z.literal('videoResource'),\n\t\t\tfields: EpicVideoInfoSchema,\n\t\t}),\n\t})\n\tconst PostSchema = z.object({\n\t\tfields: z.object({ title: z.string() }),\n\t\tresources: z.array(z.any()).nullable(),\n\t})\n\tconst post = PostSchema.safeParse(result)\n\tif (!post.success) return null\n\tfor (const resource of post.data.resources ?? []) {\n\t\tconst videoResource = PostVideoResourceSchema.safeParse(resource)\n\n\t\tif (videoResource.success) {\n\t\t\treturn {\n\t\t\t\t...videoResource.data.resource.fields,\n\t\t\t\ttitle: post.data.fields.title,\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null\n}\n\nasync function getEpicProgress({\n\ttimings,\n\trequest,\n\tforceFresh,\n}: { timings?: Timings; request?: Request; forceFresh?: boolean } = {}) {\n\tif (getEnv().EPICSHOP_DEPLOYED) return []\n\n\tconst authInfo = await getAuthInfo()\n\tconst {\n\t\tproduct: { host },\n\t} = getWorkshopConfig()\n\tif (!authInfo) return []\n\n\tconst tokenPart = md5(authInfo.tokenSet.access_token)\n\tconst EpicProgressSchema = z.array(\n\t\tz.object({\n\t\t\tlessonId: z.string(),\n\t\t\tcompletedAt: z.string().nullable(),\n\t\t}),\n\t)\n\n\tlog(`fetching progress from EpicWeb host: ${host}`)\n\treturn cachified({\n\t\tkey: `epic-progress:${host}:${tokenPart}`,\n\t\tcache: epicApiCache,\n\t\trequest,\n\t\ttimings,\n\t\tforceFresh,\n\t\tttl: 1000 * 2,\n\t\tswr: 1000 * 60 * 60 * 24 * 365 * 10,\n\t\tofflineFallbackValue: [],\n\t\tcheckValue: EpicProgressSchema,\n\t\tasync getFreshValue(context): Promise<z.infer<typeof EpicProgressSchema>> {\n\t\t\tconst progressUrl = `https://${host}/api/progress`\n\t\t\tlog(`making progress API request to: ${progressUrl}`)\n\n\t\t\tconst response = await fetch(progressUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tauthorization: `Bearer ${authInfo.tokenSet.access_token}`,\n\t\t\t\t},\n\t\t\t}).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\n\n\t\t\tlog(`progress API response: ${response.status} ${response.statusText}`)\n\n\t\t\tif (response.status < 200 || response.status >= 300) {\n\t\t\t\tlog.error(\n\t\t\t\t\t`failed to fetch progress from EpicWeb: ${response.status} ${response.statusText}`,\n\t\t\t\t)\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Failed to fetch progress from EpicWeb: ${response.status} ${response.statusText}`,\n\t\t\t\t)\n\t\t\t\t// don't cache errors for long...\n\t\t\t\tcontext.metadata.ttl = 1000 * 2\n\t\t\t\tcontext.metadata.swr = 0\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tconst progressData = await response.json()\n\t\t\tconst parsedProgress = EpicProgressSchema.parse(progressData)\n\t\t\tlog(`successfully fetched ${parsedProgress.length} progress entries`)\n\t\t\treturn parsedProgress\n\t\t},\n\t})\n}\n\nexport type Progress = Awaited<ReturnType<typeof getProgress>>[number]\nexport async function getProgress({\n\ttimings,\n\trequest,\n}: {\n\ttimings?: Timings\n\trequest?: Request\n} = {}) {\n\tif (getEnv().EPICSHOP_DEPLOYED) return []\n\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) return []\n\n\tconst {\n\t\tproduct: { slug, host },\n\t} = getWorkshopConfig()\n\tif (!slug) return []\n\n\tlog(`aggregating progress data for workshop: ${slug}`)\n\tconst [\n\t\tworkshopData,\n\t\tepicProgress,\n\t\tworkshopInstructions,\n\t\tworkshopFinished,\n\t\texercises,\n\t] = await Promise.all([\n\t\tgetWorkshopData(slug, { request, timings }),\n\t\tgetEpicProgress({ request, timings }),\n\t\tgetWorkshopInstructions({ request }),\n\t\tgetWorkshopFinished({ request }),\n\t\tgetExercises({ request, timings }),\n\t])\n\n\ttype ProgressInfo = {\n\t\tepicLessonUrl: string\n\t\tepicLessonSlug: string\n\t\tepicCompletedAt: string | null\n\t}\n\tconst progress: Array<\n\t\tProgressInfo &\n\t\t\t(ReturnType<typeof getProgressForLesson> | { type: 'unknown' })\n\t> = []\n\n\tfor (const resource of workshopData.resources ?? []) {\n\t\tconst lessons = resource._type === 'section' ? resource.lessons : [resource]\n\t\tfor (const lesson of lessons) {\n\t\t\tconst epicLessonSlug = lesson.slug\n\t\t\tconst lessonProgress = epicProgress.find(\n\t\t\t\t({ lessonId }) => lessonId === lesson._id,\n\t\t\t)\n\t\t\tconst epicCompletedAt = lessonProgress ? lessonProgress.completedAt : null\n\t\t\tconst progressForLesson = getProgressForLesson(epicLessonSlug, {\n\t\t\t\tworkshopInstructions,\n\t\t\t\tworkshopFinished,\n\t\t\t\texercises,\n\t\t\t})\n\t\t\tconst epicLessonUrl = `https://${host}/workshops/${slug}/${epicLessonSlug}`\n\t\t\tif (progressForLesson) {\n\t\t\t\tprogress.push({\n\t\t\t\t\t...progressForLesson,\n\t\t\t\t\tepicLessonUrl,\n\t\t\t\t\tepicLessonSlug,\n\t\t\t\t\tepicCompletedAt,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tprogress.push({\n\t\t\t\t\ttype: 'unknown',\n\t\t\t\t\tepicLessonUrl,\n\t\t\t\t\tepicLessonSlug,\n\t\t\t\t\tepicCompletedAt,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tlog(`processed ${progress.length} progress entries for workshop: ${slug}`)\n\treturn progress\n}\n\nfunction getProgressForLesson(\n\tepicLessonSlug: string,\n\t{\n\t\tworkshopInstructions,\n\t\tworkshopFinished,\n\t\texercises,\n\t}: {\n\t\tworkshopInstructions: Awaited<ReturnType<typeof getWorkshopInstructions>>\n\t\tworkshopFinished: Awaited<ReturnType<typeof getWorkshopFinished>>\n\t\texercises: Awaited<ReturnType<typeof getExercises>>\n\t},\n) {\n\tconst hasEmbed = (embed?: Array<string>) =>\n\t\tembed?.some((e) => e.split('/').at(-1) === epicLessonSlug)\n\tif (\n\t\tworkshopInstructions.compiled.status === 'success' &&\n\t\thasEmbed(workshopInstructions.compiled.epicVideoEmbeds)\n\t) {\n\t\treturn { type: 'workshop-instructions' } as const\n\t}\n\tif (\n\t\tworkshopFinished.compiled.status === 'success' &&\n\t\thasEmbed(workshopFinished.compiled.epicVideoEmbeds)\n\t) {\n\t\treturn { type: 'workshop-finished' } as const\n\t}\n\tfor (const exercise of exercises) {\n\t\tif (hasEmbed(exercise.instructionsEpicVideoEmbeds)) {\n\t\t\treturn {\n\t\t\t\ttype: 'instructions',\n\t\t\t\texerciseNumber: exercise.exerciseNumber,\n\t\t\t} as const\n\t\t}\n\t\tif (hasEmbed(exercise.finishedEpicVideoEmbeds)) {\n\t\t\treturn {\n\t\t\t\ttype: 'finished',\n\t\t\t\texerciseNumber: exercise.exerciseNumber,\n\t\t\t} as const\n\t\t}\n\t\tfor (const step of exercise.steps.filter(Boolean)) {\n\t\t\tif (hasEmbed(step.problem?.epicVideoEmbeds)) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'step',\n\t\t\t\t\texerciseNumber: exercise.exerciseNumber,\n\t\t\t\t\tstepNumber: step.stepNumber,\n\t\t\t\t} as const\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport async function updateProgress(\n\t{ lessonSlug, complete }: { lessonSlug: string; complete?: boolean },\n\t{\n\t\ttimings,\n\t\trequest,\n\t}: {\n\t\ttimings?: Timings\n\t\trequest?: Request\n\t} = {},\n) {\n\tif (getEnv().EPICSHOP_DEPLOYED) {\n\t\treturn {\n\t\t\tstatus: 'error',\n\t\t\terror: 'cannot update progress when deployed',\n\t\t} as const\n\t}\n\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) {\n\t\treturn { status: 'error', error: 'not authenticated' } as const\n\t}\n\n\tconst {\n\t\tproduct: { host },\n\t} = getWorkshopConfig()\n\n\tconst progressUrl = `https://${host}/api/progress`\n\tconst payload = complete ? { lessonSlug } : { lessonSlug, remove: true }\n\n\tlog(`updating progress for lesson: ${lessonSlug} (complete: ${complete})`)\n\tlog(\n\t\t`making POST request to: ${progressUrl} with payload: ${JSON.stringify(payload)}`,\n\t)\n\n\tconst response = await fetch(progressUrl, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\tauthorization: `Bearer ${authInfo.tokenSet.access_token}`,\n\t\t\t'content-type': 'application/json',\n\t\t},\n\t\tbody: JSON.stringify(payload),\n\t}).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\n\n\tlog(`progress update response: ${response.status} ${response.statusText}`)\n\n\t// force the progress to be fresh whether or not we're successful\n\tawait getEpicProgress({ forceFresh: true, request, timings })\n\n\tif (response.status < 200 || response.status >= 300) {\n\t\tlog(`progress update failed: ${response.status} ${response.statusText}`)\n\t\treturn {\n\t\t\tstatus: 'error',\n\t\t\terror: `${response.status} ${response.statusText}`,\n\t\t} as const\n\t}\n\n\tlog(`progress update successful for lesson: ${lessonSlug}`)\n\treturn { status: 'success' } as const\n}\n\nconst ModuleSchema = z.object({\n\tresources: z\n\t\t.array(\n\t\t\tz.union([\n\t\t\t\tz.object({\n\t\t\t\t\t_type: z.literal('lesson'),\n\t\t\t\t\t_id: z.string(),\n\t\t\t\t\tslug: z.string(),\n\t\t\t\t}),\n\t\t\t\tz.object({\n\t\t\t\t\t_type: z.literal('section'),\n\t\t\t\t\tlessons: z.array(z.object({ _id: z.string(), slug: z.string() })),\n\t\t\t\t}),\n\t\t\t]),\n\t\t)\n\t\t.nullable(),\n})\n\nexport async function getWorkshopData(\n\tslug: string,\n\t{\n\t\ttimings,\n\t\trequest,\n\t\tforceFresh,\n\t}: {\n\t\ttimings?: Timings\n\t\trequest?: Request\n\t\tforceFresh?: boolean\n\t} = {},\n) {\n\tif (getEnv().EPICSHOP_DEPLOYED) return { resources: [] }\n\n\tconst authInfo = await getAuthInfo()\n\t// auth is not required, but we only use it for progress which is only needed\n\t// if you're authenticated anyway.\n\tif (!authInfo) return { resources: [] }\n\n\tconst {\n\t\tproduct: { host },\n\t} = getWorkshopConfig()\n\n\tlog(`fetching workshop data for slug: ${slug} from host: ${host}`)\n\treturn cachified({\n\t\tkey: `epic-workshop-data:${host}:${slug}`,\n\t\tttl: 1000 * 60 * 5,\n\t\tswr: 1000 * 60 * 60 * 24 * 365 * 10,\n\t\tcache: epicApiCache,\n\t\trequest,\n\t\tforceFresh,\n\t\ttimings,\n\t\tofflineFallbackValue: { resources: [] },\n\t\tcheckValue: ModuleSchema,\n\t\tasync getFreshValue(): Promise<z.infer<typeof ModuleSchema>> {\n\t\t\tconst workshopUrl = `https://${host}/api/workshops/${encodeURIComponent(slug)}`\n\t\t\tlog(`making workshop data request to: ${workshopUrl}`)\n\n\t\t\tconst response = await fetch(workshopUrl).catch(\n\t\t\t\t(e) => new Response(getErrorMessage(e), { status: 500 }),\n\t\t\t)\n\n\t\t\tlog(`workshop data response: ${response.status} ${response.statusText}`)\n\n\t\t\tif (response.status < 200 || response.status >= 300) {\n\t\t\t\tlog.error(\n\t\t\t\t\t`failed to fetch workshop data from EpicWeb for ${slug}: ${response.status} ${response.statusText}`,\n\t\t\t\t)\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Failed to fetch workshop data from EpicWeb for ${slug}: ${response.status} ${response.statusText}`,\n\t\t\t\t)\n\t\t\t\treturn { resources: [] }\n\t\t\t}\n\n\t\t\tconst jsonResponse = await response.json()\n\t\t\tconst parsedData = ModuleSchema.parse(jsonResponse)\n\t\t\tlog(\n\t\t\t\t`successfully fetched workshop data for ${slug} with ${parsedData.resources?.length ?? 0} resources`,\n\t\t\t)\n\t\t\treturn parsedData\n\t\t},\n\t})\n}\n\nexport async function userHasAccessToWorkshop({\n\ttimings,\n\trequest,\n\tforceFresh,\n}: {\n\trequest?: Request\n\ttimings?: Timings\n\tforceFresh?: boolean\n} = {}) {\n\tconst config = getWorkshopConfig()\n\tconst {\n\t\tproduct: { host, slug },\n\t} = config\n\tif (!slug) return true\n\n\tif (getEnv().EPICSHOP_DEPLOYED) {\n\t\tconst cookieHeader = request?.headers.get('Cookie')\n\t\tif (!cookieHeader) return false\n\t\tconst cookies = cookie.parse(cookieHeader)\n\t\treturn cookies.skill?.split(',').includes(slug) ?? false\n\t}\n\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) return false\n\n\treturn cachified({\n\t\tkey: `user-has-access-to-workshop:${host}:${slug}`,\n\t\tcache: epicApiCache,\n\t\trequest,\n\t\tforceFresh,\n\t\ttimings,\n\t\tttl: 1000 * 5,\n\t\tofflineFallbackValue: false,\n\t\tcheckValue: z.boolean(),\n\t\tasync getFreshValue(context) {\n\t\t\tconst accessUrl = `https://${host}/api/workshops/${encodeURIComponent(slug)}/access`\n\t\t\tlog(`checking workshop access via API: ${accessUrl}`)\n\n\t\t\tconst response = await fetch(accessUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tauthorization: `Bearer ${authInfo.tokenSet.access_token}`,\n\t\t\t\t},\n\t\t\t}).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\n\n\t\t\tlog(\n\t\t\t\t`workshop access API response: ${response.status} ${response.statusText}`,\n\t\t\t)\n\n\t\t\tconst hasAccess = response.ok ? (await response.json()) === true : false\n\t\t\tlog(`workshop access result for ${slug}: ${hasAccess}`)\n\n\t\t\tif (hasAccess) {\n\t\t\t\tcontext.metadata.ttl = 1000 * 60 * 5\n\t\t\t\tcontext.metadata.swr = 1000 * 60 * 60 * 24 * 365 * 10\n\t\t\t}\n\n\t\t\treturn hasAccess\n\t\t},\n\t}).catch((e) => {\n\t\tconsole.error('Failed to check workshop access', e)\n\t\treturn false\n\t})\n}\n\nconst UserInfoSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tname: z.string().nullable(),\n\t\temail: z.string().email(),\n\t\timage: z.string().nullable(),\n\t\tdiscordProfile: z\n\t\t\t.object({\n\t\t\t\tnick: z.string().nullable().optional(),\n\t\t\t\tuser: z\n\t\t\t\t\t.object({\n\t\t\t\t\t\tid: z.string(),\n\t\t\t\t\t\tusername: z.string(),\n\t\t\t\t\t\tavatar: z.string().nullable().optional(),\n\t\t\t\t\t\tglobal_name: z.string().nullable().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.optional(),\n\t\t\t})\n\t\t\t.nullable()\n\t\t\t.optional(),\n\t})\n\t.transform((data) => {\n\t\treturn {\n\t\t\t...data,\n\t\t\timageUrlSmall:\n\t\t\t\tresizeImageUrl(data.image, { size: 64 }) ??\n\t\t\t\tresolveDiscordAvatar(data.discordProfile?.user, {\n\t\t\t\t\tsize: 64,\n\t\t\t\t}) ??\n\t\t\t\tresolveGravatarUrl(data.email, { size: 64 }),\n\t\t\timageUrlLarge:\n\t\t\t\tresizeImageUrl(data.image, { size: 512 }) ??\n\t\t\t\tresolveDiscordAvatar(data.discordProfile?.user, {\n\t\t\t\t\tsize: 512,\n\t\t\t\t}) ??\n\t\t\t\tresolveGravatarUrl(data.email, { size: 512 }),\n\t\t}\n\t})\n\nfunction resizeImageUrl(url: string | null, { size }: { size: number }) {\n\tif (!url) return null\n\tconst urlObj = new URL(url)\n\turlObj.searchParams.set('size', size.toString())\n\treturn urlObj.toString()\n}\n\nfunction resolveGravatarUrl(\n\temail: string | undefined,\n\t{ size }: { size: number },\n) {\n\tif (!email) return null\n\n\tconst hash = md5(email.toLowerCase())\n\tconst gravatarOptions = new URLSearchParams({\n\t\tsize: size.toString(),\n\t\tdefault: 'identicon',\n\t})\n\treturn `https://www.gravatar.com/avatar/${hash}?${gravatarOptions.toString()}`\n}\n\nfunction resolveDiscordAvatar(\n\tuser: { avatar?: string | null; id: string } | undefined,\n\t{ size }: { size: 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 },\n) {\n\tif (!user) return null\n\n\tconst { avatar, id: userId } = user\n\tif (!avatar) return null\n\tconst isGif = avatar.startsWith('a_')\n\tconst url = new URL(\n\t\t`/avatars/${userId}/${avatar}.${isGif ? 'gif' : 'png'}`,\n\t\t'https://cdn.discordapp.com',\n\t)\n\turl.searchParams.set('size', size.toString())\n\treturn url.toString()\n}\n\nexport type UserInfo = z.infer<typeof UserInfoSchema>\n\nexport async function getUserInfo({\n\ttimings,\n\trequest,\n\tforceFresh,\n}: {\n\ttimings?: Timings\n\trequest?: Request\n\tforceFresh?: boolean\n} = {}) {\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) return null\n\n\tconst { tokenSet } = authInfo\n\tconst {\n\t\tproduct: { host },\n\t} = getWorkshopConfig()\n\n\tconst accessToken = tokenSet.access_token\n\tconst url = `https://${host}/oauth/userinfo`\n\n\tlog('fetching user info from: %s', url)\n\tconst userInfo = await cachified({\n\t\tkey: `${url}:${md5(accessToken)}`,\n\t\tcache: epicApiCache,\n\t\trequest,\n\t\tforceFresh,\n\t\ttimings,\n\t\tttl: 1000 * 30,\n\t\tswr: 1000 * 60 * 60 * 24 * 365 * 10,\n\t\tofflineFallbackValue: null,\n\t\tcheckValue: UserInfoSchema,\n\t\tasync getFreshValue(): Promise<UserInfo> {\n\t\t\tlog(`making user info API request to: ${url}`)\n\n\t\t\tconst response = await fetch(url, {\n\t\t\t\theaders: { authorization: `Bearer ${accessToken}` },\n\t\t\t}).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\n\n\t\t\tlog(`user info API response: ${response.status} ${response.statusText}`)\n\n\t\t\tif (!response.ok) {\n\t\t\t\tlog(\n\t\t\t\t\t`user info API request failed: ${response.status} ${response.statusText}`,\n\t\t\t\t)\n\t\t\t\tif (\n\t\t\t\t\tresponse.headers.get('content-type')?.includes('application/json')\n\t\t\t\t) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthrow new Error(`Failed to fetch user info: ${JSON.stringify(data)}`)\n\t\t\t\t} else {\n\t\t\t\t\tconst text = await response.text()\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Failed to fetch user info: ${text || response.statusText}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data = await response.json()\n\t\t\tconst parsedUserInfo = UserInfoSchema.parse(data)\n\t\t\tlog(\n\t\t\t\t`successfully fetched user info for user: ${parsedUserInfo.id} (${parsedUserInfo.email})`,\n\t\t\t)\n\t\t\treturn parsedUserInfo\n\t\t},\n\t}).catch((e) => {\n\t\tlog.error(`failed to get user info:`, e)\n\t\treturn null\n\t})\n\n\t// we used to md5 hash the email to get the id\n\t// if the id doesn't match what we have on file, update it\n\t// you can probably safely remove this in January 2025\n\tif (userInfo && authInfo.id !== userInfo.id) {\n\t\tawait setAuthInfo({\n\t\t\t...authInfo,\n\t\t\tid: userInfo.id,\n\t\t})\n\t}\n\n\treturn userInfo\n}\n\nexport async function warmCache() {\n\tawait Promise.all([getUserInfo(), getProgress()])\n}\n"]}
|
|
1
|
+
{"version":3,"file":"epic-api.server.js","sourceRoot":"","sources":["../../src/epic-api.server.ts"],"names":[],"mappings":"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","sourcesContent":["import { invariant } from '@epic-web/invariant'\nimport * as cookie from 'cookie'\n\nimport md5 from 'md5-hex'\nimport { z } from 'zod'\nimport {\n\tgetExercises,\n\tgetWorkshopFinished,\n\tgetWorkshopInstructions,\n} from './apps.server.js'\nimport { cachified, epicApiCache } from './cache.server.js'\nimport { getWorkshopConfig } from './config.server.js'\nimport { getAuthInfo, setAuthInfo } from './db.server.js'\nimport { getEnv } from './init-env.js'\nimport { logger } from './logger.js'\nimport { type Timings } from './timing.server.js'\nimport { getErrorMessage } from './utils.js'\n\n// Module-level logger for epic-api operations\nconst log = logger('epic:api')\n\nconst Transcript = z\n\t.string()\n\t.nullable()\n\t.optional()\n\t.transform((s) => s ?? 'Transcripts not available')\nconst EpicVideoInfoSchema = z.object({\n\ttitle: z.string().nullable().optional(),\n\ttranscript: Transcript,\n\tmuxPlaybackId: z.string(),\n})\n\nconst EpicVideoRegionRestrictedErrorSchema = z.object({\n\trequestCountry: z.string(),\n\trestrictedCountry: z.string(),\n\tisRegionRestricted: z.literal(true),\n})\n\nconst CachedEpicVideoInfoSchema = z\n\t.object({\n\t\ttitle: z.string().nullable().optional(),\n\t\ttranscript: Transcript,\n\t\tmuxPlaybackId: z.string(),\n\t\tstatus: z.literal('success'),\n\t\tstatusCode: z.number(),\n\t\tstatusText: z.string(),\n\t})\n\t.or(\n\t\tz.object({\n\t\t\tstatus: z.literal('error'),\n\t\t\tstatusCode: z.number(),\n\t\t\tstatusText: z.string(),\n\t\t\ttype: z.literal('unknown'),\n\t\t}),\n\t)\n\t.or(\n\t\tz.object({\n\t\t\tstatus: z.literal('error'),\n\t\t\tstatusCode: z.number(),\n\t\t\tstatusText: z.string(),\n\t\t\ttype: z.literal('region-restricted'),\n\t\t\trequestCountry: z.string(),\n\t\t\trestrictedCountry: z.string(),\n\t\t}),\n\t)\n\t.or(z.null())\n\nexport type EpicVideoInfos = Record<\n\tstring,\n\tAwaited<ReturnType<typeof getEpicVideoInfo>>\n>\n\nconst videoInfoLog = log.logger('video-info')\n\nexport async function getEpicVideoInfos(\n\tepicWebUrls?: Array<string> | null,\n\t{ request, timings }: { request?: Request; timings?: Timings } = {},\n) {\n\tif (!epicWebUrls) {\n\t\tvideoInfoLog.warn('no epic web URLs provided, returning empty object')\n\t\treturn {}\n\t}\n\n\tconst authInfo = await getAuthInfo()\n\tif (getEnv().EPICSHOP_DEPLOYED) return {}\n\n\tvideoInfoLog(`fetching epic video infos for ${epicWebUrls.length} URLs`)\n\tconst epicVideoInfos: EpicVideoInfos = {}\n\tfor (const epicVideoEmbed of epicWebUrls) {\n\t\tconst epicVideoInfo = await getEpicVideoInfo({\n\t\t\tepicVideoEmbed,\n\t\t\taccessToken: authInfo?.tokenSet.access_token,\n\t\t\trequest,\n\t\t\ttimings,\n\t\t})\n\t\tif (epicVideoInfo) {\n\t\t\tepicVideoInfos[epicVideoEmbed] = epicVideoInfo\n\t\t}\n\t}\n\tvideoInfoLog(\n\t\t`successfully fetched ${Object.keys(epicVideoInfos).length} epic video infos`,\n\t)\n\treturn epicVideoInfos\n}\n\nasync function getEpicVideoInfo({\n\tepicVideoEmbed,\n\taccessToken,\n\trequest,\n\ttimings,\n}: {\n\tepicVideoEmbed: string\n\taccessToken?: string\n\trequest?: Request\n\ttimings?: Timings\n}) {\n\tconst tokenPortion = accessToken ? md5(accessToken) : 'unauthenticated'\n\tconst key = `epic-video-info:${tokenPortion}:${epicVideoEmbed}`\n\n\tvideoInfoLog(`fetching video info for URL: ${epicVideoEmbed}`)\n\treturn cachified({\n\t\tkey,\n\t\trequest,\n\t\tcache: epicApiCache,\n\t\ttimings,\n\t\tttl: 1000 * 60 * 60,\n\t\tswr: 1000 * 60 * 60 * 24 * 365 * 10,\n\t\tofflineFallbackValue: null,\n\t\tcheckValue: CachedEpicVideoInfoSchema,\n\t\tasync getFreshValue(\n\t\t\tcontext,\n\t\t): Promise<z.infer<typeof CachedEpicVideoInfoSchema>> {\n\t\t\tconst epicUrl = new URL(epicVideoEmbed)\n\t\t\tif (\n\t\t\t\tepicUrl.host !== 'www.epicweb.dev' &&\n\t\t\t\tepicUrl.host !== 'www.epicreact.dev' &&\n\t\t\t\tepicUrl.host !== 'www.epicai.pro'\n\t\t\t) {\n\t\t\t\tvideoInfoLog.error(`unsupported host for video URL: ${epicUrl.host}`)\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// this may be temporary until the /tutorials/ endpoint supports /api\n\t\t\tif (epicUrl.pathname.startsWith('/tutorials/')) {\n\t\t\t\tepicUrl.pathname = epicUrl.pathname.replace(\n\t\t\t\t\t/^\\/tutorials\\//,\n\t\t\t\t\t'/workshops/',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// special case for epicai.pro videos\n\t\t\tconst apiUrl =\n\t\t\t\tepicUrl.host === 'www.epicai.pro'\n\t\t\t\t\t? getEpicAIVideoAPIUrl(epicVideoEmbed)\n\t\t\t\t\t: `https://${epicUrl.host}/api${epicUrl.pathname}`\n\n\t\t\tvideoInfoLog(`making API request to: ${apiUrl}`)\n\t\t\tconst infoResponse = await fetch(\n\t\t\t\tapiUrl,\n\t\t\t\taccessToken\n\t\t\t\t\t? { headers: { authorization: `Bearer ${accessToken}` } }\n\t\t\t\t\t: undefined,\n\t\t\t)\n\t\t\tconst { status, statusText } = infoResponse\n\t\t\tvideoInfoLog(`API response: ${status} ${statusText}`)\n\n\t\t\tif (infoResponse.status >= 200 && infoResponse.status < 300) {\n\t\t\t\tlet rawInfo = await infoResponse.json()\n\t\t\t\t// another special case for epicai.pro videos\n\t\t\t\tif (epicUrl.host === 'www.epicai.pro') {\n\t\t\t\t\trawInfo = preprocessEpicAIVideoAPIResult(rawInfo)\n\t\t\t\t}\n\t\t\t\tconst infoResult = EpicVideoInfoSchema.safeParse(rawInfo)\n\t\t\t\tif (infoResult.success) {\n\t\t\t\t\tvideoInfoLog(`successfully parsed video info for ${epicVideoEmbed}`)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'success',\n\t\t\t\t\t\tstatusCode: status,\n\t\t\t\t\t\tstatusText,\n\t\t\t\t\t\t...infoResult.data,\n\t\t\t\t\t} as const\n\t\t\t\t} else {\n\t\t\t\t\t// don't cache errors for long...\n\t\t\t\t\tcontext.metadata.ttl = 1000 * 2\n\t\t\t\t\tcontext.metadata.swr = 0\n\t\t\t\t\tconst restrictedResult =\n\t\t\t\t\t\tEpicVideoRegionRestrictedErrorSchema.safeParse(rawInfo)\n\t\t\t\t\tif (restrictedResult.success) {\n\t\t\t\t\t\tvideoInfoLog.warn(`video is region restricted: ${epicVideoEmbed}`)\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\t\tstatusCode: status,\n\t\t\t\t\t\t\tstatusText,\n\t\t\t\t\t\t\ttype: 'region-restricted',\n\t\t\t\t\t\t\t...restrictedResult.data,\n\t\t\t\t\t\t} as const\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvideoInfoLog.error(\n\t\t\t\t\t\t\t`API response parsing failed for ${epicVideoEmbed}`,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\turl: epicUrl.pathname,\n\t\t\t\t\t\t\t\trawInfo,\n\t\t\t\t\t\t\t\tparseError: infoResult.error,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t)\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\t\tstatusCode: 500,\n\t\t\t\t\t\t\tstatusText: 'API Data Type Mismatch',\n\t\t\t\t\t\t\ttype: 'unknown',\n\t\t\t\t\t\t} as const\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// don't cache errors for long...\n\t\t\t\tcontext.metadata.ttl = 1000 * 2\n\t\t\t\tcontext.metadata.swr = 0\n\t\t\t\tvideoInfoLog.error(`video API request failed for ${epicVideoEmbed}`, {\n\t\t\t\t\tstatus,\n\t\t\t\t\tstatusText,\n\t\t\t\t\turl: apiUrl,\n\t\t\t\t})\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 'error',\n\t\t\t\t\tstatusCode: status,\n\t\t\t\t\tstatusText,\n\t\t\t\t\ttype: 'unknown',\n\t\t\t\t} as const\n\t\t\t}\n\t\t},\n\t}).catch((e) => {\n\t\tvideoInfoLog.error(\n\t\t\t`failed to fetch epic video info for ${epicVideoEmbed}:`,\n\t\t\te,\n\t\t)\n\t\tthrow e\n\t})\n}\n\nfunction getEpicAIVideoAPIUrl(epicVideoEmbed: string) {\n\tconst epicUrl = new URL(epicVideoEmbed)\n\tconst pathSegments = epicUrl.pathname.split('/').filter(Boolean)\n\n\tif (epicUrl.pathname.endsWith('/solution')) {\n\t\t// slug is right before 'solution'\n\t\tconst slug = pathSegments.at(-2)\n\t\tinvariant(slug, 'Expected slug before /solution in pathname')\n\t\treturn `https://www.epicai.pro/api/lessons/${slug}/solution`\n\t} else if (epicUrl.pathname.includes('/workshops')) {\n\t\tconst slug = pathSegments.at(-1)\n\t\tinvariant(slug, 'Expected slug at end of /workshops pathname')\n\t\treturn `https://www.epicai.pro/api/lessons?slugOrId=${slug}`\n\t} else {\n\t\tconst slug = pathSegments.at(-1)\n\t\tinvariant(slug, 'Expected slug at end of pathname')\n\t\treturn `https://www.epicai.pro/api/posts?slugOrId=${slug}`\n\t}\n}\n\nfunction preprocessEpicAIVideoAPIResult(result: any) {\n\tconst PostVideoResourceSchema = z.object({\n\t\tresource: z.object({\n\t\t\ttype: z.literal('videoResource'),\n\t\t\tfields: EpicVideoInfoSchema,\n\t\t}),\n\t})\n\tconst PostSchema = z.object({\n\t\tfields: z.object({ title: z.string() }),\n\t\tresources: z.array(z.any()).nullable(),\n\t})\n\tconst post = PostSchema.safeParse(result)\n\tif (!post.success) return null\n\tfor (const resource of post.data.resources ?? []) {\n\t\tconst videoResource = PostVideoResourceSchema.safeParse(resource)\n\n\t\tif (videoResource.success) {\n\t\t\treturn {\n\t\t\t\t...videoResource.data.resource.fields,\n\t\t\t\ttitle: post.data.fields.title,\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null\n}\n\nasync function getEpicProgress({\n\ttimings,\n\trequest,\n\tforceFresh,\n}: { timings?: Timings; request?: Request; forceFresh?: boolean } = {}) {\n\tif (getEnv().EPICSHOP_DEPLOYED) return []\n\n\tconst authInfo = await getAuthInfo()\n\tconst {\n\t\tproduct: { host },\n\t} = getWorkshopConfig()\n\tif (!authInfo) return []\n\n\tconst tokenPart = md5(authInfo.tokenSet.access_token)\n\tconst EpicProgressSchema = z.array(\n\t\tz.object({\n\t\t\tlessonId: z.string(),\n\t\t\tcompletedAt: z.string().nullable(),\n\t\t}),\n\t)\n\n\tlog(`fetching progress from EpicWeb host: ${host}`)\n\treturn cachified({\n\t\tkey: `epic-progress:${host}:${tokenPart}`,\n\t\tcache: epicApiCache,\n\t\trequest,\n\t\ttimings,\n\t\tforceFresh,\n\t\tttl: 1000 * 2,\n\t\tswr: 1000 * 60 * 60 * 24 * 365 * 10,\n\t\tofflineFallbackValue: [],\n\t\tcheckValue: EpicProgressSchema,\n\t\tasync getFreshValue(context): Promise<z.infer<typeof EpicProgressSchema>> {\n\t\t\tconst progressUrl = `https://${host}/api/progress`\n\t\t\tlog(`making progress API request to: ${progressUrl}`)\n\n\t\t\tconst response = await fetch(progressUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tauthorization: `Bearer ${authInfo.tokenSet.access_token}`,\n\t\t\t\t},\n\t\t\t}).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\n\n\t\t\tlog(`progress API response: ${response.status} ${response.statusText}`)\n\n\t\t\tif (response.status < 200 || response.status >= 300) {\n\t\t\t\tlog.error(\n\t\t\t\t\t`failed to fetch progress from EpicWeb: ${response.status} ${response.statusText}`,\n\t\t\t\t)\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Failed to fetch progress from EpicWeb: ${response.status} ${response.statusText}`,\n\t\t\t\t)\n\t\t\t\t// don't cache errors for long...\n\t\t\t\tcontext.metadata.ttl = 1000 * 2\n\t\t\t\tcontext.metadata.swr = 0\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tconst progressData = await response.json()\n\t\t\tconst parsedProgress = EpicProgressSchema.parse(progressData)\n\t\t\tlog(`successfully fetched ${parsedProgress.length} progress entries`)\n\t\t\treturn parsedProgress\n\t\t},\n\t})\n}\n\nexport type Progress = Awaited<ReturnType<typeof getProgress>>[number]\nexport async function getProgress({\n\ttimings,\n\trequest,\n}: {\n\ttimings?: Timings\n\trequest?: Request\n} = {}) {\n\tif (getEnv().EPICSHOP_DEPLOYED) return []\n\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) return []\n\n\tconst {\n\t\tproduct: { slug, host },\n\t} = getWorkshopConfig()\n\tif (!slug) return []\n\n\tlog(`aggregating progress data for workshop: ${slug}`)\n\tconst [\n\t\tworkshopData,\n\t\tepicProgress,\n\t\tworkshopInstructions,\n\t\tworkshopFinished,\n\t\texercises,\n\t] = await Promise.all([\n\t\tgetWorkshopData(slug, { request, timings }),\n\t\tgetEpicProgress({ request, timings }),\n\t\tgetWorkshopInstructions({ request }),\n\t\tgetWorkshopFinished({ request }),\n\t\tgetExercises({ request, timings }),\n\t])\n\n\ttype ProgressInfo = {\n\t\tepicLessonUrl: string\n\t\tepicLessonSlug: string\n\t\tepicCompletedAt: string | null\n\t}\n\tconst progress: Array<\n\t\tProgressInfo &\n\t\t\t(ReturnType<typeof getProgressForLesson> | { type: 'unknown' })\n\t> = []\n\n\tfor (const resource of workshopData.resources ?? []) {\n\t\tconst lessons = resource._type === 'section' ? resource.lessons : [resource]\n\t\tfor (const lesson of lessons) {\n\t\t\tconst epicLessonSlug = lesson.slug\n\t\t\tconst lessonProgress = epicProgress.find(\n\t\t\t\t({ lessonId }) => lessonId === lesson._id,\n\t\t\t)\n\t\t\tconst epicCompletedAt = lessonProgress ? lessonProgress.completedAt : null\n\t\t\tconst progressForLesson = getProgressForLesson(epicLessonSlug, {\n\t\t\t\tworkshopInstructions,\n\t\t\t\tworkshopFinished,\n\t\t\t\texercises,\n\t\t\t})\n\t\t\tconst epicLessonUrl = `https://${host}/workshops/${slug}/${epicLessonSlug}`\n\t\t\tif (progressForLesson) {\n\t\t\t\tprogress.push({\n\t\t\t\t\t...progressForLesson,\n\t\t\t\t\tepicLessonUrl,\n\t\t\t\t\tepicLessonSlug,\n\t\t\t\t\tepicCompletedAt,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tprogress.push({\n\t\t\t\t\ttype: 'unknown',\n\t\t\t\t\tepicLessonUrl,\n\t\t\t\t\tepicLessonSlug,\n\t\t\t\t\tepicCompletedAt,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tlog(`processed ${progress.length} progress entries for workshop: ${slug}`)\n\treturn progress\n}\n\nfunction getProgressForLesson(\n\tepicLessonSlug: string,\n\t{\n\t\tworkshopInstructions,\n\t\tworkshopFinished,\n\t\texercises,\n\t}: {\n\t\tworkshopInstructions: Awaited<ReturnType<typeof getWorkshopInstructions>>\n\t\tworkshopFinished: Awaited<ReturnType<typeof getWorkshopFinished>>\n\t\texercises: Awaited<ReturnType<typeof getExercises>>\n\t},\n) {\n\tconst hasEmbed = (embed?: Array<string>) =>\n\t\tembed?.some((e) => e.split('/').at(-1) === epicLessonSlug)\n\tif (\n\t\tworkshopInstructions.compiled.status === 'success' &&\n\t\thasEmbed(workshopInstructions.compiled.epicVideoEmbeds)\n\t) {\n\t\treturn { type: 'workshop-instructions' } as const\n\t}\n\tif (\n\t\tworkshopFinished.compiled.status === 'success' &&\n\t\thasEmbed(workshopFinished.compiled.epicVideoEmbeds)\n\t) {\n\t\treturn { type: 'workshop-finished' } as const\n\t}\n\tfor (const exercise of exercises) {\n\t\tif (hasEmbed(exercise.instructionsEpicVideoEmbeds)) {\n\t\t\treturn {\n\t\t\t\ttype: 'instructions',\n\t\t\t\texerciseNumber: exercise.exerciseNumber,\n\t\t\t} as const\n\t\t}\n\t\tif (hasEmbed(exercise.finishedEpicVideoEmbeds)) {\n\t\t\treturn {\n\t\t\t\ttype: 'finished',\n\t\t\t\texerciseNumber: exercise.exerciseNumber,\n\t\t\t} as const\n\t\t}\n\t\tfor (const step of exercise.steps.filter(Boolean)) {\n\t\t\tif (hasEmbed(step.problem?.epicVideoEmbeds)) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'step',\n\t\t\t\t\texerciseNumber: exercise.exerciseNumber,\n\t\t\t\t\tstepNumber: step.stepNumber,\n\t\t\t\t} as const\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport async function updateProgress(\n\t{ lessonSlug, complete }: { lessonSlug: string; complete?: boolean },\n\t{\n\t\ttimings,\n\t\trequest,\n\t}: {\n\t\ttimings?: Timings\n\t\trequest?: Request\n\t} = {},\n) {\n\tif (getEnv().EPICSHOP_DEPLOYED) {\n\t\treturn {\n\t\t\tstatus: 'error',\n\t\t\terror: 'cannot update progress when deployed',\n\t\t} as const\n\t}\n\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) {\n\t\treturn { status: 'error', error: 'not authenticated' } as const\n\t}\n\n\tconst {\n\t\tproduct: { host },\n\t} = getWorkshopConfig()\n\n\tconst progressUrl = `https://${host}/api/progress`\n\tconst payload = complete ? { lessonSlug } : { lessonSlug, remove: true }\n\n\tlog(`updating progress for lesson: ${lessonSlug} (complete: ${complete})`)\n\tlog(\n\t\t`making POST request to: ${progressUrl} with payload: ${JSON.stringify(payload)}`,\n\t)\n\n\tconst response = await fetch(progressUrl, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\tauthorization: `Bearer ${authInfo.tokenSet.access_token}`,\n\t\t\t'content-type': 'application/json',\n\t\t},\n\t\tbody: JSON.stringify(payload),\n\t}).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\n\n\tlog(`progress update response: ${response.status} ${response.statusText}`)\n\n\t// force the progress to be fresh whether or not we're successful\n\tawait getEpicProgress({ forceFresh: true, request, timings })\n\n\tif (response.status < 200 || response.status >= 300) {\n\t\tlog(`progress update failed: ${response.status} ${response.statusText}`)\n\t\treturn {\n\t\t\tstatus: 'error',\n\t\t\terror: `${response.status} ${response.statusText}`,\n\t\t} as const\n\t}\n\n\tlog(`progress update successful for lesson: ${lessonSlug}`)\n\treturn { status: 'success' } as const\n}\n\nconst ModuleSchema = z.object({\n\tresources: z\n\t\t.array(\n\t\t\tz.union([\n\t\t\t\tz.object({\n\t\t\t\t\t_type: z.literal('lesson'),\n\t\t\t\t\t_id: z.string(),\n\t\t\t\t\tslug: z.string(),\n\t\t\t\t}),\n\t\t\t\tz.object({\n\t\t\t\t\t_type: z.literal('section'),\n\t\t\t\t\tlessons: z.array(z.object({ _id: z.string(), slug: z.string() })),\n\t\t\t\t}),\n\t\t\t]),\n\t\t)\n\t\t.nullable(),\n})\n\nexport async function getWorkshopData(\n\tslug: string,\n\t{\n\t\ttimings,\n\t\trequest,\n\t\tforceFresh,\n\t}: {\n\t\ttimings?: Timings\n\t\trequest?: Request\n\t\tforceFresh?: boolean\n\t} = {},\n) {\n\tif (getEnv().EPICSHOP_DEPLOYED) return { resources: [] }\n\n\tconst authInfo = await getAuthInfo()\n\t// auth is not required, but we only use it for progress which is only needed\n\t// if you're authenticated anyway.\n\tif (!authInfo) return { resources: [] }\n\n\tconst {\n\t\tproduct: { host },\n\t} = getWorkshopConfig()\n\n\tlog(`fetching workshop data for slug: ${slug} from host: ${host}`)\n\treturn cachified({\n\t\tkey: `epic-workshop-data:${host}:${slug}`,\n\t\tttl: 1000 * 60 * 5,\n\t\tswr: 1000 * 60 * 60 * 24 * 365 * 10,\n\t\tcache: epicApiCache,\n\t\trequest,\n\t\tforceFresh,\n\t\ttimings,\n\t\tofflineFallbackValue: { resources: [] },\n\t\tcheckValue: ModuleSchema,\n\t\tasync getFreshValue(): Promise<z.infer<typeof ModuleSchema>> {\n\t\t\tconst workshopUrl = `https://${host}/api/workshops/${encodeURIComponent(slug)}`\n\t\t\tlog(`making workshop data request to: ${workshopUrl}`)\n\n\t\t\tconst response = await fetch(workshopUrl).catch(\n\t\t\t\t(e) => new Response(getErrorMessage(e), { status: 500 }),\n\t\t\t)\n\n\t\t\tlog(`workshop data response: ${response.status} ${response.statusText}`)\n\n\t\t\tif (response.status < 200 || response.status >= 300) {\n\t\t\t\tlog.error(\n\t\t\t\t\t`failed to fetch workshop data from EpicWeb for ${slug}: ${response.status} ${response.statusText}`,\n\t\t\t\t)\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Failed to fetch workshop data from EpicWeb for ${slug}: ${response.status} ${response.statusText}`,\n\t\t\t\t)\n\t\t\t\treturn { resources: [] }\n\t\t\t}\n\n\t\t\tconst jsonResponse = await response.json()\n\t\t\tconst parsedData = ModuleSchema.parse(jsonResponse)\n\t\t\tlog(\n\t\t\t\t`successfully fetched workshop data for ${slug} with ${parsedData.resources?.length ?? 0} resources`,\n\t\t\t)\n\t\t\treturn parsedData\n\t\t},\n\t})\n}\n\nexport async function userHasAccessToWorkshop({\n\ttimings,\n\trequest,\n\tforceFresh,\n}: {\n\trequest?: Request\n\ttimings?: Timings\n\tforceFresh?: boolean\n} = {}) {\n\tconst config = getWorkshopConfig()\n\tconst {\n\t\tproduct: { host, slug },\n\t} = config\n\tif (!slug) return true\n\n\tif (getEnv().EPICSHOP_DEPLOYED) {\n\t\tconst cookieHeader = request?.headers.get('Cookie')\n\t\tif (!cookieHeader) return false\n\t\tconst cookies = cookie.parse(cookieHeader)\n\t\treturn cookies.skill?.split(',').includes(slug) ?? false\n\t}\n\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) return false\n\n\treturn cachified({\n\t\tkey: `user-has-access-to-workshop:${host}:${slug}`,\n\t\tcache: epicApiCache,\n\t\trequest,\n\t\tforceFresh,\n\t\ttimings,\n\t\tttl: 1000 * 5,\n\t\tofflineFallbackValue: false,\n\t\tcheckValue: z.boolean(),\n\t\tasync getFreshValue(context) {\n\t\t\tconst accessUrl = `https://${host}/api/workshops/${encodeURIComponent(slug)}/access`\n\t\t\tlog(`checking workshop access via API: ${accessUrl}`)\n\n\t\t\tconst response = await fetch(accessUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tauthorization: `Bearer ${authInfo.tokenSet.access_token}`,\n\t\t\t\t},\n\t\t\t}).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\n\n\t\t\tlog(\n\t\t\t\t`workshop access API response: ${response.status} ${response.statusText}`,\n\t\t\t)\n\n\t\t\tconst hasAccess = response.ok ? (await response.json()) === true : false\n\t\t\tlog(`workshop access result for ${slug}: ${hasAccess}`)\n\n\t\t\tif (hasAccess) {\n\t\t\t\tcontext.metadata.ttl = 1000 * 60 * 5\n\t\t\t\tcontext.metadata.swr = 1000 * 60 * 60 * 24 * 365 * 10\n\t\t\t}\n\n\t\t\treturn hasAccess\n\t\t},\n\t}).catch((e) => {\n\t\tconsole.error('Failed to check workshop access', e)\n\t\treturn false\n\t})\n}\n\nconst UserInfoSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tname: z.string().nullable(),\n\t\temail: z.string().email(),\n\t\timage: z.string().nullable(),\n\t\tdiscordProfile: z\n\t\t\t.object({\n\t\t\t\tnick: z.string().nullable().optional(),\n\t\t\t\tuser: z\n\t\t\t\t\t.object({\n\t\t\t\t\t\tid: z.string(),\n\t\t\t\t\t\tusername: z.string(),\n\t\t\t\t\t\tavatar: z.string().nullable().optional(),\n\t\t\t\t\t\tglobal_name: z.string().nullable().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.optional(),\n\t\t\t})\n\t\t\t.nullable()\n\t\t\t.optional(),\n\t})\n\t.transform((data) => {\n\t\treturn {\n\t\t\t...data,\n\t\t\timageUrlSmall:\n\t\t\t\tresizeImageUrl(data.image, { size: 64 }) ??\n\t\t\t\tresolveDiscordAvatar(data.discordProfile?.user, {\n\t\t\t\t\tsize: 64,\n\t\t\t\t}) ??\n\t\t\t\tresolveGravatarUrl(data.email, { size: 64 }),\n\t\t\timageUrlLarge:\n\t\t\t\tresizeImageUrl(data.image, { size: 512 }) ??\n\t\t\t\tresolveDiscordAvatar(data.discordProfile?.user, {\n\t\t\t\t\tsize: 512,\n\t\t\t\t}) ??\n\t\t\t\tresolveGravatarUrl(data.email, { size: 512 }),\n\t\t}\n\t})\n\nfunction resizeImageUrl(url: string | null, { size }: { size: number }) {\n\tif (!url) return null\n\tconst urlObj = new URL(url)\n\turlObj.searchParams.set('size', size.toString())\n\treturn urlObj.toString()\n}\n\nfunction resolveGravatarUrl(\n\temail: string | undefined,\n\t{ size }: { size: number },\n) {\n\tif (!email) return null\n\n\tconst hash = md5(email.toLowerCase())\n\tconst gravatarOptions = new URLSearchParams({\n\t\tsize: size.toString(),\n\t\tdefault: 'identicon',\n\t})\n\treturn `https://www.gravatar.com/avatar/${hash}?${gravatarOptions.toString()}`\n}\n\nfunction resolveDiscordAvatar(\n\tuser: { avatar?: string | null; id: string } | undefined,\n\t{ size }: { size: 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 },\n) {\n\tif (!user) return null\n\n\tconst { avatar, id: userId } = user\n\tif (!avatar) return null\n\tconst isGif = avatar.startsWith('a_')\n\tconst url = new URL(\n\t\t`/avatars/${userId}/${avatar}.${isGif ? 'gif' : 'png'}`,\n\t\t'https://cdn.discordapp.com',\n\t)\n\turl.searchParams.set('size', size.toString())\n\treturn url.toString()\n}\n\nexport type UserInfo = z.infer<typeof UserInfoSchema>\n\nconst userinfoLog = log.logger('userinfo')\nexport async function getUserInfo({\n\ttimings,\n\trequest,\n\tforceFresh,\n}: {\n\ttimings?: Timings\n\trequest?: Request\n\tforceFresh?: boolean\n} = {}) {\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) return null\n\n\tconst { tokenSet } = authInfo\n\tconst {\n\t\tproduct: { host },\n\t} = getWorkshopConfig()\n\n\tconst accessToken = tokenSet.access_token\n\tconst url = `https://${host}/oauth/userinfo`\n\n\tuserinfoLog(`calling cachified to get user info from: ${url}`)\n\tconst userInfo = await cachified({\n\t\tkey: `${url}:${md5(accessToken)}`,\n\t\tcache: epicApiCache,\n\t\trequest,\n\t\tforceFresh,\n\t\ttimings,\n\t\tttl: 1000 * 30,\n\t\tswr: 1000 * 60 * 60 * 24 * 365 * 10,\n\t\tofflineFallbackValue: null,\n\t\tcheckValue: UserInfoSchema,\n\t\tasync getFreshValue(): Promise<UserInfo> {\n\t\t\tuserinfoLog(`getting fresh value for user info from: ${url}`)\n\n\t\t\tconst response = await fetch(url, {\n\t\t\t\theaders: { authorization: `Bearer ${accessToken}` },\n\t\t\t}).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\n\n\t\t\tuserinfoLog(\n\t\t\t\t`user info API response: ${response.status} ${response.statusText}`,\n\t\t\t)\n\n\t\t\tif (!response.ok) {\n\t\t\t\tuserinfoLog(\n\t\t\t\t\t`user info API request failed: ${response.status} ${response.statusText}`,\n\t\t\t\t)\n\t\t\t\tif (\n\t\t\t\t\tresponse.headers.get('content-type')?.includes('application/json')\n\t\t\t\t) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthrow new Error(`Failed to fetch user info: ${JSON.stringify(data)}`)\n\t\t\t\t} else {\n\t\t\t\t\tconst text = await response.text()\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Failed to fetch user info: ${text || response.statusText}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data = await response.json()\n\t\t\tconst parsedUserInfo = UserInfoSchema.parse(data)\n\t\t\tuserinfoLog(\n\t\t\t\t`successfully fetched user info for user: ${parsedUserInfo.id} (${parsedUserInfo.email})`,\n\t\t\t)\n\t\t\treturn parsedUserInfo\n\t\t},\n\t}).catch((e) => {\n\t\tuserinfoLog.error(`failed to get user info:`, e)\n\t\treturn null\n\t})\n\n\t// we used to md5 hash the email to get the id\n\t// if the id doesn't match what we have on file, update it\n\t// you can probably safely remove this in January 2025\n\tif (userInfo && authInfo.id !== userInfo.id) {\n\t\tawait setAuthInfo({\n\t\t\t...authInfo,\n\t\t\tid: userInfo.id,\n\t\t})\n\t}\n\n\treturn userInfo\n}\n\nexport async function warmCache() {\n\tawait Promise.all([getUserInfo(), getProgress()])\n}\n"]}
|
package/dist/esm/logger.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type
|
|
3
|
-
|
|
1
|
+
import { type DebugLogger } from 'node:util';
|
|
2
|
+
type Msg = string | (() => string);
|
|
3
|
+
type Params = unknown[];
|
|
4
|
+
type LogFunction = (msg: Msg, ...param: Params) => void;
|
|
5
|
+
interface Logger extends LogFunction, DebugLogger {
|
|
4
6
|
error: LogFunction;
|
|
5
7
|
warn: LogFunction;
|
|
6
8
|
info: LogFunction;
|
|
7
9
|
namespace: string;
|
|
8
10
|
logger: typeof logger;
|
|
9
|
-
isEnabled: () => boolean;
|
|
10
11
|
}
|
|
11
12
|
export declare function logger(ns: string): Logger;
|
|
12
13
|
export {};
|
package/dist/esm/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAEtD,KAAK,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAA;AAClC,KAAK,MAAM,GAAG,OAAO,EAAE,CAAA;AACvB,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;AAEvD,UAAU,MAAO,SAAQ,WAAW,EAAE,WAAW;IAChD,KAAK,EAAE,WAAW,CAAA;IAClB,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE,WAAW,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,OAAO,MAAM,CAAA;CACrB;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAyBzC"}
|
package/dist/esm/logger.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { debuglog } from 'node:util';
|
|
2
2
|
export function logger(ns) {
|
|
3
3
|
const log = debuglog(ns);
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const prefixedLoggerFn = (prefix, stringOrFn, ...params) => {
|
|
5
|
+
if (!debuglog(ns).enabled)
|
|
6
|
+
return;
|
|
7
|
+
const string = typeof stringOrFn === 'function' ? stringOrFn() : stringOrFn;
|
|
8
|
+
log(prefix ? `${prefix} ${string}` : string, ...params);
|
|
9
|
+
};
|
|
10
|
+
const loggerFn = prefixedLoggerFn.bind(null, null);
|
|
11
|
+
loggerFn.error = prefixedLoggerFn.bind(null, 'đ¨');
|
|
12
|
+
loggerFn.warn = prefixedLoggerFn.bind(null, 'â ī¸');
|
|
13
|
+
loggerFn.info = prefixedLoggerFn.bind(null, 'âšī¸');
|
|
8
14
|
loggerFn.namespace = ns;
|
|
9
15
|
loggerFn.logger = (subNs) => logger(`${ns}:${subNs}`);
|
|
10
|
-
|
|
16
|
+
Object.defineProperty(loggerFn, 'enabled', {
|
|
17
|
+
get: () => log.enabled,
|
|
18
|
+
});
|
|
11
19
|
return loggerFn;
|
|
12
20
|
}
|
|
13
21
|
//# sourceMappingURL=logger.js.map
|
package/dist/esm/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAoB,MAAM,WAAW,CAAA;AActD,MAAM,UAAU,MAAM,CAAC,EAAU;IAChC,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IAExB,MAAM,gBAAgB,GAAG,CACxB,MAAqB,EACrB,UAAe,EACf,GAAG,MAAc,EAChB,EAAE;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO;YAAE,OAAM;QACjC,MAAM,MAAM,GAAG,OAAO,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAAA;QAC3E,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,CAAA;IACxD,CAAC,CAAA;IAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAW,CAAA;IAE5D,QAAQ,CAAC,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAClD,QAAQ,CAAC,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACjD,QAAQ,CAAC,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACjD,QAAQ,CAAC,SAAS,GAAG,EAAE,CAAA;IACvB,QAAQ,CAAC,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,KAAK,EAAE,CAAC,CAAA;IAC7D,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE;QAC1C,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO;KACtB,CAAC,CAAA;IAEF,OAAO,QAAQ,CAAA;AAChB,CAAC","sourcesContent":["import { debuglog, type DebugLogger } from 'node:util'\n\ntype Msg = string | (() => string)\ntype Params = unknown[]\ntype LogFunction = (msg: Msg, ...param: Params) => void\n\ninterface Logger extends LogFunction, DebugLogger {\n\terror: LogFunction\n\twarn: LogFunction\n\tinfo: LogFunction\n\tnamespace: string\n\tlogger: typeof logger\n}\n\nexport function logger(ns: string): Logger {\n\tconst log = debuglog(ns)\n\n\tconst prefixedLoggerFn = (\n\t\tprefix: string | null,\n\t\tstringOrFn: Msg,\n\t\t...params: Params\n\t) => {\n\t\tif (!debuglog(ns).enabled) return\n\t\tconst string = typeof stringOrFn === 'function' ? stringOrFn() : stringOrFn\n\t\tlog(prefix ? `${prefix} ${string}` : string, ...params)\n\t}\n\n\tconst loggerFn = prefixedLoggerFn.bind(null, null) as Logger\n\n\tloggerFn.error = prefixedLoggerFn.bind(null, 'đ¨')\n\tloggerFn.warn = prefixedLoggerFn.bind(null, 'â ī¸')\n\tloggerFn.info = prefixedLoggerFn.bind(null, 'âšī¸')\n\tloggerFn.namespace = ns\n\tloggerFn.logger = (subNs: string) => logger(`${ns}:${subNs}`)\n\tObject.defineProperty(loggerFn, 'enabled', {\n\t\tget: () => log.enabled,\n\t})\n\n\treturn loggerFn\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.server.d.ts","sourceRoot":"","sources":["../../src/utils.server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAA;AAGtB,OAAO,QAAQ,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"utils.server.d.ts","sourceRoot":"","sources":["../../src/utils.server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAA;AAGtB,OAAO,QAAQ,MAAM,OAAO,CAAA;AAM5B,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAEjD,eAAO,MAAM,KAAK,iBAKhB,CAAA;AAkDF,wBAAsB,eAAe,CAAC,EACrC,OAAO,EACP,OAAO,GACP,GAAE;IACF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;CACZ,oBAwBL"}
|
package/dist/esm/utils.server.js
CHANGED
|
@@ -5,13 +5,47 @@ import relativeTimePlugin from 'dayjs/plugin/relativeTime.js';
|
|
|
5
5
|
import timeZonePlugin from 'dayjs/plugin/timezone.js';
|
|
6
6
|
import utcPlugin from 'dayjs/plugin/utc.js';
|
|
7
7
|
import { cachified, connectionCache } from './cache.server.js';
|
|
8
|
+
import { logger } from './logger.js';
|
|
8
9
|
export const dayjs = remember('dayjs', () => {
|
|
9
10
|
dayjsLib.extend(utcPlugin);
|
|
10
11
|
dayjsLib.extend(timeZonePlugin);
|
|
11
12
|
dayjsLib.extend(relativeTimePlugin);
|
|
12
13
|
return dayjsLib;
|
|
13
14
|
});
|
|
15
|
+
const connectionLog = logger('epic:connection');
|
|
16
|
+
async function probeOnce(url, expectedOk = (s) => s >= 200 && s < 300, init) {
|
|
17
|
+
const signal = AbortSignal.timeout(10_000);
|
|
18
|
+
const start = Date.now();
|
|
19
|
+
connectionLog(`probing ${url}`);
|
|
20
|
+
const res = await fetch(url, {
|
|
21
|
+
method: 'HEAD',
|
|
22
|
+
cache: 'no-store',
|
|
23
|
+
signal,
|
|
24
|
+
...init,
|
|
25
|
+
});
|
|
26
|
+
connectionLog(`probe to ${url} returned ${res.status} in ${Date.now() - start}ms`);
|
|
27
|
+
return expectedOk(res.status);
|
|
28
|
+
}
|
|
29
|
+
async function raceConnectivity() {
|
|
30
|
+
// we have multiple just in case some VPN blocks one or another
|
|
31
|
+
const candidates = [
|
|
32
|
+
probeOnce('https://connected.kentcdodds.workers.dev'),
|
|
33
|
+
// Non-CF options (different providers / networks):
|
|
34
|
+
probeOnce('https://www.gstatic.com/generate_204', (s) => s === 204),
|
|
35
|
+
probeOnce('http://www.msftconnecttest.com/connecttest.txt', (s) => s === 200),
|
|
36
|
+
];
|
|
37
|
+
// Use Promise.any to succeed on first truthy result; treat rejections/non-2xx as false
|
|
38
|
+
const wrapped = candidates.map((p) => p.then((ok) => (ok ? true : Promise.reject(new Error('not ok')))));
|
|
39
|
+
try {
|
|
40
|
+
await Promise.any(wrapped);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
14
47
|
export async function checkConnection({ request, timings, } = {}) {
|
|
48
|
+
connectionLog('calling cachified to check connection');
|
|
15
49
|
const isOnline = await cachified({
|
|
16
50
|
cache: connectionCache,
|
|
17
51
|
request,
|
|
@@ -19,11 +53,9 @@ export async function checkConnection({ request, timings, } = {}) {
|
|
|
19
53
|
key: 'connected',
|
|
20
54
|
ttl: 1000 * 10,
|
|
21
55
|
async getFreshValue(context) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
26
|
-
if (response.ok) {
|
|
56
|
+
connectionLog('getting fresh connection value');
|
|
57
|
+
const isOnline = await raceConnectivity();
|
|
58
|
+
if (isOnline) {
|
|
27
59
|
context.metadata.ttl = 1000 * 60;
|
|
28
60
|
context.metadata.swr = 1000 * 60 * 30;
|
|
29
61
|
return true;
|
|
@@ -33,6 +65,7 @@ export async function checkConnection({ request, timings, } = {}) {
|
|
|
33
65
|
}
|
|
34
66
|
},
|
|
35
67
|
});
|
|
68
|
+
connectionLog(`connection check says we are ${isOnline ? 'online' : 'offline'}`);
|
|
36
69
|
return isOnline;
|
|
37
70
|
}
|
|
38
71
|
//# sourceMappingURL=utils.server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.server.js","sourceRoot":"","sources":["../../src/utils.server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAA;AAEtB,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,QAAQ,MAAM,OAAO,CAAA;AAC5B,OAAO,kBAAkB,MAAM,8BAA8B,CAAA;AAC7D,OAAO,cAAc,MAAM,0BAA0B,CAAA;AACrD,OAAO,SAAS,MAAM,qBAAqB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"utils.server.js","sourceRoot":"","sources":["../../src/utils.server.ts"],"names":[],"mappings":"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","sourcesContent":["import './init-env.js'\n\nimport { remember } from '@epic-web/remember'\nimport dayjsLib from 'dayjs'\nimport relativeTimePlugin from 'dayjs/plugin/relativeTime.js'\nimport timeZonePlugin from 'dayjs/plugin/timezone.js'\nimport utcPlugin from 'dayjs/plugin/utc.js'\nimport { cachified, connectionCache } from './cache.server.js'\nimport { logger } from './logger.js'\nimport { type Timings } from './timing.server.js'\n\nexport const dayjs = remember('dayjs', () => {\n\tdayjsLib.extend(utcPlugin)\n\tdayjsLib.extend(timeZonePlugin)\n\tdayjsLib.extend(relativeTimePlugin)\n\treturn dayjsLib\n})\n\nconst connectionLog = logger('epic:connection')\n\nasync function probeOnce(\n\turl: string,\n\texpectedOk: (status: number) => boolean = (s) => s >= 200 && s < 300,\n\tinit?: RequestInit,\n): Promise<boolean> {\n\tconst signal = AbortSignal.timeout(10_000)\n\tconst start = Date.now()\n\tconnectionLog(`probing ${url}`)\n\tconst res = await fetch(url, {\n\t\tmethod: 'HEAD',\n\t\tcache: 'no-store',\n\t\tsignal,\n\t\t...init,\n\t})\n\tconnectionLog(\n\t\t`probe to ${url} returned ${res.status} in ${Date.now() - start}ms`,\n\t)\n\treturn expectedOk(res.status)\n}\n\nasync function raceConnectivity(): Promise<boolean> {\n\t// we have multiple just in case some VPN blocks one or another\n\tconst candidates: Array<Promise<boolean>> = [\n\t\tprobeOnce('https://connected.kentcdodds.workers.dev'),\n\n\t\t// Non-CF options (different providers / networks):\n\t\tprobeOnce('https://www.gstatic.com/generate_204', (s) => s === 204),\n\t\tprobeOnce(\n\t\t\t'http://www.msftconnecttest.com/connecttest.txt',\n\t\t\t(s) => s === 200,\n\t\t),\n\t]\n\n\t// Use Promise.any to succeed on first truthy result; treat rejections/non-2xx as false\n\tconst wrapped = candidates.map((p) =>\n\t\tp.then((ok) => (ok ? true : Promise.reject(new Error('not ok')))),\n\t)\n\n\ttry {\n\t\tawait Promise.any(wrapped)\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\nexport async function checkConnection({\n\trequest,\n\ttimings,\n}: {\n\trequest?: Request\n\ttimings?: Timings\n} = {}) {\n\tconnectionLog('calling cachified to check connection')\n\tconst isOnline = await cachified({\n\t\tcache: connectionCache,\n\t\trequest,\n\t\ttimings,\n\t\tkey: 'connected',\n\t\tttl: 1000 * 10,\n\t\tasync getFreshValue(context) {\n\t\t\tconnectionLog('getting fresh connection value')\n\t\t\tconst isOnline = await raceConnectivity()\n\t\t\tif (isOnline) {\n\t\t\t\tcontext.metadata.ttl = 1000 * 60\n\t\t\t\tcontext.metadata.swr = 1000 * 60 * 30\n\t\t\t\treturn true\n\t\t\t} else {\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\t})\n\tconnectionLog(\n\t\t`connection check says we are ${isOnline ? 'online' : 'offline'}`,\n\t)\n\treturn isOnline\n}\n"]}
|