Skip to content

Commit

Permalink
Merge pull request #490 from SprocketBot/feat/scrims
Browse files Browse the repository at this point in the history
Initial Implementations of Scrims
  • Loading branch information
ItsMeBrianD authored Jun 4, 2023
2 parents d2c1334 + e7cd100 commit 2fcfdab
Show file tree
Hide file tree
Showing 96 changed files with 1,288 additions and 362 deletions.
5 changes: 4 additions & 1 deletion clients/web/houdini.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ const config = {
}
},
"plugins": {
"houdini-svelte": {}
"houdini-svelte": {},
},
scalars: {
DateTime: { type: Date.name }
}
}

export default config
4 changes: 3 additions & 1 deletion clients/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@steeze-ui/svelte-icon": "^1.3.2",
"@sveltejs/adapter-auto": "^1.0.1",
"@sveltejs/kit": "^1.1.1",
"@types/lodash.startcase": "^4.4.7",
"@types/lodash.times": "^4.3.7",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
Expand Down Expand Up @@ -53,8 +54,9 @@
"@fontsource/montserrat": "^4.5.14",
"@sveltejs/adapter-node": "^1.0.0-next.106",
"echarts": "^5.4.1",
"graphql-ws": "^5.11.3",
"jwt-decode": "^3.1.2",
"lodash.startcase": "^4.4.0",
"subscriptions-transport-ws": "^0.11.0",
"typescript-cookie": "^1.0.4"
}
}
99 changes: 19 additions & 80 deletions clients/web/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import {env} from "$env/dynamic/public";
import {HoudiniClient, RefreshLoginStore} from "$houdini";
import {createClient} from "graphql-ws";
import {HoudiniClient} from "$houdini";
import {SubscriptionClient} from "subscriptions-transport-ws";
import {subscription} from "$houdini/plugins";
import {goto} from "$app/navigation";
import {browser} from "$app/environment";
import {getAuthCookies, updateAuthCookies} from "$lib/api/auth-cookies";
import {getExpiryFromJwt} from "./lib/utilities/getExpiryFromJwt";
import {clearAuthCookies} from "./lib/api/auth-cookies";
import {getAuthCookies, clearAuthCookies} from "$lib/api";
import {redirect} from "@sveltejs/kit";
import jwtDecode from "jwt-decode";
import {refreshAuthPlugin} from "./houdini/refresh-auth.plugin";

const getAuthToken = ({
session,
Expand Down Expand Up @@ -61,81 +58,23 @@ export default new HoudiniClient({
},
},
plugins: [
() => {
refreshAuthPlugin,
subscription(ctx => {
const c = new SubscriptionClient(`${env.PUBLIC_GQL_URL.replace("http", "ws")}/graphql`, {
reconnect: true,
lazy: true,
});
return {
beforeNetwork: async (ctx, b) => {
const {next} = b;
const {session} = ctx;

// TODO: Actually check the thing
if (!session?.access) {
next(ctx);
return;
}
const expAt = getExpiryFromJwt(session?.access);

// If there is at least one minute on the token; do nothing
if (expAt.getTime() > new Date().getTime() + 60 * 1000) {
next(ctx);
return;
}
if (ctx.artifact.name === "RefreshLogin") {
console.debug(
"Avoiding infinite loop. Will not try to refresh auth before a refresh auth call.",
);
next(ctx);
return;
}

// Otherwise we need to refresh
console.log("Attempting to refresh authentication");

if (session.refresh) {
const s = new RefreshLoginStore();
try {
const result = await s.mutate(null, {
metadata: {
accessTokenOverride: session.refresh,
},
fetch: fetch ?? ctx.fetch,
});
if (!result.data) {
throw new Error(
result.errors?.map(e => e.message).join("\n") ?? "Refresh Login Response Empty",
);
}

if (!ctx.session) ctx.session = {};
ctx.session.access = result.data.refreshLogin.access;
ctx.session.refresh = result.data.refreshLogin.refresh;

if (browser) {
updateAuthCookies(result.data.refreshLogin);
} else {
// How do I set cookies here
console.warn(
"Failed to persist auth cookie updates. Setting cookies in a plugin is not possible?",
);
}
console.debug("Auth has been refreshed");
} catch (e) {
console.warn("Failed to refresh auth token!", e);
}
} else {
console.debug("Skipping Auth Refresh");
}

next(ctx);
subscribe(payload, handlers) {
const {unsubscribe} = c
.request({
...payload,
context: {authorization: getAuthToken() ? `Bearer ${getAuthToken()}` : undefined},
})
.subscribe(handlers);
return unsubscribe;
},
};
},
subscription(ctx =>
createClient({
url: `${env.PUBLIC_GQL_URL}/graphql`,
connectionParams: {
authorization: ctx.session?.access ? `Bearer ${ctx.session.access}` : "",
},
}),
),
}),
],
});
7 changes: 6 additions & 1 deletion clients/web/src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type {Handle} from "@sveltejs/kit";
import type {Handle, HandleServerError} from "@sveltejs/kit";
import {sequence} from "@sveltejs/kit/hooks";
import {HoudiniSessionHook} from "./hooks/HoudiniSession.hook";

export const handle: Handle = sequence(HoudiniSessionHook);
export const handleError: HandleServerError = ({error, event}) => {
return {
message: "An Error has occurred!",
};
};
72 changes: 72 additions & 0 deletions clients/web/src/houdini/refresh-auth.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {browser} from "$app/environment";
import {RefreshLoginStore} from "$houdini";
import {updateAuthCookies} from "$lib/api";
import {getExpiryFromJwt} from "$lib/utilities/getExpiryFromJwt";
import type {ClientPlugin} from "$houdini";

export const refreshAuthPlugin: ClientPlugin = () => {
return {
beforeNetwork: async (ctx, b) => {
const {next} = b;
const {session} = ctx;

// TODO: Actually check the thing
if (!session?.access) {
next(ctx);
return;
}
const expAt = getExpiryFromJwt(session?.access);

// If there is at least one minute on the token; do nothing
if (expAt.getTime() > new Date().getTime() + 60 * 1000) {
next(ctx);
return;
}
if (ctx.artifact.name === "RefreshLogin") {
console.debug("Avoiding infinite loop. Will not try to refresh auth before a refresh auth call.");
next(ctx);
return;
}

// Otherwise we need to refresh
console.log("Attempting to refresh authentication");

if (session.refresh) {
const s = new RefreshLoginStore();
try {
const result = await s.mutate(null, {
metadata: {
accessTokenOverride: session.refresh,
},
fetch: fetch ?? ctx.fetch,
});
if (!result.data) {
throw new Error(
result.errors?.map(e => e.message).join("\n") ?? "Refresh Login Response Empty",
);
}

if (!ctx.session) ctx.session = {};
ctx.session.access = result.data.refreshLogin.access;
ctx.session.refresh = result.data.refreshLogin.refresh;

if (browser) {
updateAuthCookies(result.data.refreshLogin);
} else {
// How do I set cookies here
console.warn(
"Failed to persist auth cookie updates. Setting cookies in a plugin is not possible?",
);
}
console.debug("Auth has been refreshed");
} catch (e) {
console.warn("Failed to refresh auth token!", e);
}
} else {
console.debug("Skipping Auth Refresh");
}

next(ctx);
},
};
};
1 change: 1 addition & 0 deletions clients/web/src/lib/api/authentication/UserInfo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./UserInfo.context";
2 changes: 2 additions & 0 deletions clients/web/src/lib/api/authentication/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./UserInfo";
export * from "./utils";
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {onMount} from "svelte";
import {RefreshLoginStore} from "$houdini";
import {getAuthCookies, updateAuthCookies} from "./auth-cookies";

export const authIntervalRefresh = () =>
onMount(() => {
const refreshStore = new RefreshLoginStore();

const refreshAuth = async () => {
const {refresh} = getAuthCookies();
const r = await refreshStore.mutate(null, {metadata: {accessTokenOverride: refresh}});
if (!r.data) return;
console.debug("Auth Refreshed");
updateAuthCookies(r.data?.refreshLogin);
};

// Refresh auth token every 2 minutes
const i = setInterval(refreshAuth, 1000 * 60 * 2);
refreshAuth();
return () => clearInterval(i);
});
2 changes: 2 additions & 0 deletions clients/web/src/lib/api/authentication/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./auth-cookies";
export * from "./auth-refresh-interval";
13 changes: 13 additions & 0 deletions clients/web/src/lib/api/games/MemberGames.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
query MemberGames {
getMemberGames @list(name: "MemberGames") {
id
title
modes {
id
code
description
teamSize
teamCount
}
}
}
2 changes: 2 additions & 0 deletions clients/web/src/lib/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./authentication";
export * from "./scrims";
22 changes: 22 additions & 0 deletions clients/web/src/lib/api/scrims/CreateScrim.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
mutation CreateScrim($options: CreateScrimInput!) {
createScrim(data: $options) {
id
createdAt
gameMode {
description
}
maxPlayers
playerCount
settings {
competitive
mode
observable
teamCount
teamSize
}
skillGroup {
description
}
status
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
fragment CurrentScrimFragment on ScrimObject {
id
author {
displayName
}
playerCount
maxPlayers
status
skillGroup {
description
}
gameMode {
description
}
settings {
competitive
mode
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query CurrentScrim {
getCurrentScrim {
...CurrentScrimFragment @mask_disable
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
subscription CurrentScrimSub {
followCurrentScrim {
scrim {
...CurrentScrimFragment @mask_disable
}
event
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {readable, type Readable} from "svelte/store";
import type {CurrentScrim$result, CurrentScrimStore, CurrentScrimSub$result, CurrentScrimSubStore} from "$houdini";

export const CurrentScrimLiveStore = (
queryStore: CurrentScrimStore,
subStore: CurrentScrimSubStore,
eventMap?: Partial<Record<CurrentScrimSub$result["followCurrentScrim"]["event"], () => void>>,
): Readable<CurrentScrim$result["getCurrentScrim"]> => {
return readable<CurrentScrim$result["getCurrentScrim"]>(null, set => {
const queryUnsub = queryStore.subscribe($q => {
if ($q.data?.getCurrentScrim) {
set($q.data?.getCurrentScrim);
}
});
const subUnsub = subStore.subscribe($s => {
const eventName = $s.data?.followCurrentScrim.event;
if (eventName) {
const handler = eventMap?.[eventName];
if (handler) handler();
}
if ($s.data) {
if (["EMPTY", "CANCELLED", "COMPLETE"].includes($s.data.followCurrentScrim.scrim.status)) {
// Scrim end states
console.log("Scrim has ended");
set(null);
} else {
set($s.data.followCurrentScrim.scrim);
}
}
});

return () => {
queryUnsub();
subUnsub();
};
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {getContext, setContext} from "svelte";
import type {CurrentScrim$result} from "$houdini";
import type {Readable} from "svelte/store";

const CurrentScrimContextKey = "CURRENT_SCRIM_CONTEXT_KEY";

export type CurrentScrimContextValue = Readable<CurrentScrim$result["getCurrentScrim"] | undefined>;

export const CurrentScrimContext = () => getContext<CurrentScrimContextValue>(CurrentScrimContextKey);
export const SetCurrentScrimContext = (v: CurrentScrimContextValue) =>
setContext<CurrentScrimContextValue>(CurrentScrimContextKey, v);
2 changes: 2 additions & 0 deletions clients/web/src/lib/api/scrims/CurrentScrim/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./CurrentScrims.Live.store";
export * from "./CurrentScrims.context";
3 changes: 3 additions & 0 deletions clients/web/src/lib/api/scrims/LeaveScrim.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mutation LeaveScrim {
leaveScrim
}
Loading

0 comments on commit 2fcfdab

Please sign in to comment.