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

Persisterer language og context valg til cookies #451

Merged
merged 9 commits into from
Sep 26, 2024
6 changes: 2 additions & 4 deletions packages/client/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ import { initHistoryEvents, initScrollToEvents } from "./events";
import { addFaroMetaData } from "./faro";
import { refreshAuthData } from "./helpers/auth";
import { buildHtmlElement } from "./helpers/html-element-builder";
import { param, initParams } from "./params";
import "./main.css";
import { param, updateDecoratorParams } from "./params";

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

updateDecoratorParams({});

// @TODO: Refactor loaders
window.addEventListener("load", () => {
addFaroMetaData();
});
Expand All @@ -35,6 +32,7 @@ const injectHeadAssets = () => {
};

const init = () => {
initParams();
injectHeadAssets();
initHistoryEvents();
initScrollToEvents();
Expand Down
67 changes: 50 additions & 17 deletions packages/client/src/params.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,70 @@
import { ClientParams, Environment } from "decorator-shared/params";
import Cookies from "js-cookie";
import {
ClientParams,
Context,
Environment,
Language,
} from "decorator-shared/params";
import { createEvent } from "./events";

export const hasParam = (paramKey: keyof ClientParams): boolean => {
return window.__DECORATOR_DATA__.params[paramKey] !== undefined;
};
type ParamKey = keyof ClientParams;
type EnvKey = keyof Environment;

const CONTEXT_COOKIE = "decorator-context";
const LANGUAGE_COOKIE = "decorator-language";

export const param = <TKey extends keyof ClientParams>(paramKey: TKey) => {
return window.__DECORATOR_DATA__.params[paramKey] as ClientParams[TKey];
export const param = <TKey extends ParamKey>(paramKey: TKey) => {
return window.__DECORATOR_DATA__.params[paramKey];
};

export const env = <TKey extends keyof Environment>(envKey: TKey): string => {
return window.__DECORATOR_DATA__.env[envKey] as Environment[TKey];
export const env = <TKey extends EnvKey>(envKey: TKey) => {
return window.__DECORATOR_DATA__.env[envKey];
};

export const updateDecoratorParams = (params: Partial<ClientParams>) => {
const updatedParams = params;
const updatedParams = { ...params };

Object.entries(params).map(([key, value]) => {
if (param(key as keyof ClientParams) === value) {
delete updatedParams[key as keyof ClientParams];
Object.entries(params).forEach(([key, value]) => {
if (param(key as ParamKey) === value) {
delete updatedParams[key as ParamKey];
}
});

if (Object.keys(updatedParams).length > 0) {
window.__DECORATOR_DATA__.params = {
...window.__DECORATOR_DATA__.params,
...updatedParams,
};
window.__DECORATOR_DATA__.params = {
...window.__DECORATOR_DATA__.params,
...updatedParams,
};

Cookies.set(CONTEXT_COOKIE, param("context"));
Cookies.set(LANGUAGE_COOKIE, param("language"));

if (Object.keys(updatedParams).length > 0) {
window.dispatchEvent(
createEvent("paramsupdated", {
detail: { params: updatedParams },
}),
);
}
};

export const initParams = () => {
const rawParams = window.__DECORATOR_DATA__.rawParams;

const initialParams: Partial<ClientParams> = {};

const language =
rawParams?.language ||
(Cookies.get(LANGUAGE_COOKIE) as Language | undefined);
if (language) {
initialParams.language = language;
}

const context =
rawParams?.context ||
(Cookies.get(CONTEXT_COOKIE) as Context | undefined);
if (context) {
initialParams.context = context;
}

updateDecoratorParams(initialParams);
};
9 changes: 6 additions & 3 deletions packages/server/src/handlers/ssr-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { validParams } from "../validateParams";
import { parseAndValidateParams } from "../validateParams";
import { getFeatures } from "../unleash";
import { HeaderTemplate } from "../views/header/header";
import { FooterTemplate } from "../views/footer/footer";
Expand All @@ -16,7 +16,8 @@ type SsrPayload = {
};

export const ssrApiHandler: Handler = async ({ req, json }) => {
const params = validParams(req.query());
const query = req.query();
const params = parseAndValidateParams(query);
const features = getFeatures();

return json({
Expand All @@ -33,7 +34,9 @@ export const ssrApiHandler: Handler = async ({ req, json }) => {
withContainers: true,
})
).render(params),
scripts: ScriptsTemplate({ features, params }).render(params),
scripts: ScriptsTemplate({ features, params, rawParams: query }).render(
params,
),
headAssets: HeadAssetsTemplate().render(),
versionId: env.VERSION_ID,
} satisfies SsrPayload);
Expand Down
45 changes: 23 additions & 22 deletions packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { fetchOpsMessages } from "./ops-msgs";
import { getTaskAnalyticsSurveys } from "./task-analytics-config";
import { getFeatures } from "./unleash";
import { isLocalhost } from "./urls";
import { validParams } from "./validateParams";
import { IndexTemplate } from "./views";
import { parseAndValidateParams } from "./validateParams";
import { IndexHtml } from "./views";
import { HeaderTemplate } from "./views/header/header";
import { FooterTemplate } from "./views/footer/footer";
import { buildDecoratorData } from "./views/scripts";
Expand Down Expand Up @@ -79,14 +79,14 @@ app.post("/api/notifications/:id/archive", async ({ req, json }) => {
app.get("/api/search", async ({ req, html }) =>
html(
await searchHandler({
...validParams(req.query()),
...parseAndValidateParams(req.query()),
query: req.query("q") ?? "",
}),
),
);
app.get("/api/csp", ({ json }) => json(cspDirectives));
app.get("/main-menu", async ({ req, html }) => {
const data = validParams(req.query());
const data = parseAndValidateParams(req.query());

return html(
(
Expand All @@ -99,14 +99,14 @@ app.get("/main-menu", async ({ req, html }) => {
app.get("/auth", async ({ req, json }) =>
json(
await authHandler({
params: validParams(req.query()),
params: parseAndValidateParams(req.query()),
cookie: req.header("Cookie") ?? "",
}),
),
);
app.get("/ops-messages", async ({ json }) => json(await fetchOpsMessages()));
app.get("/header", async ({ req, html }) => {
const params = validParams(req.query());
const params = parseAndValidateParams(req.query());

return html(
(await HeaderTemplate({ params, withContainers: false })).render(
Expand All @@ -115,7 +115,7 @@ app.get("/header", async ({ req, html }) => {
);
});
app.get("/footer", async ({ req, html }) => {
const params = validParams(req.query());
const params = parseAndValidateParams(req.query());

return html(
(
Expand All @@ -130,7 +130,8 @@ app.get("/footer", async ({ req, html }) => {
app.get("/ssr", ssrApiHandler);
// TODO: The CSR implementation can probably be tweaked to use the same data as /ssr
app.on("GET", ["/env", "/csr"], async ({ req, json }) => {
const params = validParams(req.query());
const query = req.query();
const params = parseAndValidateParams(query);
const features = getFeatures();

return json({
Expand All @@ -147,9 +148,13 @@ app.on("GET", ["/env", "/csr"], async ({ req, json }) => {
withContainers: true,
})
).render(params),
data: buildDecoratorData({ params, features, headAssets }),
data: buildDecoratorData({
params,
rawParams: query,
features,
headAssets,
}),
scripts: csrAssets.mainScripts,
//TODO: Add css?
} satisfies CsrPayload);
});
app.get("/:clientWithId{client(.*).js}", async ({ redirect }) =>
Expand All @@ -158,18 +163,14 @@ app.get("/:clientWithId{client(.*).js}", async ({ redirect }) =>
app.get("/css/:clientWithId{client(.*).css}", async ({ redirect }) =>
redirect(csrAssets.cssUrl),
);
app.get("/", async ({ req, html }) => {
const params = validParams(req.query());

return html(
(
await IndexTemplate({
params,
url: req.url,
})
).render(params),
);
});
app.get("/", async ({ req, html }) =>
html(
IndexHtml({
rawParams: req.query(),
url: req.url,
}),
),
);

app.route("/decorator-next", app);
app.route("/dekoratoren", app);
Expand Down
18 changes: 9 additions & 9 deletions packages/server/src/validateParams.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { describe, expect, it } from "bun:test";
import {
parseBooleanParam,
validateParams,
validParams,
parseAndValidateParams,
} from "./validateParams";
import { formatParams } from "decorator-shared/json";
import { Params } from "decorator-shared/params";

describe("Validating urls", () => {
it("Should validate nav.no urls", () => {
const params = validParams({
const params = parseAndValidateParams({
redirectToUrl: "https://myapp.nav.no/foo",
redirectToUrlLogout: "https://my.app.nav.no/bar",
logoutUrl: "https://www.nav.no/qwer",
Expand Down Expand Up @@ -47,7 +47,7 @@ describe("Validating urls", () => {
});

it("Should validate paths", () => {
const params = validParams({
const params = parseAndValidateParams({
redirectToUrl: "/foo",
redirectToUrlLogout: "/bar",
logoutUrl: "/qwer",
Expand Down Expand Up @@ -85,31 +85,31 @@ describe("Validating urls", () => {
});

it("Should not validate redirectToUrl with non-nav origin", () => {
const params = validParams({
const params = parseAndValidateParams({
redirectToUrl: "https://www.vg.no",
} satisfies Partial<Record<keyof Params, unknown>>);

expect(params.redirectToUrl).toBeUndefined();
});

it("Should not validate redirectToUrlLogout with non-nav origins", () => {
const params = validParams({
const params = parseAndValidateParams({
redirectToUrlLogout: "https://navv.no",
} satisfies Partial<Record<keyof Params, unknown>>);

expect(params.redirectToUrlLogout).toBeUndefined();
});

it("Should not validate logoutUrl with non-nav origins", () => {
const params = validParams({
const params = parseAndValidateParams({
logoutUrl: "https://www.notevilatall.no",
} satisfies Partial<Record<keyof Params, unknown>>);

expect(params.logoutUrl).toBeUndefined();
});

it("Should not validate breadcrumbs with non-nav origins", () => {
const params = validParams({
const params = parseAndValidateParams({
breadcrumbs: JSON.stringify([
{
title: "test",
Expand All @@ -123,7 +123,7 @@ describe("Validating urls", () => {

it("Should not validate availableLanguages with non-nav origins", () => {
const validateAvailableLanguage = () =>
validParams({
parseAndValidateParams({
availableLanguages: JSON.stringify([
{
handleInApp: false,
Expand All @@ -136,7 +136,7 @@ describe("Validating urls", () => {
});

it("Should not validate logoutUrl without protocol prefix", () => {
const params = validParams({
const params = parseAndValidateParams({
logoutUrl: "www.nav.no",
} satisfies Partial<Record<keyof Params, unknown>>);

Expand Down
Loading
Loading