Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fallback til client-side rendering #373

Closed
wants to merge 15 commits into from
Closed
2 changes: 1 addition & 1 deletion .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@ jobs:
env:
CLUSTER: ${{ inputs.CLUSTER }}
RESOURCE: .nais/config.yml
VAR: image=${{ steps.docker-push.outputs.image }}
VAR: image=${{ steps.docker-push.outputs.image }},BUILD_ID=${{ github.sha }}
VARS: .nais/vars/${{ inputs.DEPLOY_INSTANCE }}.yml
8 changes: 4 additions & 4 deletions .github/workflows/deploy.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
env:
CLUSTER: dev-gcp
RESOURCE: .nais/config.yml
VAR: image=${{ needs.build-and-test.outputs.image }}
VAR: image=${{ needs.build-and-test.outputs.image }},BUILD_ID=${{ github.sha }}
VARS: .nais/vars/dev-stable.yml

deploy-beta-navno:
Expand All @@ -75,7 +75,7 @@ jobs:
env:
CLUSTER: dev-gcp
RESOURCE: .nais/config.yml
VAR: image=${{ needs.build-and-test.outputs.image }}
VAR: image=${{ needs.build-and-test.outputs.image }},BUILD_ID=${{ github.sha }}
VARS: .nais/vars/dev-beta-navno.yml

deploy-prod-next:
Expand All @@ -90,7 +90,7 @@ jobs:
env:
CLUSTER: prod-gcp
RESOURCE: .nais/config.yml
VAR: image=${{ needs.build-and-test.outputs.image }}
VAR: image=${{ needs.build-and-test.outputs.image }},BUILD_ID=${{ github.sha }}
VARS: .nais/vars/prod-next.yml

deploy-prod:
Expand All @@ -105,5 +105,5 @@ jobs:
env:
CLUSTER: prod-gcp
RESOURCE: .nais/config.yml
VAR: image=${{ needs.build-and-test.outputs.image }}
VAR: image=${{ needs.build-and-test.outputs.image }},BUILD_ID=${{ github.sha }}
VARS: .nais/vars/prod.yml
2 changes: 2 additions & 0 deletions .nais/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ spec:
- application: "*"
namespace: "*"
env:
- name: BUILD_ID
value: {{BUILD_ID}}
{{#each env as |var|}}
- name: {{var.name}}
value: {{var.value}}
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
"build-storybook": "storybook build",
"test": "bun run --cwd packages/shared test && bun run --cwd packages/client test && bun run --cwd packages/server test",
"dev": "concurrently \"bun run --cwd packages/client dev\" \"bun run --cwd packages/server dev\"",
"build": "bun run --cwd packages/client build && bun run --cwd packages/server build && bun run copy-assets",
"copy-assets": "mkdir -p packages/server/public && cp -r packages/client/dist/assets packages/server/public/",
"build": "bun run --cwd packages/client build && bun run --cwd packages/server build && bun run copy-assets && bun run copy-to-old-prod-build-file",
"copy-assets": "mkdir -p packages/server/public && cp -r packages/client/dist/assets packages/server/public/ && bun run copy-assets-temp",
"copy-assets-temp": "bun run copy-97 && bun run copy-96 && bun run copy-95 && bun run copy-94",
"copy-97": "cp packages/client/dist/assets/csr-*.js packages/client/dist/assets/main-GjdQNcNv.js",
"copy-96": "cp packages/client/dist/assets/csr-*.js packages/client/dist/assets/main-BTg_tyx3.js",
"copy-95": "cp packages/client/dist/assets/csr-*.js packages/client/dist/assets/main-Dn44Sa6d.js",
"copy-94": "cp packages/client/dist/assets/csr-*.js packages/client/dist/assets/main-B0G_DFXG.js",
"prepare": "husky",
"benchmarking": "./benchmarking/run",
"clean": "rm -rf node_modules packages/**/node_modules bun.lockb packages/**/bun.lockb",
Expand Down
36 changes: 0 additions & 36 deletions packages/client/src/auth.ts

This file was deleted.

39 changes: 39 additions & 0 deletions packages/client/src/client-state-update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ClientStateResponse } from "decorator-shared/auth";
import { CustomEvents, createEvent } from "./events";
import { endpointUrlWithParams } from "./helpers/urls";
import { env } from "./params";

const fetchClientState = async (): Promise<ClientStateResponse> => {
const url = endpointUrlWithParams("/client-state");

return fetch(url, {
credentials: "include",
})
.then((res) => res.json() as Promise<ClientStateResponse>)
.catch((error) => {
console.error(`Failed to fetch init data - ${error}`);
return { auth: { authenticated: false }, buildId: env("BUILD_ID") };
});
};

const updateClientState = () =>
fetchClientState().then((authResponse) => {
dispatchEvent(
createEvent("client-state-updated", { detail: authResponse }),
);
return authResponse;
});

const updateAuthOnContextSwitch = (
e: CustomEvent<CustomEvents["paramsupdated"]>,
) => {
if (e.detail.params.context) {
updateClientState();
}
};

export const initClientState = () =>
updateClientState().then((response) => {
window.addEventListener("paramsupdated", updateAuthOnContextSwitch);
return response;
});
19 changes: 19 additions & 0 deletions packages/client/src/csr-bundle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { fetchAndRenderClientSide } from "./csr";

const findOrError = (id: string) => {
const el = document.getElementById(`decorator-${id}`);

if (!el) {
throw new Error(`No elem:${id}. See github.com/navikt/decorator-next`);
}

return el;
};

const hydrate = () => fetchAndRenderClientSide(findOrError("env").dataset.src!);

if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", hydrate);
} else {
hydrate();
}
52 changes: 28 additions & 24 deletions packages/client/src/csr.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import { CsrPayload } from "decorator-shared/types";
import { buildHtmlElement } from "./helpers/html-element-builder";
import type { CsrPayload } from "decorator-shared/types";

const findOrError = (id: string) => {
const el = document.getElementById(`decorator-${id}`);
export const fetchAndRenderClientSide = async (url: string) => {
const csrAssets = await fetch(url)
.then((res) => res.json() as Promise<CsrPayload>)
.catch((e) => {
console.error("Error fetching CSR assets: ", e);
return null;
});

if (!el) {
throw new Error(`No elem:${id}. See github.com/navikt/decorator-next`);
if (!csrAssets) {
console.error("Failed to fetch CSR assets!");
return;
}

return el;
};
const headerEl = document.getElementById("decorator-header");
if (headerEl) {
headerEl.outerHTML = csrAssets.header;
}

const hydrate = () =>
fetch(findOrError("env").dataset.src!)
.then((res) => res.json())
.then((elements: CsrPayload) => {
(["header", "footer"] as const).forEach(
(key) => (findOrError(key).outerHTML = elements[key]),
);
window.__DECORATOR_DATA__ = elements.data;
const footerEl = document.getElementById("decorator-footer");
if (footerEl) {
footerEl.outerHTML = csrAssets.footer;
}

elements.scripts
.map(buildHtmlElement)
.forEach((script) => document.body.appendChild(script));
});
window.__DECORATOR_DATA__ = csrAssets.data;

csrAssets.css.map(buildHtmlElement).forEach((cssElement) => {
document.head.appendChild(cssElement);
});

if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", hydrate);
} else {
hydrate();
}
csrAssets.scripts.map(buildHtmlElement).forEach((scriptElement) => {
document.body.appendChild(scriptElement);
});
};
4 changes: 2 additions & 2 deletions packages/client/src/events.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Context, Params } from "decorator-shared/params";
import { AuthDataResponse } from "decorator-shared/auth";
import { ClientStateResponse } from "decorator-shared/auth";

export type CustomEvents = {
activecontext: { context: Context };
paramsupdated: {
params: Partial<Params>;
};
authupdated: AuthDataResponse;
"client-state-updated": ClientStateResponse;
menuopened: void;
menuclosed: void;
clearsearch: void;
Expand Down
124 changes: 25 additions & 99 deletions packages/client/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,129 +1,55 @@
/// <reference types="./client.d.ts" />
import { formatParams } from "decorator-shared/json";
import { type Context, type ParamKey } from "decorator-shared/params";
import Cookies from "js-cookie";
import "vite/modulepreload-polyfill";
import { initAnalytics } from "./analytics/analytics";
import { initAuth } from "./auth";
import { initClientState } from "./client-state-update";
import { initLogoutWarning } from "./logout-warning";
import { createEvent, initHistoryEvents } from "./events";
import { addFaroMetaData } from "./faro";
import { initHistoryEvents } from "./events";
import "./main.css";
import { env, param, updateDecoratorParams } from "./params";
import { useLoadIfActiveSession } from "./screensharing";
import { getHeadAssetsProps } from "decorator-shared/head";
import { buildHtmlElement } from "./helpers/html-element-builder";
import { cdnUrl } from "./helpers/urls";
import { fetchAndRenderClientSide } from "./csr";
import { initWindowEventHandlers } from "./window-event-handlers";

import.meta.glob("./styles/*.css", { eager: true });
import.meta.glob(["./views/**/*.ts", "!./views/**/*.test.ts"], { eager: true });

updateDecoratorParams({});

window.addEventListener("paramsupdated", (e) => {
if (e.detail.params.language) {
Promise.all(
["header", "footer"].map((key) =>
fetch(
`${env("APP_URL")}/${key}?${formatParams(window.__DECORATOR_DATA__.params)}`,
).then((res) => res.text()),
),
).then(([header, footer]) => {
const headerEl = document.getElementById("decorator-header");
const footerEl = document.getElementById("decorator-footer");
if (headerEl && footerEl) {
headerEl.outerHTML = header;
footerEl.outerHTML = footer;
init();
}
});
}
});

const msgSafetyCheck = (message: MessageEvent) => {
const { origin, source } = message;
// Only allow messages from own window
return window.location.href.startsWith(origin) && source === window;
};

window.addEventListener("message", (e) => {
if (!msgSafetyCheck(e)) {
return;
}
if (e.data.source === "decoratorClient" && e.data.event === "ready") {
window.postMessage({ source: "decorator", event: "ready" });
}
if (e.data.source === "decoratorClient" && e.data.event == "params") {
const payload = e.data.payload;

(
[
"breadcrumbs",
"availableLanguages",
"utilsBackground",
"language",
"chatbotVisible",
] satisfies ParamKey[]
).forEach((key) => {
if (payload[key] !== undefined) {
updateDecoratorParams({
[key]: payload[key],
});
}
});

if (e.data.payload.context) {
const context = e.data.payload.context;
if (
["privatperson", "arbeidsgiver", "samarbeidspartner"].includes(
context,
)
) {
window.dispatchEvent(
createEvent("activecontext", {
bubbles: true,
detail: { context },
}),
);
} else {
console.warn("Unrecognized context", context);
}
}
}
});

window.addEventListener("activecontext", (event) => {
updateDecoratorParams({
context: (event as CustomEvent<{ context: Context }>).detail.context,
});
});

// @TODO: Refactor loaders
window.addEventListener("load", () => {
useLoadIfActiveSession({
userState: Cookies.get("psCurrentState"),
});
addFaroMetaData();
});

const injectHeadAssets = () => {
getHeadAssetsProps(cdnUrl).forEach((props) => {
const element = buildHtmlElement(props);
document.head.appendChild(element);
});
};

const init = () => {
injectHeadAssets();
initHistoryEvents();

const maskDocumentFromHotjar = () => {
if (param("maskHotjar")) {
document.documentElement.setAttribute("data-hj-suppress", "");
}
};

initAuth().then((auth) => {
initAnalytics(auth);
});
const init = async () => {
maskDocumentFromHotjar();
injectHeadAssets();

const clientState = await initClientState();

if (clientState.buildId !== env("BUILD_ID")) {
console.log(
`Local build id "${env("BUILD_ID")}" differs from expected build id "${clientState.buildId}" - fetching current version and re-rendering`,
);
await fetchAndRenderClientSide(
`${env("APP_URL")}/env?${formatParams(window.__DECORATOR_DATA__.params)}`,
);
return;
}

initWindowEventHandlers();
initAnalytics(clientState.auth);
initHistoryEvents();

if (param("logoutWarning")) {
initLogoutWarning();
Expand Down
Loading
Loading