@epic-web/workshop-utils 5.4.1 → 5.6.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 +16 -4
- package/dist/esm/db.server.d.ts.map +1 -1
- package/dist/esm/db.server.js +25 -8
- package/dist/esm/db.server.js.map +1 -1
- package/dist/esm/epic-api.server.d.ts +226 -0
- package/dist/esm/epic-api.server.d.ts.map +1 -0
- package/dist/esm/epic-api.server.js +473 -0
- package/dist/esm/epic-api.server.js.map +1 -0
- package/dist/esm/user.server.d.ts +17 -0
- package/dist/esm/user.server.d.ts.map +1 -0
- package/dist/esm/user.server.js +38 -0
- package/dist/esm/user.server.js.map +1 -0
- package/package.json +18 -2
package/dist/esm/db.server.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ declare const TokenSetSchema: z.ZodObject<{
|
|
|
13
13
|
scope: string;
|
|
14
14
|
}>;
|
|
15
15
|
export declare const PlayerPreferencesSchema: z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
|
16
|
+
minResolution: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
maxResolution: z.ZodOptional<z.ZodNumber>;
|
|
16
18
|
volumeRate: z.ZodOptional<z.ZodNumber>;
|
|
17
19
|
playbackRate: z.ZodOptional<z.ZodNumber>;
|
|
18
20
|
autoplay: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -35,6 +37,8 @@ export declare const PlayerPreferencesSchema: z.ZodDefault<z.ZodOptional<z.ZodOb
|
|
|
35
37
|
id: string | null;
|
|
36
38
|
mode: "disabled" | "hidden" | "showing" | null;
|
|
37
39
|
};
|
|
40
|
+
minResolution?: number | undefined;
|
|
41
|
+
maxResolution?: number | undefined;
|
|
38
42
|
volumeRate?: number | undefined;
|
|
39
43
|
playbackRate?: number | undefined;
|
|
40
44
|
autoplay?: boolean | undefined;
|
|
@@ -47,6 +51,8 @@ export declare const PlayerPreferencesSchema: z.ZodDefault<z.ZodOptional<z.ZodOb
|
|
|
47
51
|
id?: string | null | undefined;
|
|
48
52
|
mode?: "disabled" | "hidden" | "showing" | null | undefined;
|
|
49
53
|
} | undefined;
|
|
54
|
+
minResolution?: number | undefined;
|
|
55
|
+
maxResolution?: number | undefined;
|
|
50
56
|
volumeRate?: number | undefined;
|
|
51
57
|
playbackRate?: number | undefined;
|
|
52
58
|
autoplay?: boolean | undefined;
|
|
@@ -62,6 +68,7 @@ declare const PresencePreferencesSchema: z.ZodDefault<z.ZodOptional<z.ZodObject<
|
|
|
62
68
|
}, {
|
|
63
69
|
optOut: boolean;
|
|
64
70
|
}>>>;
|
|
71
|
+
export declare function getClientId(): Promise<string>;
|
|
65
72
|
export declare function deleteDb(): Promise<null | undefined>;
|
|
66
73
|
export declare function getAuthInfo(): Promise<{
|
|
67
74
|
id: string;
|
|
@@ -86,10 +93,11 @@ export declare function requireAuthInfo({ request, redirectTo, }: {
|
|
|
86
93
|
email: string;
|
|
87
94
|
name?: string | null | undefined;
|
|
88
95
|
}>;
|
|
89
|
-
export declare function setAuthInfo({ tokenSet, email, name, }: {
|
|
96
|
+
export declare function setAuthInfo({ id, tokenSet, email, name, }: {
|
|
97
|
+
id: string;
|
|
90
98
|
tokenSet: Partial<z.infer<typeof TokenSetSchema>>;
|
|
91
|
-
email?: string;
|
|
92
|
-
name?: string;
|
|
99
|
+
email?: string | null;
|
|
100
|
+
name?: string | null;
|
|
93
101
|
}): Promise<{
|
|
94
102
|
id: string;
|
|
95
103
|
tokenSet: {
|
|
@@ -106,6 +114,8 @@ export declare function getPreferences(): Promise<{
|
|
|
106
114
|
id: string | null;
|
|
107
115
|
mode: "disabled" | "hidden" | "showing" | null;
|
|
108
116
|
};
|
|
117
|
+
minResolution?: number | undefined;
|
|
118
|
+
maxResolution?: number | undefined;
|
|
109
119
|
volumeRate?: number | undefined;
|
|
110
120
|
playbackRate?: number | undefined;
|
|
111
121
|
autoplay?: boolean | undefined;
|
|
@@ -123,6 +133,8 @@ export declare function setPlayerPreferences(playerPreferences: z.input<typeof P
|
|
|
123
133
|
id?: string | null | undefined;
|
|
124
134
|
mode?: "disabled" | "hidden" | "showing" | null | undefined;
|
|
125
135
|
} | undefined;
|
|
136
|
+
minResolution?: number | undefined;
|
|
137
|
+
maxResolution?: number | undefined;
|
|
126
138
|
volumeRate?: number | undefined;
|
|
127
139
|
playbackRate?: number | undefined;
|
|
128
140
|
autoplay?: boolean | undefined;
|
|
@@ -130,7 +142,7 @@ export declare function setPlayerPreferences(playerPreferences: z.input<typeof P
|
|
|
130
142
|
theater?: boolean | undefined;
|
|
131
143
|
defaultView?: string | undefined;
|
|
132
144
|
activeSidebarTab?: number | undefined;
|
|
133
|
-
}
|
|
145
|
+
}>;
|
|
134
146
|
export declare function setPresencePreferences(presnecePreferences: z.input<typeof PresencePreferencesSchema>): Promise<{
|
|
135
147
|
optOut: boolean;
|
|
136
148
|
} | undefined>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.server.d.ts","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,QAAA,MAAM,cAAc;;;;;;;;;;;;EAIlB,CAAA;AACF,eAAO,MAAM,uBAAuB
|
|
1
|
+
{"version":3,"file":"db.server.d.ts","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,QAAA,MAAM,cAAc;;;;;;;;;;;;EAIlB,CAAA;AACF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAyBvB,CAAA;AAEb,QAAA,MAAM,yBAAyB;;;;;;IAKH,CAAA;AA+B5B,wBAAsB,WAAW,oBAQhC;AAED,wBAAsB,QAAQ,8BAU7B;AAoBD,wBAAsB,WAAW;;;;;;;;;UAGhC;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;;;;;;;;;GAMA;AAED,wBAAsB,cAAc;;;;;;;;;;;;;;;;;;;UAGnC;AAED,wBAAsB,oBAAoB,CACzC,iBAAiB,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC;;;;;;;;;;;;;;GAgB1D;AAED,wBAAsB,sBAAsB,CAC3C,mBAAmB,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC;;eAU9D;AAED,wBAAsB,kBAAkB;;;;WAGvC;AAED,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM;;GAehE"}
|
package/dist/esm/db.server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import os from 'os';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { createId as cuid } from '@paralleldrive/cuid2';
|
|
3
4
|
import { redirect } from '@remix-run/node';
|
|
4
5
|
import fsExtra from 'fs-extra';
|
|
5
|
-
import md5 from 'md5-hex';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
const TokenSetSchema = z.object({
|
|
8
8
|
access_token: z.string(),
|
|
@@ -11,6 +11,8 @@ const TokenSetSchema = z.object({
|
|
|
11
11
|
});
|
|
12
12
|
export const PlayerPreferencesSchema = z
|
|
13
13
|
.object({
|
|
14
|
+
minResolution: z.number().optional(),
|
|
15
|
+
maxResolution: z.number().optional(),
|
|
14
16
|
volumeRate: z.number().optional(),
|
|
15
17
|
playbackRate: z.number().optional(),
|
|
16
18
|
autoplay: z.boolean().optional(),
|
|
@@ -39,13 +41,12 @@ const PresencePreferencesSchema = z
|
|
|
39
41
|
})
|
|
40
42
|
.optional()
|
|
41
43
|
.default({ optOut: false });
|
|
42
|
-
const AuthInfoSchema = z
|
|
43
|
-
.
|
|
44
|
+
const AuthInfoSchema = z.object({
|
|
45
|
+
id: z.string(),
|
|
44
46
|
tokenSet: TokenSetSchema,
|
|
45
47
|
email: z.string(),
|
|
46
48
|
name: z.string().nullable().optional(),
|
|
47
|
-
})
|
|
48
|
-
.transform((d) => ({ ...d, id: md5(d.email) }));
|
|
49
|
+
});
|
|
49
50
|
const DataSchema = z.object({
|
|
50
51
|
onboarding: z
|
|
51
52
|
.object({
|
|
@@ -62,9 +63,19 @@ const DataSchema = z.object({
|
|
|
62
63
|
.optional()
|
|
63
64
|
.default({}),
|
|
64
65
|
authInfo: AuthInfoSchema.optional(),
|
|
66
|
+
clientId: z.string().optional(),
|
|
65
67
|
});
|
|
66
68
|
const appDir = path.join(os.homedir(), '.epicshop');
|
|
67
69
|
const dbPath = path.join(appDir, 'data.json');
|
|
70
|
+
export async function getClientId() {
|
|
71
|
+
const data = await readDb();
|
|
72
|
+
if (data?.clientId)
|
|
73
|
+
return data.clientId;
|
|
74
|
+
const clientId = cuid();
|
|
75
|
+
await fsExtra.ensureDir(appDir);
|
|
76
|
+
await fsExtra.writeJSON(dbPath, { ...data, clientId });
|
|
77
|
+
return clientId;
|
|
78
|
+
}
|
|
68
79
|
export async function deleteDb() {
|
|
69
80
|
if (process.env.EPICSHOP_DEPLOYED)
|
|
70
81
|
return null;
|
|
@@ -112,9 +123,9 @@ export async function requireAuthInfo({ request, redirectTo, }) {
|
|
|
112
123
|
}
|
|
113
124
|
return authInfo;
|
|
114
125
|
}
|
|
115
|
-
export async function setAuthInfo({ tokenSet, email = 'unknown@example.com', name, }) {
|
|
126
|
+
export async function setAuthInfo({ id, tokenSet, email = 'unknown@example.com', name, }) {
|
|
116
127
|
const data = await readDb();
|
|
117
|
-
const authInfo = AuthInfoSchema.parse({ tokenSet, email, name });
|
|
128
|
+
const authInfo = AuthInfoSchema.parse({ id, tokenSet, email, name });
|
|
118
129
|
await fsExtra.ensureDir(appDir);
|
|
119
130
|
await fsExtra.writeJSON(dbPath, { ...data, authInfo });
|
|
120
131
|
return authInfo;
|
|
@@ -127,7 +138,13 @@ export async function setPlayerPreferences(playerPreferences) {
|
|
|
127
138
|
const data = await readDb();
|
|
128
139
|
const updatedData = {
|
|
129
140
|
...data,
|
|
130
|
-
preferences: {
|
|
141
|
+
preferences: {
|
|
142
|
+
...data?.preferences,
|
|
143
|
+
player: {
|
|
144
|
+
...data?.preferences?.player,
|
|
145
|
+
...playerPreferences,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
131
148
|
};
|
|
132
149
|
await fsExtra.ensureDir(appDir);
|
|
133
150
|
await fsExtra.writeJSON(dbPath, updatedData);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.server.js","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"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","sourcesContent":["import os from 'os'\nimport path from 'path'\nimport { redirect } from '@remix-run/node'\nimport fsExtra from 'fs-extra'\nimport md5 from 'md5-hex'\nimport { z } from 'zod'\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\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\n\t.object({\n\t\ttokenSet: TokenSetSchema,\n\t\temail: z.string(),\n\t\tname: z.string().nullable().optional(),\n\t})\n\t.transform((d) => ({ ...d, id: md5(d.email) }))\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})\n\t\t.optional()\n\t\t.default({}),\n\tauthInfo: AuthInfoSchema.optional(),\n})\n\nconst appDir = path.join(os.homedir(), '.epicshop')\nconst dbPath = path.join(appDir, 'data.json')\n\nexport async function deleteDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tif (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 in ${dbPath}`, error)\n\t}\n}\n\nasync function readDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tif (await fsExtra.exists(dbPath)) {\n\t\t\tconst db = DataSchema.parse(await fsExtra.readJSON(dbPath))\n\t\t\treturn db\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\n\t\t\t`Error reading the database in ${dbPath}, moving it to a .bkp file to avoid parsing errors in the future`,\n\t\t\terror,\n\t\t)\n\t\tvoid fsExtra.move(dbPath, `${dbPath}.bkp`).catch(() => {})\n\t}\n\treturn null\n}\n\nexport async function getAuthInfo() {\n\tconst data = await readDb()\n\treturn data?.authInfo ?? 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\ttokenSet,\n\temail = 'unknown@example.com',\n\tname,\n}: {\n\ttokenSet: Partial<z.infer<typeof TokenSetSchema>>\n\temail?: string\n\tname?: string\n}) {\n\tconst data = await readDb()\n\tconst authInfo = AuthInfoSchema.parse({ tokenSet, email, name })\n\tawait fsExtra.ensureDir(appDir)\n\tawait fsExtra.writeJSON(dbPath, { ...data, authInfo })\n\treturn authInfo\n}\n\nexport async function getPreferences() {\n\tconst data = await readDb()\n\treturn data?.preferences ?? null\n}\n\nexport async function setPlayerPreferences(\n\tplayerPreferences: z.input<typeof PlayerPreferencesSchema>,\n) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: { ...data?.preferences, player: playerPreferences },\n\t}\n\tawait fsExtra.ensureDir(appDir)\n\tawait fsExtra.writeJSON(dbPath, updatedData)\n\treturn updatedData.preferences.player\n}\n\nexport async function setPresencePreferences(\n\tpresnecePreferences: z.input<typeof PresencePreferencesSchema>,\n) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: { ...data?.preferences, presence: presnecePreferences },\n\t}\n\tawait fsExtra.ensureDir(appDir)\n\tawait fsExtra.writeJSON(dbPath, updatedData)\n\treturn updatedData.preferences.presence\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 fsExtra.ensureDir(appDir)\n\tawait fsExtra.writeJSON(dbPath, updatedData)\n\treturn updatedData.onboarding\n}\n"]}
|
|
1
|
+
{"version":3,"file":"db.server.js","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"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","sourcesContent":["import os from 'os'\nimport path from 'path'\nimport { createId as cuid } from '@paralleldrive/cuid2'\nimport { redirect } from '@remix-run/node'\nimport fsExtra from 'fs-extra'\nimport { z } from 'zod'\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 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})\n\t\t.optional()\n\t\t.default({}),\n\tauthInfo: AuthInfoSchema.optional(),\n\tclientId: z.string().optional(),\n})\n\nconst appDir = path.join(os.homedir(), '.epicshop')\nconst dbPath = path.join(appDir, 'data.json')\n\nexport async function getClientId() {\n\tconst data = await readDb()\n\tif (data?.clientId) return data.clientId\n\n\tconst clientId = cuid()\n\tawait fsExtra.ensureDir(appDir)\n\tawait fsExtra.writeJSON(dbPath, { ...data, clientId })\n\treturn clientId\n}\n\nexport async function deleteDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tif (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 in ${dbPath}`, error)\n\t}\n}\n\nasync function readDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tif (await fsExtra.exists(dbPath)) {\n\t\t\tconst db = DataSchema.parse(await fsExtra.readJSON(dbPath))\n\t\t\treturn db\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\n\t\t\t`Error reading the database in ${dbPath}, moving it to a .bkp file to avoid parsing errors in the future`,\n\t\t\terror,\n\t\t)\n\t\tvoid fsExtra.move(dbPath, `${dbPath}.bkp`).catch(() => {})\n\t}\n\treturn null\n}\n\nexport async function getAuthInfo() {\n\tconst data = await readDb()\n\treturn data?.authInfo ?? 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\tawait fsExtra.ensureDir(appDir)\n\tawait fsExtra.writeJSON(dbPath, { ...data, authInfo })\n\treturn authInfo\n}\n\nexport async function getPreferences() {\n\tconst data = await readDb()\n\treturn data?.preferences ?? null\n}\n\nexport async function setPlayerPreferences(\n\tplayerPreferences: z.input<typeof PlayerPreferencesSchema>,\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\tplayer: {\n\t\t\t\t...data?.preferences?.player,\n\t\t\t\t...playerPreferences,\n\t\t\t},\n\t\t},\n\t}\n\tawait fsExtra.ensureDir(appDir)\n\tawait fsExtra.writeJSON(dbPath, updatedData)\n\treturn updatedData.preferences.player\n}\n\nexport async function setPresencePreferences(\n\tpresnecePreferences: z.input<typeof PresencePreferencesSchema>,\n) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: { ...data?.preferences, presence: presnecePreferences },\n\t}\n\tawait fsExtra.ensureDir(appDir)\n\tawait fsExtra.writeJSON(dbPath, updatedData)\n\treturn updatedData.preferences.presence\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 fsExtra.ensureDir(appDir)\n\tawait fsExtra.writeJSON(dbPath, updatedData)\n\treturn updatedData.onboarding\n}\n"]}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type Timings } from './timing.server.js';
|
|
3
|
+
export type EpicVideoInfos = Record<string, Awaited<ReturnType<typeof getEpicVideoInfo>>>;
|
|
4
|
+
export declare function getEpicVideoInfos(epicWebUrls?: Array<string> | null, { request, timings }?: {
|
|
5
|
+
request?: Request;
|
|
6
|
+
timings?: Timings;
|
|
7
|
+
}): Promise<EpicVideoInfos>;
|
|
8
|
+
declare function getEpicVideoInfo({ epicVideoEmbed, accessToken, request, timings, }: {
|
|
9
|
+
epicVideoEmbed: string;
|
|
10
|
+
accessToken?: string;
|
|
11
|
+
request?: Request;
|
|
12
|
+
timings?: Timings;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
status: "success";
|
|
15
|
+
statusText: string;
|
|
16
|
+
transcript: string;
|
|
17
|
+
muxPlaybackId: string;
|
|
18
|
+
statusCode: number;
|
|
19
|
+
} | {
|
|
20
|
+
type: "unknown";
|
|
21
|
+
status: "error";
|
|
22
|
+
statusText: string;
|
|
23
|
+
statusCode: number;
|
|
24
|
+
} | {
|
|
25
|
+
type: "region-restricted";
|
|
26
|
+
status: "error";
|
|
27
|
+
statusText: string;
|
|
28
|
+
requestCountry: string;
|
|
29
|
+
restrictedCountry: string;
|
|
30
|
+
statusCode: number;
|
|
31
|
+
} | null>;
|
|
32
|
+
export type Progress = Awaited<ReturnType<typeof getProgress>>[number];
|
|
33
|
+
export declare function getProgress({ timings, request, }?: {
|
|
34
|
+
timings?: Timings;
|
|
35
|
+
request?: Request;
|
|
36
|
+
}): Promise<({
|
|
37
|
+
epicLessonUrl: string;
|
|
38
|
+
epicLessonSlug: string;
|
|
39
|
+
epicCompletedAt: string | null;
|
|
40
|
+
} & ({
|
|
41
|
+
readonly type: "workshop-instructions";
|
|
42
|
+
readonly exerciseNumber?: undefined;
|
|
43
|
+
readonly stepNumber?: undefined;
|
|
44
|
+
} | {
|
|
45
|
+
readonly type: "workshop-finished";
|
|
46
|
+
readonly exerciseNumber?: undefined;
|
|
47
|
+
readonly stepNumber?: undefined;
|
|
48
|
+
} | {
|
|
49
|
+
readonly type: "instructions";
|
|
50
|
+
readonly exerciseNumber: number;
|
|
51
|
+
readonly stepNumber?: undefined;
|
|
52
|
+
} | {
|
|
53
|
+
readonly type: "finished";
|
|
54
|
+
readonly exerciseNumber: number;
|
|
55
|
+
readonly stepNumber?: undefined;
|
|
56
|
+
} | {
|
|
57
|
+
readonly type: "step";
|
|
58
|
+
readonly exerciseNumber: number;
|
|
59
|
+
readonly stepNumber: number;
|
|
60
|
+
} | {
|
|
61
|
+
type: "unknown";
|
|
62
|
+
} | undefined))[]>;
|
|
63
|
+
export declare function updateProgress({ lessonSlug, complete }: {
|
|
64
|
+
lessonSlug: string;
|
|
65
|
+
complete?: boolean;
|
|
66
|
+
}, { timings, request, }?: {
|
|
67
|
+
timings?: Timings;
|
|
68
|
+
request?: Request;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
readonly status: "error";
|
|
71
|
+
readonly error: "cannot update progress when deployed";
|
|
72
|
+
} | {
|
|
73
|
+
readonly status: "error";
|
|
74
|
+
readonly error: "not authenticated";
|
|
75
|
+
} | {
|
|
76
|
+
readonly status: "error";
|
|
77
|
+
readonly error: `${number} ${string}`;
|
|
78
|
+
} | {
|
|
79
|
+
readonly status: "success";
|
|
80
|
+
readonly error?: undefined;
|
|
81
|
+
}>;
|
|
82
|
+
export declare function getWorkshopData(slug: string, { timings, request, forceFresh, }?: {
|
|
83
|
+
timings?: Timings;
|
|
84
|
+
request?: Request;
|
|
85
|
+
forceFresh?: boolean;
|
|
86
|
+
}): Promise<{
|
|
87
|
+
resources: ({
|
|
88
|
+
slug: string;
|
|
89
|
+
_type: "lesson";
|
|
90
|
+
_id: string;
|
|
91
|
+
} | {
|
|
92
|
+
_type: "section";
|
|
93
|
+
lessons: {
|
|
94
|
+
slug: string;
|
|
95
|
+
_id: string;
|
|
96
|
+
}[];
|
|
97
|
+
})[];
|
|
98
|
+
}>;
|
|
99
|
+
export declare function userHasAccessToWorkshop({ timings, request, forceFresh, }: {
|
|
100
|
+
request?: Request;
|
|
101
|
+
timings?: Timings;
|
|
102
|
+
forceFresh?: boolean;
|
|
103
|
+
}): Promise<boolean>;
|
|
104
|
+
declare const UserInfoSchema: z.ZodEffects<z.ZodObject<{
|
|
105
|
+
id: z.ZodString;
|
|
106
|
+
name: z.ZodNullable<z.ZodString>;
|
|
107
|
+
email: z.ZodString;
|
|
108
|
+
image: z.ZodNullable<z.ZodString>;
|
|
109
|
+
discordProfile: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
110
|
+
nick: z.ZodNullable<z.ZodString>;
|
|
111
|
+
user: z.ZodObject<{
|
|
112
|
+
id: z.ZodString;
|
|
113
|
+
username: z.ZodString;
|
|
114
|
+
avatar: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
115
|
+
global_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
116
|
+
}, "strip", z.ZodTypeAny, {
|
|
117
|
+
id: string;
|
|
118
|
+
username: string;
|
|
119
|
+
avatar?: string | null | undefined;
|
|
120
|
+
global_name?: string | null | undefined;
|
|
121
|
+
}, {
|
|
122
|
+
id: string;
|
|
123
|
+
username: string;
|
|
124
|
+
avatar?: string | null | undefined;
|
|
125
|
+
global_name?: string | null | undefined;
|
|
126
|
+
}>;
|
|
127
|
+
}, "strip", z.ZodTypeAny, {
|
|
128
|
+
nick: string | null;
|
|
129
|
+
user: {
|
|
130
|
+
id: string;
|
|
131
|
+
username: string;
|
|
132
|
+
avatar?: string | null | undefined;
|
|
133
|
+
global_name?: string | null | undefined;
|
|
134
|
+
};
|
|
135
|
+
}, {
|
|
136
|
+
nick: string | null;
|
|
137
|
+
user: {
|
|
138
|
+
id: string;
|
|
139
|
+
username: string;
|
|
140
|
+
avatar?: string | null | undefined;
|
|
141
|
+
global_name?: string | null | undefined;
|
|
142
|
+
};
|
|
143
|
+
}>>>;
|
|
144
|
+
}, "strip", z.ZodTypeAny, {
|
|
145
|
+
name: string | null;
|
|
146
|
+
image: string | null;
|
|
147
|
+
id: string;
|
|
148
|
+
email: string;
|
|
149
|
+
discordProfile?: {
|
|
150
|
+
nick: string | null;
|
|
151
|
+
user: {
|
|
152
|
+
id: string;
|
|
153
|
+
username: string;
|
|
154
|
+
avatar?: string | null | undefined;
|
|
155
|
+
global_name?: string | null | undefined;
|
|
156
|
+
};
|
|
157
|
+
} | null | undefined;
|
|
158
|
+
}, {
|
|
159
|
+
name: string | null;
|
|
160
|
+
image: string | null;
|
|
161
|
+
id: string;
|
|
162
|
+
email: string;
|
|
163
|
+
discordProfile?: {
|
|
164
|
+
nick: string | null;
|
|
165
|
+
user: {
|
|
166
|
+
id: string;
|
|
167
|
+
username: string;
|
|
168
|
+
avatar?: string | null | undefined;
|
|
169
|
+
global_name?: string | null | undefined;
|
|
170
|
+
};
|
|
171
|
+
} | null | undefined;
|
|
172
|
+
}>, {
|
|
173
|
+
imageUrlSmall: string | null;
|
|
174
|
+
imageUrlLarge: string | null;
|
|
175
|
+
name: string | null;
|
|
176
|
+
image: string | null;
|
|
177
|
+
id: string;
|
|
178
|
+
email: string;
|
|
179
|
+
discordProfile?: {
|
|
180
|
+
nick: string | null;
|
|
181
|
+
user: {
|
|
182
|
+
id: string;
|
|
183
|
+
username: string;
|
|
184
|
+
avatar?: string | null | undefined;
|
|
185
|
+
global_name?: string | null | undefined;
|
|
186
|
+
};
|
|
187
|
+
} | null | undefined;
|
|
188
|
+
}, {
|
|
189
|
+
name: string | null;
|
|
190
|
+
image: string | null;
|
|
191
|
+
id: string;
|
|
192
|
+
email: string;
|
|
193
|
+
discordProfile?: {
|
|
194
|
+
nick: string | null;
|
|
195
|
+
user: {
|
|
196
|
+
id: string;
|
|
197
|
+
username: string;
|
|
198
|
+
avatar?: string | null | undefined;
|
|
199
|
+
global_name?: string | null | undefined;
|
|
200
|
+
};
|
|
201
|
+
} | null | undefined;
|
|
202
|
+
}>;
|
|
203
|
+
export type UserInfo = z.infer<typeof UserInfoSchema>;
|
|
204
|
+
export declare function getUserInfo({ timings, request, forceFresh, }?: {
|
|
205
|
+
timings?: Timings;
|
|
206
|
+
request?: Request;
|
|
207
|
+
forceFresh?: boolean;
|
|
208
|
+
}): Promise<{
|
|
209
|
+
imageUrlSmall: string | null;
|
|
210
|
+
imageUrlLarge: string | null;
|
|
211
|
+
name: string | null;
|
|
212
|
+
image: string | null;
|
|
213
|
+
id: string;
|
|
214
|
+
email: string;
|
|
215
|
+
discordProfile?: {
|
|
216
|
+
nick: string | null;
|
|
217
|
+
user: {
|
|
218
|
+
id: string;
|
|
219
|
+
username: string;
|
|
220
|
+
avatar?: string | null | undefined;
|
|
221
|
+
global_name?: string | null | undefined;
|
|
222
|
+
};
|
|
223
|
+
} | null | undefined;
|
|
224
|
+
} | null>;
|
|
225
|
+
export {};
|
|
226
|
+
//# sourceMappingURL=epic-api.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"epic-api.server.d.ts","sourceRoot":"","sources":["../../src/epic-api.server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AASvB,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAA;AA+CjD,MAAM,MAAM,cAAc,GAAG,MAAM,CAClC,MAAM,EACN,OAAO,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAC5C,CAAA;AAED,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,2BAmBnE;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;;;;;;;;;;;;;;;;;;UA2FA;AAiDD,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;mBAwBW,MAAM;oBACL,MAAM;qBACL,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;UAIsB,SAAS;mBAoC9D;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;;;;;;;;;;;;GAqCN;AAkBD,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;;;;;;;;;;;;GAgCN;AAED,wBAAsB,uBAAuB,CAAC,EAC7C,OAAO,EACP,OAAO,EACP,UAAU,GACV,EAAE;IACF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;CACpB,oBA4CA;AAED,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCjB,CAAA;AAwCH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAErD,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;;;;;;;;;;;;;;;;UA6CL"}
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import * as cookie from 'cookie';
|
|
2
|
+
import md5 from 'md5-hex';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { getExercises, getWorkshopFinished, getWorkshopInstructions, } from './apps.server.js';
|
|
5
|
+
import { cachified, fsCache } from './cache.server.js';
|
|
6
|
+
import { getWorkshopConfig } from './config.server.js';
|
|
7
|
+
import { getAuthInfo, setAuthInfo } from './db.server.js';
|
|
8
|
+
import { getErrorMessage } from './utils.js';
|
|
9
|
+
const Transcript = z
|
|
10
|
+
.string()
|
|
11
|
+
.nullable()
|
|
12
|
+
.optional()
|
|
13
|
+
.transform((s) => s ?? 'Transcripts not available');
|
|
14
|
+
const EpicVideoInfoSchema = z.object({
|
|
15
|
+
transcript: Transcript,
|
|
16
|
+
muxPlaybackId: z.string(),
|
|
17
|
+
});
|
|
18
|
+
const EpicVideoRegionRestrictedErrorSchema = z.object({
|
|
19
|
+
requestCountry: z.string(),
|
|
20
|
+
restrictedCountry: z.string(),
|
|
21
|
+
isRegionRestricted: z.literal(true),
|
|
22
|
+
});
|
|
23
|
+
const CachedEpicVideoInfoSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
transcript: Transcript,
|
|
26
|
+
muxPlaybackId: z.string(),
|
|
27
|
+
status: z.literal('success'),
|
|
28
|
+
statusCode: z.number(),
|
|
29
|
+
statusText: z.string(),
|
|
30
|
+
})
|
|
31
|
+
.or(z.object({
|
|
32
|
+
status: z.literal('error'),
|
|
33
|
+
statusCode: z.number(),
|
|
34
|
+
statusText: z.string(),
|
|
35
|
+
type: z.literal('unknown'),
|
|
36
|
+
}))
|
|
37
|
+
.or(z.object({
|
|
38
|
+
status: z.literal('error'),
|
|
39
|
+
statusCode: z.number(),
|
|
40
|
+
statusText: z.string(),
|
|
41
|
+
type: z.literal('region-restricted'),
|
|
42
|
+
requestCountry: z.string(),
|
|
43
|
+
restrictedCountry: z.string(),
|
|
44
|
+
}))
|
|
45
|
+
.or(z.null());
|
|
46
|
+
export async function getEpicVideoInfos(epicWebUrls, { request, timings } = {}) {
|
|
47
|
+
if (!epicWebUrls)
|
|
48
|
+
return {};
|
|
49
|
+
const authInfo = await getAuthInfo();
|
|
50
|
+
if (ENV.EPICSHOP_DEPLOYED)
|
|
51
|
+
return {};
|
|
52
|
+
const epicVideoInfos = {};
|
|
53
|
+
for (const epicVideoEmbed of epicWebUrls) {
|
|
54
|
+
const epicVideoInfo = await getEpicVideoInfo({
|
|
55
|
+
epicVideoEmbed,
|
|
56
|
+
accessToken: authInfo?.tokenSet.access_token,
|
|
57
|
+
request,
|
|
58
|
+
timings,
|
|
59
|
+
});
|
|
60
|
+
if (epicVideoInfo) {
|
|
61
|
+
epicVideoInfos[epicVideoEmbed] = epicVideoInfo;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return epicVideoInfos;
|
|
65
|
+
}
|
|
66
|
+
async function getEpicVideoInfo({ epicVideoEmbed, accessToken, request, timings, }) {
|
|
67
|
+
const tokenPortion = accessToken ? md5(accessToken) : 'unauthenticated';
|
|
68
|
+
const key = `epic-video-info:${tokenPortion}:${epicVideoEmbed}`;
|
|
69
|
+
return cachified({
|
|
70
|
+
key,
|
|
71
|
+
request,
|
|
72
|
+
cache: fsCache,
|
|
73
|
+
timings,
|
|
74
|
+
ttl: 1000 * 60 * 60,
|
|
75
|
+
swr: 1000 * 60 * 60 * 24 * 30,
|
|
76
|
+
checkValue: CachedEpicVideoInfoSchema,
|
|
77
|
+
async getFreshValue(context) {
|
|
78
|
+
const epicUrl = new URL(epicVideoEmbed);
|
|
79
|
+
if (epicUrl.host !== 'www.epicweb.dev' &&
|
|
80
|
+
epicUrl.host !== 'www.epicreact.dev') {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
// this may be temporary until the /tutorials/ endpoint supports /api
|
|
84
|
+
if (epicUrl.pathname.startsWith('/tutorials/')) {
|
|
85
|
+
epicUrl.pathname = epicUrl.pathname.replace(/^\/tutorials\//, '/workshops/');
|
|
86
|
+
}
|
|
87
|
+
const infoResponse = await fetch(`https://${epicUrl.host}/api${epicUrl.pathname}`, accessToken
|
|
88
|
+
? { headers: { authorization: `Bearer ${accessToken}` } }
|
|
89
|
+
: undefined);
|
|
90
|
+
const { status, statusText } = infoResponse;
|
|
91
|
+
if (infoResponse.status >= 200 && infoResponse.status < 300) {
|
|
92
|
+
const rawInfo = await infoResponse.json();
|
|
93
|
+
const infoResult = EpicVideoInfoSchema.safeParse(rawInfo);
|
|
94
|
+
if (infoResult.success) {
|
|
95
|
+
return {
|
|
96
|
+
status: 'success',
|
|
97
|
+
statusCode: status,
|
|
98
|
+
statusText,
|
|
99
|
+
...infoResult.data,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// don't cache errors for long...
|
|
104
|
+
context.metadata.ttl = 1000 * 2; // 2 seconds
|
|
105
|
+
context.metadata.swr = 0;
|
|
106
|
+
const restrictedResult = EpicVideoRegionRestrictedErrorSchema.safeParse(rawInfo);
|
|
107
|
+
if (restrictedResult.success) {
|
|
108
|
+
return {
|
|
109
|
+
status: 'error',
|
|
110
|
+
statusCode: status,
|
|
111
|
+
statusText,
|
|
112
|
+
type: 'region-restricted',
|
|
113
|
+
...restrictedResult.data,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.warn(`Response from EpicWeb for "${epicUrl.pathname}" does not match expectation`, infoResult.error);
|
|
118
|
+
return {
|
|
119
|
+
status: 'error',
|
|
120
|
+
statusCode: 500,
|
|
121
|
+
statusText: 'API Data Type Mismatch',
|
|
122
|
+
type: 'unknown',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// don't cache errors for long...
|
|
129
|
+
context.metadata.ttl = 1000 * 2; // 2 seconds
|
|
130
|
+
context.metadata.swr = 0;
|
|
131
|
+
return {
|
|
132
|
+
status: 'error',
|
|
133
|
+
statusCode: status,
|
|
134
|
+
statusText,
|
|
135
|
+
type: 'unknown',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
}).catch((e) => {
|
|
140
|
+
console.error(`Failed to fetch epic video info for ${epicVideoEmbed}`, e);
|
|
141
|
+
throw e;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async function getEpicProgress({ timings, request, forceFresh, } = {}) {
|
|
145
|
+
if (ENV.EPICSHOP_DEPLOYED)
|
|
146
|
+
return [];
|
|
147
|
+
const authInfo = await getAuthInfo();
|
|
148
|
+
const { product: { host }, } = getWorkshopConfig();
|
|
149
|
+
if (!authInfo)
|
|
150
|
+
return [];
|
|
151
|
+
const tokenPart = md5(authInfo.tokenSet.access_token);
|
|
152
|
+
const EpicProgressSchema = z.array(z.object({
|
|
153
|
+
lessonId: z.string(),
|
|
154
|
+
completedAt: z.string().nullable(),
|
|
155
|
+
}));
|
|
156
|
+
return cachified({
|
|
157
|
+
key: `epic-progress:${host}:${tokenPart}`,
|
|
158
|
+
cache: fsCache,
|
|
159
|
+
request,
|
|
160
|
+
timings,
|
|
161
|
+
forceFresh,
|
|
162
|
+
ttl: 1000 * 2,
|
|
163
|
+
swr: 1000 * 60 * 60 * 24 * 30,
|
|
164
|
+
checkValue: EpicProgressSchema,
|
|
165
|
+
async getFreshValue(context) {
|
|
166
|
+
const response = await fetch(`https://${host}/api/progress`, {
|
|
167
|
+
headers: {
|
|
168
|
+
authorization: `Bearer ${authInfo.tokenSet.access_token}`,
|
|
169
|
+
},
|
|
170
|
+
}).catch((e) => new Response(getErrorMessage(e), { status: 500 }));
|
|
171
|
+
if (response.status < 200 || response.status >= 300) {
|
|
172
|
+
console.error(`Failed to fetch progress from EpicWeb: ${response.status} ${response.statusText}`);
|
|
173
|
+
// don't cache errors for long...
|
|
174
|
+
context.metadata.ttl = 1000 * 2; // 2 seconds
|
|
175
|
+
context.metadata.swr = 0;
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
return EpicProgressSchema.parse(await response.json());
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
export async function getProgress({ timings, request, } = {}) {
|
|
183
|
+
if (ENV.EPICSHOP_DEPLOYED)
|
|
184
|
+
return [];
|
|
185
|
+
const authInfo = await getAuthInfo();
|
|
186
|
+
if (!authInfo)
|
|
187
|
+
return [];
|
|
188
|
+
const { product: { slug, host }, } = getWorkshopConfig();
|
|
189
|
+
if (!slug)
|
|
190
|
+
return [];
|
|
191
|
+
const [workshopData, epicProgress, workshopInstructions, workshopFinished, exercises,] = await Promise.all([
|
|
192
|
+
getWorkshopData(slug, { request, timings }),
|
|
193
|
+
getEpicProgress({ request, timings }),
|
|
194
|
+
getWorkshopInstructions({ request }),
|
|
195
|
+
getWorkshopFinished({ request }),
|
|
196
|
+
getExercises({ request, timings }),
|
|
197
|
+
]);
|
|
198
|
+
const progress = [];
|
|
199
|
+
for (const resource of workshopData.resources) {
|
|
200
|
+
const lessons = resource._type === 'section' ? resource.lessons : [resource];
|
|
201
|
+
for (const lesson of lessons) {
|
|
202
|
+
const epicLessonSlug = lesson.slug;
|
|
203
|
+
const lessonProgress = epicProgress.find(({ lessonId }) => lessonId === lesson._id);
|
|
204
|
+
const epicCompletedAt = lessonProgress ? lessonProgress.completedAt : null;
|
|
205
|
+
const progressForLesson = getProgressForLesson(epicLessonSlug, {
|
|
206
|
+
workshopInstructions,
|
|
207
|
+
workshopFinished,
|
|
208
|
+
exercises,
|
|
209
|
+
});
|
|
210
|
+
const epicLessonUrl = `https://${host}/workshops/${slug}/${epicLessonSlug}`;
|
|
211
|
+
if (progressForLesson) {
|
|
212
|
+
progress.push({
|
|
213
|
+
...progressForLesson,
|
|
214
|
+
epicLessonUrl,
|
|
215
|
+
epicLessonSlug,
|
|
216
|
+
epicCompletedAt,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
progress.push({
|
|
221
|
+
type: 'unknown',
|
|
222
|
+
epicLessonUrl,
|
|
223
|
+
epicLessonSlug,
|
|
224
|
+
epicCompletedAt,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return progress;
|
|
230
|
+
}
|
|
231
|
+
function getProgressForLesson(epicLessonSlug, { workshopInstructions, workshopFinished, exercises, }) {
|
|
232
|
+
const hasEmbed = (embed) => embed?.some((e) => e.split('/').at(-1) === epicLessonSlug);
|
|
233
|
+
if (workshopInstructions.compiled.status === 'success' &&
|
|
234
|
+
hasEmbed(workshopInstructions.compiled.epicVideoEmbeds)) {
|
|
235
|
+
return { type: 'workshop-instructions' };
|
|
236
|
+
}
|
|
237
|
+
if (workshopFinished.compiled.status === 'success' &&
|
|
238
|
+
hasEmbed(workshopFinished.compiled.epicVideoEmbeds)) {
|
|
239
|
+
return { type: 'workshop-finished' };
|
|
240
|
+
}
|
|
241
|
+
for (const exercise of exercises) {
|
|
242
|
+
if (hasEmbed(exercise.instructionsEpicVideoEmbeds)) {
|
|
243
|
+
return {
|
|
244
|
+
type: 'instructions',
|
|
245
|
+
exerciseNumber: exercise.exerciseNumber,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (hasEmbed(exercise.finishedEpicVideoEmbeds)) {
|
|
249
|
+
return {
|
|
250
|
+
type: 'finished',
|
|
251
|
+
exerciseNumber: exercise.exerciseNumber,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
for (const step of exercise.steps.filter(Boolean)) {
|
|
255
|
+
if (hasEmbed(step.problem?.epicVideoEmbeds)) {
|
|
256
|
+
return {
|
|
257
|
+
type: 'step',
|
|
258
|
+
exerciseNumber: exercise.exerciseNumber,
|
|
259
|
+
stepNumber: step.stepNumber,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
export async function updateProgress({ lessonSlug, complete }, { timings, request, } = {}) {
|
|
266
|
+
if (ENV.EPICSHOP_DEPLOYED) {
|
|
267
|
+
return {
|
|
268
|
+
status: 'error',
|
|
269
|
+
error: 'cannot update progress when deployed',
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const authInfo = await getAuthInfo();
|
|
273
|
+
if (!authInfo) {
|
|
274
|
+
return { status: 'error', error: 'not authenticated' };
|
|
275
|
+
}
|
|
276
|
+
const { product: { host }, } = getWorkshopConfig();
|
|
277
|
+
const response = await fetch(`https://${host}/api/progress`, {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
headers: {
|
|
280
|
+
authorization: `Bearer ${authInfo.tokenSet.access_token}`,
|
|
281
|
+
'content-type': 'application/json',
|
|
282
|
+
},
|
|
283
|
+
body: JSON.stringify(complete ? { lessonSlug } : { lessonSlug, remove: true }),
|
|
284
|
+
}).catch((e) => new Response(getErrorMessage(e), { status: 500 }));
|
|
285
|
+
// force the progress to be fresh whether or not we're successful
|
|
286
|
+
await getEpicProgress({ forceFresh: true, request, timings });
|
|
287
|
+
if (response.status < 200 || response.status >= 300) {
|
|
288
|
+
return {
|
|
289
|
+
status: 'error',
|
|
290
|
+
error: `${response.status} ${response.statusText}`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
return { status: 'success' };
|
|
294
|
+
}
|
|
295
|
+
const ModuleSchema = z.object({
|
|
296
|
+
resources: z.array(z.union([
|
|
297
|
+
z.object({
|
|
298
|
+
_type: z.literal('lesson'),
|
|
299
|
+
_id: z.string(),
|
|
300
|
+
slug: z.string(),
|
|
301
|
+
}),
|
|
302
|
+
z.object({
|
|
303
|
+
_type: z.literal('section'),
|
|
304
|
+
lessons: z.array(z.object({ _id: z.string(), slug: z.string() })),
|
|
305
|
+
}),
|
|
306
|
+
])),
|
|
307
|
+
});
|
|
308
|
+
export async function getWorkshopData(slug, { timings, request, forceFresh, } = {}) {
|
|
309
|
+
if (ENV.EPICSHOP_DEPLOYED)
|
|
310
|
+
return { resources: [] };
|
|
311
|
+
const authInfo = await getAuthInfo();
|
|
312
|
+
// auth is not required, but we only use it for progress which is only needed
|
|
313
|
+
// if you're authenticated anyway.
|
|
314
|
+
if (!authInfo)
|
|
315
|
+
return { resources: [] };
|
|
316
|
+
const { product: { host }, } = getWorkshopConfig();
|
|
317
|
+
return cachified({
|
|
318
|
+
key: `epic-workshop-data:${host}:${slug}`,
|
|
319
|
+
cache: fsCache,
|
|
320
|
+
request,
|
|
321
|
+
forceFresh,
|
|
322
|
+
timings,
|
|
323
|
+
checkValue: ModuleSchema,
|
|
324
|
+
async getFreshValue() {
|
|
325
|
+
const response = await fetch(`https://${host}/api/workshops/${encodeURIComponent(slug)}`).catch((e) => new Response(getErrorMessage(e), { status: 500 }));
|
|
326
|
+
if (response.status < 200 || response.status >= 300) {
|
|
327
|
+
console.error(`Failed to fetch workshop data from EpicWeb for ${slug}: ${response.status} ${response.statusText}`);
|
|
328
|
+
return { resources: [] };
|
|
329
|
+
}
|
|
330
|
+
return ModuleSchema.parse(await response.json());
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
export async function userHasAccessToWorkshop({ timings, request, forceFresh, }) {
|
|
335
|
+
const config = getWorkshopConfig();
|
|
336
|
+
const { product: { host, slug }, } = config;
|
|
337
|
+
if (!slug)
|
|
338
|
+
return true;
|
|
339
|
+
if (ENV.EPICSHOP_DEPLOYED) {
|
|
340
|
+
const cookieHeader = request?.headers.get('Cookie');
|
|
341
|
+
if (!cookieHeader)
|
|
342
|
+
return false;
|
|
343
|
+
const cookies = cookie.parse(cookieHeader);
|
|
344
|
+
return cookies.skill?.split(',').includes(slug) ?? false;
|
|
345
|
+
}
|
|
346
|
+
const authInfo = await getAuthInfo();
|
|
347
|
+
if (!authInfo)
|
|
348
|
+
return false;
|
|
349
|
+
return cachified({
|
|
350
|
+
key: `user-has-access-to-workshop:${host}:${slug}`,
|
|
351
|
+
cache: fsCache,
|
|
352
|
+
request,
|
|
353
|
+
forceFresh,
|
|
354
|
+
timings,
|
|
355
|
+
ttl: 1000 * 5,
|
|
356
|
+
checkValue: z.boolean(),
|
|
357
|
+
async getFreshValue(context) {
|
|
358
|
+
const response = await fetch(`https://${host}/api/workshops/${encodeURIComponent(slug)}/access`, {
|
|
359
|
+
headers: {
|
|
360
|
+
authorization: `Bearer ${authInfo.tokenSet.access_token}`,
|
|
361
|
+
},
|
|
362
|
+
}).catch((e) => new Response(getErrorMessage(e), { status: 500 }));
|
|
363
|
+
const hasAccess = response.ok ? (await response.json()) === true : false;
|
|
364
|
+
if (hasAccess) {
|
|
365
|
+
context.metadata.ttl = 1000 * 60 * 60 * 24 * 30;
|
|
366
|
+
context.metadata.swr = 1000 * 60 * 60 * 24 * 30;
|
|
367
|
+
}
|
|
368
|
+
return hasAccess;
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const UserInfoSchema = z
|
|
373
|
+
.object({
|
|
374
|
+
id: z.string(),
|
|
375
|
+
name: z.string().nullable(),
|
|
376
|
+
email: z.string().email(),
|
|
377
|
+
image: z.string().nullable(),
|
|
378
|
+
discordProfile: z
|
|
379
|
+
.object({
|
|
380
|
+
nick: z.string().nullable(),
|
|
381
|
+
user: z.object({
|
|
382
|
+
id: z.string(),
|
|
383
|
+
username: z.string(),
|
|
384
|
+
avatar: z.string().nullable().optional(),
|
|
385
|
+
global_name: z.string().nullable().optional(),
|
|
386
|
+
}),
|
|
387
|
+
})
|
|
388
|
+
.nullable()
|
|
389
|
+
.optional(),
|
|
390
|
+
})
|
|
391
|
+
.transform((data) => {
|
|
392
|
+
return {
|
|
393
|
+
...data,
|
|
394
|
+
imageUrlSmall: resizeImageUrl(data.image, { size: 64 }) ??
|
|
395
|
+
resolveDiscordAvatar(data.discordProfile?.user, {
|
|
396
|
+
size: 64,
|
|
397
|
+
}) ??
|
|
398
|
+
resolveGravatarUrl(data.email, { size: 64 }),
|
|
399
|
+
imageUrlLarge: resizeImageUrl(data.image, { size: 512 }) ??
|
|
400
|
+
resolveDiscordAvatar(data.discordProfile?.user, {
|
|
401
|
+
size: 512,
|
|
402
|
+
}) ??
|
|
403
|
+
resolveGravatarUrl(data.email, { size: 512 }),
|
|
404
|
+
};
|
|
405
|
+
});
|
|
406
|
+
function resizeImageUrl(url, { size }) {
|
|
407
|
+
if (!url)
|
|
408
|
+
return null;
|
|
409
|
+
const urlObj = new URL(url);
|
|
410
|
+
urlObj.searchParams.set('size', size.toString());
|
|
411
|
+
return urlObj.toString();
|
|
412
|
+
}
|
|
413
|
+
function resolveGravatarUrl(email, { size }) {
|
|
414
|
+
if (!email)
|
|
415
|
+
return null;
|
|
416
|
+
const hash = md5(email.toLowerCase());
|
|
417
|
+
const gravatarOptions = new URLSearchParams({
|
|
418
|
+
size: size.toString(),
|
|
419
|
+
default: 'identicon',
|
|
420
|
+
});
|
|
421
|
+
return `https://www.gravatar.com/avatar/${hash}?${gravatarOptions.toString()}`;
|
|
422
|
+
}
|
|
423
|
+
function resolveDiscordAvatar(user, { size }) {
|
|
424
|
+
if (!user)
|
|
425
|
+
return null;
|
|
426
|
+
const { avatar, id: userId } = user;
|
|
427
|
+
if (!avatar)
|
|
428
|
+
return null;
|
|
429
|
+
const isGif = avatar.startsWith('a_');
|
|
430
|
+
const url = new URL(`/avatars/${userId}/${avatar}.${isGif ? 'gif' : 'png'}`, 'https://cdn.discordapp.com');
|
|
431
|
+
url.searchParams.set('size', size.toString());
|
|
432
|
+
return url.toString();
|
|
433
|
+
}
|
|
434
|
+
export async function getUserInfo({ timings, request, forceFresh, } = {}) {
|
|
435
|
+
const authInfo = await getAuthInfo();
|
|
436
|
+
if (!authInfo)
|
|
437
|
+
return null;
|
|
438
|
+
const { tokenSet } = authInfo;
|
|
439
|
+
const { product: { host }, } = getWorkshopConfig();
|
|
440
|
+
const accessToken = tokenSet.access_token;
|
|
441
|
+
const url = `https://${host}/oauth/userinfo`;
|
|
442
|
+
const userInfo = await cachified({
|
|
443
|
+
key: `${url}:${md5(accessToken)}`,
|
|
444
|
+
cache: fsCache,
|
|
445
|
+
request,
|
|
446
|
+
forceFresh,
|
|
447
|
+
timings,
|
|
448
|
+
ttl: 1000 * 30,
|
|
449
|
+
swr: 1000 * 60 * 60 * 24 * 365,
|
|
450
|
+
checkValue: UserInfoSchema,
|
|
451
|
+
async getFreshValue() {
|
|
452
|
+
const response = await fetch(url, {
|
|
453
|
+
headers: { authorization: `Bearer ${accessToken}` },
|
|
454
|
+
});
|
|
455
|
+
if (!response.ok) {
|
|
456
|
+
throw new Error(`Failed to fetch user info: ${response.statusText}`);
|
|
457
|
+
}
|
|
458
|
+
const data = await response.json();
|
|
459
|
+
return UserInfoSchema.parse(data);
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
// we used to md5 hash the email to get the id
|
|
463
|
+
// if the id doesn't match what we have on file, update it
|
|
464
|
+
// you can probably safely remove this in January 2025
|
|
465
|
+
if (userInfo && authInfo.id !== userInfo.id) {
|
|
466
|
+
await setAuthInfo({
|
|
467
|
+
...authInfo,
|
|
468
|
+
id: userInfo.id,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
return userInfo;
|
|
472
|
+
}
|
|
473
|
+
//# sourceMappingURL=epic-api.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"epic-api.server.js","sourceRoot":"","sources":["../../src/epic-api.server.ts"],"names":[],"mappings":"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","sourcesContent":["import * as cookie from 'cookie'\nimport md5 from 'md5-hex'\nimport { z } from 'zod'\nimport {\n\tgetExercises,\n\tgetWorkshopFinished,\n\tgetWorkshopInstructions,\n} from './apps.server.js'\nimport { cachified, fsCache } from './cache.server.js'\nimport { getWorkshopConfig } from './config.server.js'\nimport { getAuthInfo, setAuthInfo } from './db.server.js'\nimport { type Timings } from './timing.server.js'\nimport { getErrorMessage } from './utils.js'\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\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\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) return {}\n\tconst authInfo = await getAuthInfo()\n\tif (ENV.EPICSHOP_DEPLOYED) return {}\n\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\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\treturn cachified({\n\t\tkey,\n\t\trequest,\n\t\tcache: fsCache,\n\t\ttimings,\n\t\tttl: 1000 * 60 * 60,\n\t\tswr: 1000 * 60 * 60 * 24 * 30,\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) {\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\tconst infoResponse = await fetch(\n\t\t\t\t`https://${epicUrl.host}/api${epicUrl.pathname}`,\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\tif (infoResponse.status >= 200 && infoResponse.status < 300) {\n\t\t\t\tconst rawInfo = await infoResponse.json()\n\t\t\t\tconst infoResult = EpicVideoInfoSchema.safeParse(rawInfo)\n\t\t\t\tif (infoResult.success) {\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 // 2 seconds\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\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\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 // 2 seconds\n\t\t\t\tcontext.metadata.swr = 0\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\tconsole.error(`Failed to fetch epic video info for ${epicVideoEmbed}`, e)\n\t\tthrow e\n\t})\n}\n\nasync function getEpicProgress({\n\ttimings,\n\trequest,\n\tforceFresh,\n}: { timings?: Timings; request?: Request; forceFresh?: boolean } = {}) {\n\tif (ENV.EPICSHOP_DEPLOYED) return []\n\tconst authInfo = await getAuthInfo()\n\tconst {\n\t\tproduct: { host },\n\t} = getWorkshopConfig()\n\tif (!authInfo) return []\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\treturn cachified({\n\t\tkey: `epic-progress:${host}:${tokenPart}`,\n\t\tcache: fsCache,\n\t\trequest,\n\t\ttimings,\n\t\tforceFresh,\n\t\tttl: 1000 * 2,\n\t\tswr: 1000 * 60 * 60 * 24 * 30,\n\t\tcheckValue: EpicProgressSchema,\n\t\tasync getFreshValue(context): Promise<z.infer<typeof EpicProgressSchema>> {\n\t\t\tconst response = await fetch(`https://${host}/api/progress`, {\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\t\t\tif (response.status < 200 || response.status >= 300) {\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 // 2 seconds\n\t\t\t\tcontext.metadata.swr = 0\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn EpicProgressSchema.parse(await response.json())\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 (ENV.EPICSHOP_DEPLOYED) return []\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) return []\n\tconst {\n\t\tproduct: { slug, host },\n\t} = getWorkshopConfig()\n\tif (!slug) return []\n\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\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 (ENV.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\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) {\n\t\treturn { status: 'error', error: 'not authenticated' } as const\n\t}\n\tconst {\n\t\tproduct: { host },\n\t} = getWorkshopConfig()\n\n\tconst response = await fetch(`https://${host}/api/progress`, {\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(\n\t\t\tcomplete ? { lessonSlug } : { lessonSlug, remove: true },\n\t\t),\n\t}).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\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\treturn {\n\t\t\tstatus: 'error',\n\t\t\terror: `${response.status} ${response.statusText}`,\n\t\t} as const\n\t}\n\n\treturn { status: 'success' } as const\n}\n\nconst ModuleSchema = z.object({\n\tresources: z.array(\n\t\tz.union([\n\t\t\tz.object({\n\t\t\t\t_type: z.literal('lesson'),\n\t\t\t\t_id: z.string(),\n\t\t\t\tslug: z.string(),\n\t\t\t}),\n\t\t\tz.object({\n\t\t\t\t_type: z.literal('section'),\n\t\t\t\tlessons: z.array(z.object({ _id: z.string(), slug: z.string() })),\n\t\t\t}),\n\t\t]),\n\t),\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 (ENV.EPICSHOP_DEPLOYED) return { resources: [] }\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\treturn cachified({\n\t\tkey: `epic-workshop-data:${host}:${slug}`,\n\t\tcache: fsCache,\n\t\trequest,\n\t\tforceFresh,\n\t\ttimings,\n\t\tcheckValue: ModuleSchema,\n\t\tasync getFreshValue(): Promise<z.infer<typeof ModuleSchema>> {\n\t\t\tconst response = await fetch(\n\t\t\t\t`https://${host}/api/workshops/${encodeURIComponent(slug)}`,\n\t\t\t).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\n\t\t\tif (response.status < 200 || response.status >= 300) {\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\t\t\treturn ModuleSchema.parse(await response.json())\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 (ENV.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: fsCache,\n\t\trequest,\n\t\tforceFresh,\n\t\ttimings,\n\t\tttl: 1000 * 5,\n\t\tcheckValue: z.boolean(),\n\t\tasync getFreshValue(context) {\n\t\t\tconst response = await fetch(\n\t\t\t\t`https://${host}/api/workshops/${encodeURIComponent(slug)}/access`,\n\t\t\t\t{\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tauthorization: `Bearer ${authInfo.tokenSet.access_token}`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t).catch((e) => new Response(getErrorMessage(e), { status: 500 }))\n\t\t\tconst hasAccess = response.ok ? (await response.json()) === true : false\n\n\t\t\tif (hasAccess) {\n\t\t\t\tcontext.metadata.ttl = 1000 * 60 * 60 * 24 * 30\n\t\t\t\tcontext.metadata.swr = 1000 * 60 * 60 * 24 * 30\n\t\t\t}\n\n\t\t\treturn hasAccess\n\t\t},\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(),\n\t\t\t\tuser: z.object({\n\t\t\t\t\tid: z.string(),\n\t\t\t\t\tusername: z.string(),\n\t\t\t\t\tavatar: z.string().nullable().optional(),\n\t\t\t\t\tglobal_name: z.string().nullable().optional(),\n\t\t\t\t}),\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\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\tconst userInfo = await cachified({\n\t\tkey: `${url}:${md5(accessToken)}`,\n\t\tcache: fsCache,\n\t\trequest,\n\t\tforceFresh,\n\t\ttimings,\n\t\tttl: 1000 * 30,\n\t\tswr: 1000 * 60 * 60 * 24 * 365,\n\t\tcheckValue: UserInfoSchema,\n\t\tasync getFreshValue(): Promise<UserInfo> {\n\t\t\tconst response = await fetch(url, {\n\t\t\t\theaders: { authorization: `Bearer ${accessToken}` },\n\t\t\t})\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`Failed to fetch user info: ${response.statusText}`)\n\t\t\t}\n\n\t\t\tconst data = await response.json()\n\t\t\treturn UserInfoSchema.parse(data)\n\t\t},\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"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function getUserId({ request }: {
|
|
2
|
+
request: Request;
|
|
3
|
+
}): Promise<{
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly type: "cookie.clientId";
|
|
6
|
+
} | {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly type: "cookie.randomId";
|
|
9
|
+
} | {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly type: "db.authInfo";
|
|
12
|
+
} | {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly type: "db.clientId";
|
|
15
|
+
}>;
|
|
16
|
+
export declare function getSetClientIdCookieHeader(clientId: string): string;
|
|
17
|
+
//# sourceMappingURL=user.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user.server.d.ts","sourceRoot":"","sources":["../../src/user.server.ts"],"names":[],"mappings":"AAIA,wBAAsB,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE;;;;;;;;;;;;GAiChE;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,UAE1D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createId as cuid } from '@paralleldrive/cuid2';
|
|
2
|
+
import * as cookie from 'cookie';
|
|
3
|
+
import { getAuthInfo, getClientId } from './db.server.js';
|
|
4
|
+
export async function getUserId({ request }) {
|
|
5
|
+
if (ENV.EPICSHOP_DEPLOYED) {
|
|
6
|
+
const cookieHeader = request.headers.get('cookie');
|
|
7
|
+
const cookieValue = cookie.parse(cookieHeader ?? '');
|
|
8
|
+
if (cookieValue.clientId) {
|
|
9
|
+
return {
|
|
10
|
+
id: cookieValue.clientId,
|
|
11
|
+
type: 'cookie.clientId',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
const newId = cuid();
|
|
16
|
+
return {
|
|
17
|
+
id: newId,
|
|
18
|
+
type: 'cookie.randomId',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const authInfo = await getAuthInfo();
|
|
23
|
+
if (authInfo?.id) {
|
|
24
|
+
return {
|
|
25
|
+
id: authInfo.id,
|
|
26
|
+
type: 'db.authInfo',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const clientId = await getClientId();
|
|
30
|
+
return {
|
|
31
|
+
id: clientId,
|
|
32
|
+
type: 'db.clientId',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function getSetClientIdCookieHeader(clientId) {
|
|
36
|
+
return `clientId=${clientId}; Path=/; HttpOnly; SameSite=Lax`;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=user.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user.server.js","sourceRoot":"","sources":["../../src/user.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAA;AAChC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEzD,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,EAAE,OAAO,EAAwB;IAChE,IAAI,GAAG,CAAC,iBAAiB,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAClD,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC,CAAA;QAEpD,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;YAC1B,OAAO;gBACN,EAAE,EAAE,WAAW,CAAC,QAAQ;gBACxB,IAAI,EAAE,iBAAiB;aACd,CAAA;QACX,CAAC;aAAM,CAAC;YACP,MAAM,KAAK,GAAG,IAAI,EAAE,CAAA;YACpB,OAAO;gBACN,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,iBAAiB;aACd,CAAA;QACX,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAA;IAEpC,IAAI,QAAQ,EAAE,EAAE,EAAE,CAAC;QAClB,OAAO;YACN,EAAE,EAAE,QAAQ,CAAC,EAAE;YACf,IAAI,EAAE,aAAa;SACV,CAAA;IACX,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAA;IACpC,OAAO;QACN,EAAE,EAAE,QAAQ;QACZ,IAAI,EAAE,aAAa;KACV,CAAA;AACX,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,QAAgB;IAC1D,OAAO,YAAY,QAAQ,kCAAkC,CAAA;AAC9D,CAAC","sourcesContent":["import { createId as cuid } from '@paralleldrive/cuid2'\nimport * as cookie from 'cookie'\nimport { getAuthInfo, getClientId } from './db.server.js'\n\nexport async function getUserId({ request }: { request: Request }) {\n\tif (ENV.EPICSHOP_DEPLOYED) {\n\t\tconst cookieHeader = request.headers.get('cookie')\n\t\tconst cookieValue = cookie.parse(cookieHeader ?? '')\n\n\t\tif (cookieValue.clientId) {\n\t\t\treturn {\n\t\t\t\tid: cookieValue.clientId,\n\t\t\t\ttype: 'cookie.clientId',\n\t\t\t} as const\n\t\t} else {\n\t\t\tconst newId = cuid()\n\t\t\treturn {\n\t\t\t\tid: newId,\n\t\t\t\ttype: 'cookie.randomId',\n\t\t\t} as const\n\t\t}\n\t}\n\n\tconst authInfo = await getAuthInfo()\n\n\tif (authInfo?.id) {\n\t\treturn {\n\t\t\tid: authInfo.id,\n\t\t\ttype: 'db.authInfo',\n\t\t} as const\n\t}\n\n\tconst clientId = await getClientId()\n\treturn {\n\t\tid: clientId,\n\t\ttype: 'db.clientId',\n\t} as const\n}\n\nexport function getSetClientIdCookieHeader(clientId: string) {\n\treturn `clientId=${clientId}; Path=/; HttpOnly; SameSite=Lax`\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epic-web/workshop-utils",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
"./package.json": "./package.json",
|
|
15
15
|
"./apps.server": "./src/apps.server.ts",
|
|
16
16
|
"./env.server": "./src/env.server.ts",
|
|
17
|
+
"./epic-api.server": "./src/epic-api.server.ts",
|
|
18
|
+
"./user.server": "./src/user.server.ts",
|
|
17
19
|
"./cache.server": "./src/cache.server.ts",
|
|
18
20
|
"./config.server": "./src/config.server.ts",
|
|
19
21
|
"./db.server": "./src/db.server.ts",
|
|
@@ -43,6 +45,18 @@
|
|
|
43
45
|
"default": "./dist/esm/env.server.js"
|
|
44
46
|
}
|
|
45
47
|
},
|
|
48
|
+
"./epic-api.server": {
|
|
49
|
+
"import": {
|
|
50
|
+
"types": "./dist/esm/epic-api.server.d.ts",
|
|
51
|
+
"default": "./dist/esm/epic-api.server.js"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"./user.server": {
|
|
55
|
+
"import": {
|
|
56
|
+
"types": "./dist/esm/user.server.d.ts",
|
|
57
|
+
"default": "./dist/esm/user.server.js"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
46
60
|
"./cache.server": {
|
|
47
61
|
"import": {
|
|
48
62
|
"types": "./dist/esm/cache.server.d.ts",
|
|
@@ -135,8 +149,9 @@
|
|
|
135
149
|
"@epic-web/remember": "^1.1.0",
|
|
136
150
|
"@kentcdodds/md-temp": "^9.0.1",
|
|
137
151
|
"@mdx-js/mdx": "^3.0.1",
|
|
152
|
+
"@paralleldrive/cuid2": "^2.2.2",
|
|
138
153
|
"@playwright/test": "^1.47.2",
|
|
139
|
-
"@remix-run/node": "
|
|
154
|
+
"@remix-run/node": "2.12.1",
|
|
140
155
|
"@testing-library/dom": "^10.4.0",
|
|
141
156
|
"@testing-library/jest-dom": "^6.5.0",
|
|
142
157
|
"@total-typescript/ts-reset": "^0.6.1",
|
|
@@ -148,6 +163,7 @@
|
|
|
148
163
|
"chalk": "^5.3.0",
|
|
149
164
|
"chokidar": "^4.0.1",
|
|
150
165
|
"close-with-grace": "^2.1.0",
|
|
166
|
+
"cookie": "^1.0.1",
|
|
151
167
|
"cross-spawn": "^7.0.3",
|
|
152
168
|
"execa": "^9.4.0",
|
|
153
169
|
"fkill": "^9.0.0",
|