Skip to content

Commit 2fcfdab

Browse files
authored
Merge pull request #490 from SprocketBot/feat/scrims
Initial Implementations of Scrims
2 parents d2c1334 + e7cd100 commit 2fcfdab

File tree

96 files changed

+1288
-362
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+1288
-362
lines changed

clients/web/houdini.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ const config = {
88
}
99
},
1010
"plugins": {
11-
"houdini-svelte": {}
11+
"houdini-svelte": {},
1212
},
13+
scalars: {
14+
DateTime: { type: Date.name }
15+
}
1316
}
1417

1518
export default config

clients/web/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@steeze-ui/svelte-icon": "^1.3.2",
2222
"@sveltejs/adapter-auto": "^1.0.1",
2323
"@sveltejs/kit": "^1.1.1",
24+
"@types/lodash.startcase": "^4.4.7",
2425
"@types/lodash.times": "^4.3.7",
2526
"@typescript-eslint/eslint-plugin": "^5.27.0",
2627
"@typescript-eslint/parser": "^5.27.0",
@@ -53,8 +54,9 @@
5354
"@fontsource/montserrat": "^4.5.14",
5455
"@sveltejs/adapter-node": "^1.0.0-next.106",
5556
"echarts": "^5.4.1",
56-
"graphql-ws": "^5.11.3",
5757
"jwt-decode": "^3.1.2",
58+
"lodash.startcase": "^4.4.0",
59+
"subscriptions-transport-ws": "^0.11.0",
5860
"typescript-cookie": "^1.0.4"
5961
}
6062
}

clients/web/src/client.ts

Lines changed: 19 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import {env} from "$env/dynamic/public";
2-
import {HoudiniClient, RefreshLoginStore} from "$houdini";
3-
import {createClient} from "graphql-ws";
2+
import {HoudiniClient} from "$houdini";
3+
import {SubscriptionClient} from "subscriptions-transport-ws";
44
import {subscription} from "$houdini/plugins";
55
import {goto} from "$app/navigation";
6-
import {browser} from "$app/environment";
7-
import {getAuthCookies, updateAuthCookies} from "$lib/api/auth-cookies";
8-
import {getExpiryFromJwt} from "./lib/utilities/getExpiryFromJwt";
9-
import {clearAuthCookies} from "./lib/api/auth-cookies";
6+
import {getAuthCookies, clearAuthCookies} from "$lib/api";
107
import {redirect} from "@sveltejs/kit";
11-
import jwtDecode from "jwt-decode";
8+
import {refreshAuthPlugin} from "./houdini/refresh-auth.plugin";
129

1310
const getAuthToken = ({
1411
session,
@@ -61,81 +58,23 @@ export default new HoudiniClient({
6158
},
6259
},
6360
plugins: [
64-
() => {
61+
refreshAuthPlugin,
62+
subscription(ctx => {
63+
const c = new SubscriptionClient(`${env.PUBLIC_GQL_URL.replace("http", "ws")}/graphql`, {
64+
reconnect: true,
65+
lazy: true,
66+
});
6567
return {
66-
beforeNetwork: async (ctx, b) => {
67-
const {next} = b;
68-
const {session} = ctx;
69-
70-
// TODO: Actually check the thing
71-
if (!session?.access) {
72-
next(ctx);
73-
return;
74-
}
75-
const expAt = getExpiryFromJwt(session?.access);
76-
77-
// If there is at least one minute on the token; do nothing
78-
if (expAt.getTime() > new Date().getTime() + 60 * 1000) {
79-
next(ctx);
80-
return;
81-
}
82-
if (ctx.artifact.name === "RefreshLogin") {
83-
console.debug(
84-
"Avoiding infinite loop. Will not try to refresh auth before a refresh auth call.",
85-
);
86-
next(ctx);
87-
return;
88-
}
89-
90-
// Otherwise we need to refresh
91-
console.log("Attempting to refresh authentication");
92-
93-
if (session.refresh) {
94-
const s = new RefreshLoginStore();
95-
try {
96-
const result = await s.mutate(null, {
97-
metadata: {
98-
accessTokenOverride: session.refresh,
99-
},
100-
fetch: fetch ?? ctx.fetch,
101-
});
102-
if (!result.data) {
103-
throw new Error(
104-
result.errors?.map(e => e.message).join("\n") ?? "Refresh Login Response Empty",
105-
);
106-
}
107-
108-
if (!ctx.session) ctx.session = {};
109-
ctx.session.access = result.data.refreshLogin.access;
110-
ctx.session.refresh = result.data.refreshLogin.refresh;
111-
112-
if (browser) {
113-
updateAuthCookies(result.data.refreshLogin);
114-
} else {
115-
// How do I set cookies here
116-
console.warn(
117-
"Failed to persist auth cookie updates. Setting cookies in a plugin is not possible?",
118-
);
119-
}
120-
console.debug("Auth has been refreshed");
121-
} catch (e) {
122-
console.warn("Failed to refresh auth token!", e);
123-
}
124-
} else {
125-
console.debug("Skipping Auth Refresh");
126-
}
127-
128-
next(ctx);
68+
subscribe(payload, handlers) {
69+
const {unsubscribe} = c
70+
.request({
71+
...payload,
72+
context: {authorization: getAuthToken() ? `Bearer ${getAuthToken()}` : undefined},
73+
})
74+
.subscribe(handlers);
75+
return unsubscribe;
12976
},
13077
};
131-
},
132-
subscription(ctx =>
133-
createClient({
134-
url: `${env.PUBLIC_GQL_URL}/graphql`,
135-
connectionParams: {
136-
authorization: ctx.session?.access ? `Bearer ${ctx.session.access}` : "",
137-
},
138-
}),
139-
),
78+
}),
14079
],
14180
});

clients/web/src/hooks.server.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
import type {Handle} from "@sveltejs/kit";
1+
import type {Handle, HandleServerError} from "@sveltejs/kit";
22
import {sequence} from "@sveltejs/kit/hooks";
33
import {HoudiniSessionHook} from "./hooks/HoudiniSession.hook";
44

55
export const handle: Handle = sequence(HoudiniSessionHook);
6+
export const handleError: HandleServerError = ({error, event}) => {
7+
return {
8+
message: "An Error has occurred!",
9+
};
10+
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {browser} from "$app/environment";
2+
import {RefreshLoginStore} from "$houdini";
3+
import {updateAuthCookies} from "$lib/api";
4+
import {getExpiryFromJwt} from "$lib/utilities/getExpiryFromJwt";
5+
import type {ClientPlugin} from "$houdini";
6+
7+
export const refreshAuthPlugin: ClientPlugin = () => {
8+
return {
9+
beforeNetwork: async (ctx, b) => {
10+
const {next} = b;
11+
const {session} = ctx;
12+
13+
// TODO: Actually check the thing
14+
if (!session?.access) {
15+
next(ctx);
16+
return;
17+
}
18+
const expAt = getExpiryFromJwt(session?.access);
19+
20+
// If there is at least one minute on the token; do nothing
21+
if (expAt.getTime() > new Date().getTime() + 60 * 1000) {
22+
next(ctx);
23+
return;
24+
}
25+
if (ctx.artifact.name === "RefreshLogin") {
26+
console.debug("Avoiding infinite loop. Will not try to refresh auth before a refresh auth call.");
27+
next(ctx);
28+
return;
29+
}
30+
31+
// Otherwise we need to refresh
32+
console.log("Attempting to refresh authentication");
33+
34+
if (session.refresh) {
35+
const s = new RefreshLoginStore();
36+
try {
37+
const result = await s.mutate(null, {
38+
metadata: {
39+
accessTokenOverride: session.refresh,
40+
},
41+
fetch: fetch ?? ctx.fetch,
42+
});
43+
if (!result.data) {
44+
throw new Error(
45+
result.errors?.map(e => e.message).join("\n") ?? "Refresh Login Response Empty",
46+
);
47+
}
48+
49+
if (!ctx.session) ctx.session = {};
50+
ctx.session.access = result.data.refreshLogin.access;
51+
ctx.session.refresh = result.data.refreshLogin.refresh;
52+
53+
if (browser) {
54+
updateAuthCookies(result.data.refreshLogin);
55+
} else {
56+
// How do I set cookies here
57+
console.warn(
58+
"Failed to persist auth cookie updates. Setting cookies in a plugin is not possible?",
59+
);
60+
}
61+
console.debug("Auth has been refreshed");
62+
} catch (e) {
63+
console.warn("Failed to refresh auth token!", e);
64+
}
65+
} else {
66+
console.debug("Skipping Auth Refresh");
67+
}
68+
69+
next(ctx);
70+
},
71+
};
72+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./UserInfo.context";
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./UserInfo";
2+
export * from "./utils";
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {onMount} from "svelte";
2+
import {RefreshLoginStore} from "$houdini";
3+
import {getAuthCookies, updateAuthCookies} from "./auth-cookies";
4+
5+
export const authIntervalRefresh = () =>
6+
onMount(() => {
7+
const refreshStore = new RefreshLoginStore();
8+
9+
const refreshAuth = async () => {
10+
const {refresh} = getAuthCookies();
11+
const r = await refreshStore.mutate(null, {metadata: {accessTokenOverride: refresh}});
12+
if (!r.data) return;
13+
console.debug("Auth Refreshed");
14+
updateAuthCookies(r.data?.refreshLogin);
15+
};
16+
17+
// Refresh auth token every 2 minutes
18+
const i = setInterval(refreshAuth, 1000 * 60 * 2);
19+
refreshAuth();
20+
return () => clearInterval(i);
21+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./auth-cookies";
2+
export * from "./auth-refresh-interval";
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
query MemberGames {
2+
getMemberGames @list(name: "MemberGames") {
3+
id
4+
title
5+
modes {
6+
id
7+
code
8+
description
9+
teamSize
10+
teamCount
11+
}
12+
}
13+
}

clients/web/src/lib/api/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./authentication";
2+
export * from "./scrims";
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
mutation CreateScrim($options: CreateScrimInput!) {
2+
createScrim(data: $options) {
3+
id
4+
createdAt
5+
gameMode {
6+
description
7+
}
8+
maxPlayers
9+
playerCount
10+
settings {
11+
competitive
12+
mode
13+
observable
14+
teamCount
15+
teamSize
16+
}
17+
skillGroup {
18+
description
19+
}
20+
status
21+
}
22+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
fragment CurrentScrimFragment on ScrimObject {
2+
id
3+
author {
4+
displayName
5+
}
6+
playerCount
7+
maxPlayers
8+
status
9+
skillGroup {
10+
description
11+
}
12+
gameMode {
13+
description
14+
}
15+
settings {
16+
competitive
17+
mode
18+
}
19+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
query CurrentScrim {
2+
getCurrentScrim {
3+
...CurrentScrimFragment @mask_disable
4+
}
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
subscription CurrentScrimSub {
2+
followCurrentScrim {
3+
scrim {
4+
...CurrentScrimFragment @mask_disable
5+
}
6+
event
7+
}
8+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {readable, type Readable} from "svelte/store";
2+
import type {CurrentScrim$result, CurrentScrimStore, CurrentScrimSub$result, CurrentScrimSubStore} from "$houdini";
3+
4+
export const CurrentScrimLiveStore = (
5+
queryStore: CurrentScrimStore,
6+
subStore: CurrentScrimSubStore,
7+
eventMap?: Partial<Record<CurrentScrimSub$result["followCurrentScrim"]["event"], () => void>>,
8+
): Readable<CurrentScrim$result["getCurrentScrim"]> => {
9+
return readable<CurrentScrim$result["getCurrentScrim"]>(null, set => {
10+
const queryUnsub = queryStore.subscribe($q => {
11+
if ($q.data?.getCurrentScrim) {
12+
set($q.data?.getCurrentScrim);
13+
}
14+
});
15+
const subUnsub = subStore.subscribe($s => {
16+
const eventName = $s.data?.followCurrentScrim.event;
17+
if (eventName) {
18+
const handler = eventMap?.[eventName];
19+
if (handler) handler();
20+
}
21+
if ($s.data) {
22+
if (["EMPTY", "CANCELLED", "COMPLETE"].includes($s.data.followCurrentScrim.scrim.status)) {
23+
// Scrim end states
24+
console.log("Scrim has ended");
25+
set(null);
26+
} else {
27+
set($s.data.followCurrentScrim.scrim);
28+
}
29+
}
30+
});
31+
32+
return () => {
33+
queryUnsub();
34+
subUnsub();
35+
};
36+
});
37+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {getContext, setContext} from "svelte";
2+
import type {CurrentScrim$result} from "$houdini";
3+
import type {Readable} from "svelte/store";
4+
5+
const CurrentScrimContextKey = "CURRENT_SCRIM_CONTEXT_KEY";
6+
7+
export type CurrentScrimContextValue = Readable<CurrentScrim$result["getCurrentScrim"] | undefined>;
8+
9+
export const CurrentScrimContext = () => getContext<CurrentScrimContextValue>(CurrentScrimContextKey);
10+
export const SetCurrentScrimContext = (v: CurrentScrimContextValue) =>
11+
setContext<CurrentScrimContextValue>(CurrentScrimContextKey, v);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./CurrentScrims.Live.store";
2+
export * from "./CurrentScrims.context";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mutation LeaveScrim {
2+
leaveScrim
3+
}

0 commit comments

Comments
 (0)