@alpaca-headless/alpaca-headless 1.0.471
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/.eslintrc.json +3 -0
- package/dist/client-components/ClientLink.d.ts +7 -0
- package/dist/client-components/ClientLink.js +20 -0
- package/dist/client-components/index.d.ts +1 -0
- package/dist/client-components/index.js +1 -0
- package/dist/components/Error.d.ts +1 -0
- package/dist/components/Error.js +7 -0
- package/dist/components/LazyPlaceholder.d.ts +6 -0
- package/dist/components/LazyPlaceholder.js +15 -0
- package/dist/components/Placeholder.d.ts +30 -0
- package/dist/components/Placeholder.js +123 -0
- package/dist/components/Slot.d.ts +7 -0
- package/dist/components/Slot.js +6 -0
- package/dist/components/Translate.d.ts +7 -0
- package/dist/components/Translate.js +17 -0
- package/dist/components/field-renderers/Link.d.ts +8 -0
- package/dist/components/field-renderers/Link.js +29 -0
- package/dist/components/field-renderers/Picture.d.ts +22 -0
- package/dist/components/field-renderers/Picture.js +140 -0
- package/dist/components/field-renderers/RichText.d.ts +7 -0
- package/dist/components/field-renderers/RichText.js +30 -0
- package/dist/components/field-renderers/Text.d.ts +7 -0
- package/dist/components/field-renderers/Text.js +24 -0
- package/dist/configuration.d.ts +17 -0
- package/dist/configuration.js +4 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +15 -0
- package/dist/internals/index.d.ts +5 -0
- package/dist/internals/index.js +2 -0
- package/dist/middleware/handleRequest.d.ts +7 -0
- package/dist/middleware/handleRequest.js +38 -0
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.js +1 -0
- package/dist/picture-shared.d.ts +16 -0
- package/dist/picture-shared.js +26 -0
- package/dist/render-context.d.ts +47 -0
- package/dist/render-context.js +33 -0
- package/dist/renderings/renderings.d.ts +7 -0
- package/dist/renderings/renderings.js +22 -0
- package/dist/renderings/tailwind.d.ts +1 -0
- package/dist/renderings/tailwind.js +43 -0
- package/dist/route-data/dictionary.d.ts +2 -0
- package/dist/route-data/dictionary.js +31 -0
- package/dist/route-data/resolve-route.d.ts +44 -0
- package/dist/route-data/resolve-route.js +123 -0
- package/dist/route-data/route-data.d.ts +41 -0
- package/dist/route-data/route-data.js +1 -0
- package/dist/services/api.d.ts +18 -0
- package/dist/services/api.js +103 -0
- package/dist/services/graphQL.d.ts +29 -0
- package/dist/services/graphQL.js +54 -0
- package/dist/types/fetch.d.ts +7 -0
- package/dist/types/fetch.js +1 -0
- package/dist/types/fieldTypes.d.ts +106 -0
- package/dist/types/fieldTypes.js +1 -0
- package/dist/types/items.d.ts +56 -0
- package/dist/types/items.js +1 -0
- package/dist/types/layoutDataTypes.d.ts +142 -0
- package/dist/types/layoutDataTypes.js +1 -0
- package/dist/utils/media-protection.d.ts +3 -0
- package/dist/utils/media-protection.js +49 -0
- package/next.config.mjs +4 -0
- package/package.json +61 -0
- package/postcss.config.js +6 -0
- package/src/client-components/ClientLink.tsx +33 -0
- package/src/client-components/index.ts +1 -0
- package/src/components/Error.tsx +56 -0
- package/src/components/LazyPlaceholder.tsx +35 -0
- package/src/components/Placeholder.tsx +312 -0
- package/src/components/Slot.tsx +22 -0
- package/src/components/Translate.tsx +32 -0
- package/src/components/field-renderers/Link.tsx +38 -0
- package/src/components/field-renderers/Picture.tsx +251 -0
- package/src/components/field-renderers/RichText.tsx +48 -0
- package/src/components/field-renderers/Text.tsx +32 -0
- package/src/configuration.ts +30 -0
- package/src/index.ts +84 -0
- package/src/internals/index.ts +7 -0
- package/src/middleware/handleRequest.ts +54 -0
- package/src/middleware/index.ts +1 -0
- package/src/picture-shared.ts +53 -0
- package/src/render-context.ts +123 -0
- package/src/renderings/renderings.tsx +44 -0
- package/src/renderings/tailwind.ts +53 -0
- package/src/route-data/dictionary.ts +56 -0
- package/src/route-data/resolve-route.ts +332 -0
- package/src/route-data/route-data.ts +51 -0
- package/src/services/api.ts +142 -0
- package/src/services/graphQL.ts +106 -0
- package/src/types/fetch.ts +8 -0
- package/src/types/fieldTypes.ts +127 -0
- package/src/types/items.ts +66 -0
- package/src/types/layoutDataTypes.ts +145 -0
- package/src/utils/media-protection.ts +61 -0
- package/tailwind.config.js +17 -0
- package/tailwind.config.ts +20 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { resolveRoute } from "../route-data/resolve-route";
|
|
2
|
+
export async function handleRequest({ request, mapHost, renderOptions, }) {
|
|
3
|
+
if (renderOptions)
|
|
4
|
+
throw new Error("Just a dummy to keep nextjs compiler happy. Remove with react 19 ");
|
|
5
|
+
const urlSearchParams = new URLSearchParams(request.nextUrl.search);
|
|
6
|
+
const nextSearchParams = {};
|
|
7
|
+
for (const [key, value] of urlSearchParams.entries()) {
|
|
8
|
+
nextSearchParams[key] = value;
|
|
9
|
+
}
|
|
10
|
+
const routeData = await resolveRoute({
|
|
11
|
+
path: request.nextUrl.pathname,
|
|
12
|
+
searchParams: nextSearchParams,
|
|
13
|
+
host: mapHost(request.headers.get("host") || ""),
|
|
14
|
+
renderOptions,
|
|
15
|
+
renderings: {},
|
|
16
|
+
});
|
|
17
|
+
if (routeData.result.type === "redirect") {
|
|
18
|
+
return new Response(null, {
|
|
19
|
+
status: routeData.result.redirectType === "permanent" ? 301 : 302,
|
|
20
|
+
headers: {
|
|
21
|
+
Location: routeData.result.location,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (routeData.result.type === "unauthorized") {
|
|
26
|
+
if (routeData.result.auth)
|
|
27
|
+
return new Response(null, {
|
|
28
|
+
status: 401,
|
|
29
|
+
headers: {
|
|
30
|
+
"WWW-Authenticate": routeData.result.auth.replace(", Bearer", ""),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
else
|
|
34
|
+
return new Response(null, {
|
|
35
|
+
status: 401,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./handleRequest";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./handleRequest";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PictureVariant } from "./types/fieldTypes";
|
|
2
|
+
export type PictureParams = {
|
|
3
|
+
variant?: string;
|
|
4
|
+
aspectRatio?: number;
|
|
5
|
+
scales?: number[];
|
|
6
|
+
maxWidth?: number;
|
|
7
|
+
custom?: {
|
|
8
|
+
[paramName: string]: string | number | boolean | undefined;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export type MediaPictureParams = {
|
|
12
|
+
default: PictureParams;
|
|
13
|
+
[mediaQuery: string]: PictureParams;
|
|
14
|
+
};
|
|
15
|
+
export declare function getRenderedPictureVariant(params: PictureParams, variants: PictureVariant[]): PictureVariant;
|
|
16
|
+
export declare function getBestMatchingVariant(variants: PictureVariant[], aspectRatio: number | undefined): PictureVariant;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function getRenderedPictureVariant(params, variants) {
|
|
2
|
+
var _a, _b;
|
|
3
|
+
const variantName = (_a = params.variant) !== null && _a !== void 0 ? _a : "auto";
|
|
4
|
+
const variant = variantName.toLocaleLowerCase() === "auto" && params.aspectRatio
|
|
5
|
+
? getBestMatchingVariant(variants, params.aspectRatio)
|
|
6
|
+
: (_b = variants.find((v) => v.name.toLowerCase() == variantName.toLowerCase())) !== null && _b !== void 0 ? _b : variants[0];
|
|
7
|
+
return variant;
|
|
8
|
+
}
|
|
9
|
+
export function getBestMatchingVariant(variants, aspectRatio) {
|
|
10
|
+
var _a;
|
|
11
|
+
if (!aspectRatio)
|
|
12
|
+
return ((_a = variants.find((v) => v.name.toLowerCase() == "default")) !== null && _a !== void 0 ? _a : variants[0]);
|
|
13
|
+
let minDistance = 1000000;
|
|
14
|
+
let bestMatch = undefined;
|
|
15
|
+
for (let i = 0; i < variants.length; i++) {
|
|
16
|
+
const variant = variants[i];
|
|
17
|
+
if (!variant.src)
|
|
18
|
+
continue;
|
|
19
|
+
const distance = Math.abs(aspectRatio - variant.aspectRatio);
|
|
20
|
+
if (distance < minDistance) {
|
|
21
|
+
minDistance = distance;
|
|
22
|
+
bestMatch = variant;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return bestMatch !== null && bestMatch !== void 0 ? bestMatch : variants[0];
|
|
26
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { Rendering } from "./renderings/renderings";
|
|
3
|
+
import { ApiConfig } from "./services/api";
|
|
4
|
+
import { ComponentData, Dictionary, Page, RenderingReference, RenderingsMap } from "./types/layoutDataTypes";
|
|
5
|
+
export type RenderOptions = {
|
|
6
|
+
lazyPlaceholders?: LazyPlaceholder[];
|
|
7
|
+
};
|
|
8
|
+
export type LazyPlaceholder = {
|
|
9
|
+
name: string;
|
|
10
|
+
numEagerComponents?: number;
|
|
11
|
+
};
|
|
12
|
+
export type ClientRenderContext = {
|
|
13
|
+
page: Page;
|
|
14
|
+
dictionary: Dictionary;
|
|
15
|
+
mode: string;
|
|
16
|
+
isEditing: boolean;
|
|
17
|
+
isPreview: boolean;
|
|
18
|
+
timings: Timings;
|
|
19
|
+
};
|
|
20
|
+
export type ContextBase = {
|
|
21
|
+
isError?: boolean;
|
|
22
|
+
};
|
|
23
|
+
export type Timings = {
|
|
24
|
+
[key: string]: number;
|
|
25
|
+
};
|
|
26
|
+
export type ServerRenderContext = ContextBase & ClientRenderContext & {
|
|
27
|
+
isError: false;
|
|
28
|
+
renderings: RenderingsMap;
|
|
29
|
+
renderOptions: RenderOptions;
|
|
30
|
+
timings: Timings;
|
|
31
|
+
resolveRendering: (rendering: RenderingReference) => Promise<any>;
|
|
32
|
+
};
|
|
33
|
+
export declare function createRenderContext({ page, renderings, renderOptions, compileRendering, timings, mode, apiConfig, }: {
|
|
34
|
+
page: Page;
|
|
35
|
+
renderings: RenderingsMap;
|
|
36
|
+
renderOptions?: RenderOptions;
|
|
37
|
+
timings?: Timings;
|
|
38
|
+
compileRendering?: (rendering: Rendering) => Promise<React.ComponentType<any> | null>;
|
|
39
|
+
mode: string;
|
|
40
|
+
apiConfig?: ApiConfig;
|
|
41
|
+
}): Promise<ServerRenderContext>;
|
|
42
|
+
export type RenderingProps = {
|
|
43
|
+
context: ServerRenderContext;
|
|
44
|
+
component: ComponentData;
|
|
45
|
+
index: number;
|
|
46
|
+
count: number;
|
|
47
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { loadDictionary } from "./route-data/dictionary";
|
|
2
|
+
export async function createRenderContext({ page, renderings, renderOptions, compileRendering, timings, mode, apiConfig, }) {
|
|
3
|
+
const startDictionaryTime = performance.now();
|
|
4
|
+
const dictionary = page
|
|
5
|
+
? await loadDictionary(page.site.name, page.language, page.mode, apiConfig)
|
|
6
|
+
: undefined;
|
|
7
|
+
if (timings) {
|
|
8
|
+
const dictionaryTime = performance.now() - startDictionaryTime;
|
|
9
|
+
timings["dictionary"] = dictionaryTime;
|
|
10
|
+
}
|
|
11
|
+
const context = {
|
|
12
|
+
dictionary: dictionary,
|
|
13
|
+
page,
|
|
14
|
+
mode,
|
|
15
|
+
isEditing: mode === "edit",
|
|
16
|
+
isPreview: mode === "preview",
|
|
17
|
+
renderings,
|
|
18
|
+
isError: false,
|
|
19
|
+
renderOptions: renderOptions || {},
|
|
20
|
+
resolveRendering: (renderingReference) => {
|
|
21
|
+
if (compileRendering === undefined)
|
|
22
|
+
throw new Error("compileRendering is not defined" + renderingReference);
|
|
23
|
+
// return alpacaHeadless.configuration.getRenderingById(
|
|
24
|
+
// renderingReference,
|
|
25
|
+
// compileRendering
|
|
26
|
+
// );
|
|
27
|
+
// TODO
|
|
28
|
+
return Promise.resolve(null);
|
|
29
|
+
},
|
|
30
|
+
timings: timings || {},
|
|
31
|
+
};
|
|
32
|
+
return context;
|
|
33
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { RenderingReference } from "../types/layoutDataTypes.js";
|
|
3
|
+
export type Rendering = {
|
|
4
|
+
code: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function getRenderingById(renderingReference: RenderingReference, compileRendering: (rendering: Rendering) => Promise<React.ComponentType<any> | null>): Promise<React.ComponentType<any> | null>;
|
|
7
|
+
export declare function getRendering(id: string): Promise<Rendering | undefined>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { apiGet } from "../services/api";
|
|
2
|
+
export async function getRenderingById(renderingReference, compileRendering) {
|
|
3
|
+
let cache = globalThis.alpacaRenderingCache ||
|
|
4
|
+
(globalThis.alpacaRenderingCache = {});
|
|
5
|
+
const id = renderingReference.id;
|
|
6
|
+
if (id in cache && cache[id].revision === renderingReference.revision) {
|
|
7
|
+
return cache[id].rendering;
|
|
8
|
+
}
|
|
9
|
+
const rendering = await getRendering(id);
|
|
10
|
+
if (!rendering)
|
|
11
|
+
return null;
|
|
12
|
+
const compiledRendering = await compileRendering(rendering);
|
|
13
|
+
cache[id] = { compiledRendering, revision: renderingReference.revision };
|
|
14
|
+
return compiledRendering;
|
|
15
|
+
}
|
|
16
|
+
export async function getRendering(id) {
|
|
17
|
+
const result = await apiGet("/alpaca/headless/component/rendering?id=" + id);
|
|
18
|
+
if (result.error) {
|
|
19
|
+
console.log("Could not load rendering " + id + " Response: " + result.error);
|
|
20
|
+
}
|
|
21
|
+
return result.data;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runTailwind(tsxContent: string): Promise<string>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { writeFile, mkdir, readFile, rm } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
import { exec } from "child_process";
|
|
6
|
+
import { randomBytes } from "crypto";
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
export async function runTailwind(tsxContent) {
|
|
9
|
+
const randomString = randomBytes(8).toString("hex");
|
|
10
|
+
const dir = join(tmpdir(), `tailwind-${Date.now()}-${randomString}`);
|
|
11
|
+
const tempDir = await mkdir(dir, {
|
|
12
|
+
recursive: true,
|
|
13
|
+
});
|
|
14
|
+
if (!tempDir)
|
|
15
|
+
throw new Error("Could not create temp directory");
|
|
16
|
+
try {
|
|
17
|
+
const tsxFilePath = join(tempDir, "Component.tsx");
|
|
18
|
+
await writeFile(tsxFilePath, tsxContent);
|
|
19
|
+
const tailwindConfigPath = join(tempDir, "tailwind.config.js");
|
|
20
|
+
const tailwindConfigContent = `
|
|
21
|
+
module.exports = {
|
|
22
|
+
content: ["./*.tsx"],
|
|
23
|
+
theme: {
|
|
24
|
+
extend: {},
|
|
25
|
+
},
|
|
26
|
+
plugins: [],
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
await writeFile(tailwindConfigPath, tailwindConfigContent);
|
|
30
|
+
const inputCssPath = join(tempDir, "input.css");
|
|
31
|
+
const inputCssContent = "@tailwind components; @tailwind utilities;";
|
|
32
|
+
await writeFile(inputCssPath, inputCssContent);
|
|
33
|
+
const outputCssPath = join(tempDir, "output.css");
|
|
34
|
+
await execAsync("npx tailwindcss " +
|
|
35
|
+
["-i", inputCssPath, "-o", outputCssPath, "--minify"].join(" "), { encoding: "utf8", cwd: tempDir });
|
|
36
|
+
const generatedCss = await readFile(outputCssPath, "utf8");
|
|
37
|
+
return generatedCss;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
//Cleanup: remove the temporary directory
|
|
41
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { apiGet } from "../services/api";
|
|
2
|
+
const dictionaryCache = {};
|
|
3
|
+
const cacheExpirationTime = 60000;
|
|
4
|
+
export async function loadDictionary(siteName, language, mode, apiConfig) {
|
|
5
|
+
const cacheKey = siteName + "_#_" + language;
|
|
6
|
+
if (mode !== "edit" &&
|
|
7
|
+
dictionaryCache[cacheKey] &&
|
|
8
|
+
dictionaryCache[cacheKey].timestamp <
|
|
9
|
+
new Date(Date.now() + cacheExpirationTime)) {
|
|
10
|
+
return dictionaryCache[cacheKey].dictionary;
|
|
11
|
+
}
|
|
12
|
+
let url = "/alpaca/headless/dictionary" +
|
|
13
|
+
(mode === "edit" ? "/editable" : "") +
|
|
14
|
+
"?siteName=" +
|
|
15
|
+
siteName +
|
|
16
|
+
"&language=" +
|
|
17
|
+
language;
|
|
18
|
+
console.log(`Fetching dictionary from ${url}.`);
|
|
19
|
+
const response = await apiGet(url, apiConfig);
|
|
20
|
+
if (response.error) {
|
|
21
|
+
console.log("Could not load dictionary: Response code: " + response.status);
|
|
22
|
+
console.log(response.error);
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
const dictionary = await response.data;
|
|
26
|
+
dictionaryCache[cacheKey] = {
|
|
27
|
+
dictionary: dictionary,
|
|
28
|
+
timestamp: new Date(),
|
|
29
|
+
};
|
|
30
|
+
return dictionary;
|
|
31
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { RenderOptions } from "..";
|
|
3
|
+
import { ApiConfig } from "../services/api";
|
|
4
|
+
import { ItemDescriptor } from "../types/items";
|
|
5
|
+
import { RenderMode, RenderingsMap } from "../types/layoutDataTypes";
|
|
6
|
+
import { PageResult, Result, RouteData } from "./route-data";
|
|
7
|
+
type CacheEntry = {
|
|
8
|
+
result: Promise<Result>;
|
|
9
|
+
expiresOn: Date;
|
|
10
|
+
};
|
|
11
|
+
declare global {
|
|
12
|
+
var alpacaRouteDataCache: {
|
|
13
|
+
[key: string]: CacheEntry;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export type GetPageParams = {
|
|
17
|
+
itemId: string;
|
|
18
|
+
language: string;
|
|
19
|
+
version: number;
|
|
20
|
+
siteName: string;
|
|
21
|
+
renderOptions: RenderOptions;
|
|
22
|
+
renderings: RenderingsMap;
|
|
23
|
+
compileRendering?: (rendering: {
|
|
24
|
+
code: string;
|
|
25
|
+
}) => Promise<React.ComponentType<any> | null>;
|
|
26
|
+
mode: RenderMode;
|
|
27
|
+
searchParams: any;
|
|
28
|
+
apiConfig?: ApiConfig;
|
|
29
|
+
};
|
|
30
|
+
export declare function getPage({ itemId, language, version, siteName, renderOptions, renderings, compileRendering, mode, searchParams, apiConfig, }: GetPageParams): Promise<RouteData>;
|
|
31
|
+
export type ResolveRouteParams = {
|
|
32
|
+
path: string[] | string;
|
|
33
|
+
searchParams: any;
|
|
34
|
+
host: string;
|
|
35
|
+
renderOptions: RenderOptions;
|
|
36
|
+
renderings: RenderingsMap;
|
|
37
|
+
compileRendering?: (rendering: {
|
|
38
|
+
code: string;
|
|
39
|
+
}) => Promise<React.ComponentType<any> | null>;
|
|
40
|
+
apiConfig?: ApiConfig;
|
|
41
|
+
};
|
|
42
|
+
export declare function resolveRoute({ path, searchParams, host, renderOptions, renderings, compileRendering, apiConfig, }: ResolveRouteParams): Promise<RouteData>;
|
|
43
|
+
export declare const loadPartialLayout: (itemId: string, language: string, version: number, component: ItemDescriptor | undefined, placeholderKey: string, options: RenderOptions, siteName: string, mode: string) => Promise<PageResult | null>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createRenderContext } from "../render-context";
|
|
2
|
+
import { apiPostObject } from "../services/api";
|
|
3
|
+
if (!globalThis.alpacaRouteDataCache) {
|
|
4
|
+
globalThis.alpacaRouteDataCache = {};
|
|
5
|
+
}
|
|
6
|
+
const fetchRouteData = async (path, host, mode, itemId, language, version, options, apiConfig, editRevision, siteName) => {
|
|
7
|
+
var _a;
|
|
8
|
+
const cache = globalThis.alpacaRouteDataCache;
|
|
9
|
+
const cacheKey = editRevision + itemId + language + version + siteName + path + host + mode;
|
|
10
|
+
if (cacheKey && ((_a = cache[cacheKey]) === null || _a === void 0 ? void 0 : _a.expiresOn) > new Date()) {
|
|
11
|
+
Object.keys(cache).forEach((key) => {
|
|
12
|
+
if (cache[key].expiresOn < new Date()) {
|
|
13
|
+
delete cache[key];
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return Object.assign({}, (await cache[cacheKey].result));
|
|
17
|
+
}
|
|
18
|
+
const cacheEntry = (cache[cacheKey] = {
|
|
19
|
+
result: doFetchRouteData(path, host, mode, itemId, language, version, options, apiConfig, siteName),
|
|
20
|
+
expiresOn: new Date(Date.now() + 1000 * 5),
|
|
21
|
+
});
|
|
22
|
+
return Object.assign({}, (await cacheEntry.result));
|
|
23
|
+
};
|
|
24
|
+
const doFetchRouteData = async (path, host, mode, itemId, language, version, options, apiConfig, siteName) => {
|
|
25
|
+
const request = {
|
|
26
|
+
path,
|
|
27
|
+
host,
|
|
28
|
+
mode,
|
|
29
|
+
itemId,
|
|
30
|
+
language,
|
|
31
|
+
version,
|
|
32
|
+
siteName,
|
|
33
|
+
options,
|
|
34
|
+
};
|
|
35
|
+
console.log(`@Fetching page layout. Host:${request.host} Path: ${request.path}`, request);
|
|
36
|
+
const startTime = performance.now();
|
|
37
|
+
const response = await apiPostObject("/alpaca/headless/layout", request, apiConfig);
|
|
38
|
+
const endTime = performance.now();
|
|
39
|
+
const duration = endTime - startTime;
|
|
40
|
+
console.log(`@Page layout fetched in ${duration}ms. Result:`, response.status);
|
|
41
|
+
if (response.status === 200) {
|
|
42
|
+
const data = await response.data;
|
|
43
|
+
const result = Object.assign(Object.assign({}, data), { type: "page", duration });
|
|
44
|
+
if (result.timings)
|
|
45
|
+
result.timings["network"] =
|
|
46
|
+
result.duration - result.timings["totalLayout"];
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
if (response.status === 401) {
|
|
50
|
+
var auth = response.headers.get("WWW-Authenticate");
|
|
51
|
+
return { type: "unauthorized", auth: auth, duration };
|
|
52
|
+
}
|
|
53
|
+
if (response.status === 404) {
|
|
54
|
+
return {
|
|
55
|
+
type: "notfound",
|
|
56
|
+
duration,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (response.status === 301 || response.status === 302) {
|
|
60
|
+
return {
|
|
61
|
+
type: "redirect",
|
|
62
|
+
location: response.headers.get("Location"),
|
|
63
|
+
redirectType: response.status === 301 ? "permanent" : "temporary",
|
|
64
|
+
duration,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
console.log("@Error: ", response.error || "Unknown error", response.status);
|
|
68
|
+
return {
|
|
69
|
+
type: "error",
|
|
70
|
+
errorCode: response.status,
|
|
71
|
+
message: response.error || "Unknown error",
|
|
72
|
+
duration,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
export async function getPage({ itemId, language, version, siteName, renderOptions, renderings, compileRendering, mode, searchParams, apiConfig, }) {
|
|
76
|
+
const result = await fetchRouteData("", "", mode || "normal", itemId, language, version, renderOptions, apiConfig, searchParams["edit_rev"], siteName);
|
|
77
|
+
return await createRouteData(result, renderings, renderOptions, mode, apiConfig, compileRendering);
|
|
78
|
+
}
|
|
79
|
+
async function createRouteData(result, renderings, renderOptions, mode, apiConfig, compileRendering) {
|
|
80
|
+
const context = result.type === "page"
|
|
81
|
+
? await createRenderContext({
|
|
82
|
+
page: result.page,
|
|
83
|
+
renderings: renderings,
|
|
84
|
+
compileRendering: compileRendering,
|
|
85
|
+
renderOptions: renderOptions,
|
|
86
|
+
mode,
|
|
87
|
+
apiConfig,
|
|
88
|
+
})
|
|
89
|
+
: undefined;
|
|
90
|
+
// const mapStart = performance.now();
|
|
91
|
+
// if (result.type === "page" && context) {
|
|
92
|
+
// mapContext(context, result.page);
|
|
93
|
+
// }
|
|
94
|
+
// const mapEnd = performance.now();
|
|
95
|
+
// console.log(`@Map context took ${mapEnd - mapStart}ms`);
|
|
96
|
+
return {
|
|
97
|
+
result,
|
|
98
|
+
context: context,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export async function resolveRoute({ path, searchParams, host, renderOptions, renderings, compileRendering, apiConfig, }) {
|
|
102
|
+
var _a;
|
|
103
|
+
const mode = searchParams["mode"] || "normal";
|
|
104
|
+
const result = await fetchRouteData(Array.isArray(path) ? (_a = path === null || path === void 0 ? void 0 : path.join("/")) !== null && _a !== void 0 ? _a : "/" : (path || "/"), host, mode, searchParams["itemid"] || searchParams["itemId"], searchParams["lang"], parseInt(searchParams["version"]), renderOptions, apiConfig, searchParams["edit_rev"]);
|
|
105
|
+
return await createRouteData(result, renderings, renderOptions, mode, apiConfig, compileRendering);
|
|
106
|
+
}
|
|
107
|
+
export const loadPartialLayout = async (itemId, language, version, component, placeholderKey, options, siteName, mode) => {
|
|
108
|
+
const request = {
|
|
109
|
+
ItemId: itemId,
|
|
110
|
+
Language: language,
|
|
111
|
+
Version: version,
|
|
112
|
+
Options: options,
|
|
113
|
+
Component: component,
|
|
114
|
+
Placeholder: placeholderKey,
|
|
115
|
+
SiteName: siteName,
|
|
116
|
+
Mode: mode,
|
|
117
|
+
};
|
|
118
|
+
const result = await apiPostObject("/alpaca/headless/layout/partial", request);
|
|
119
|
+
const data = result.data;
|
|
120
|
+
if (!data || !("page" in data))
|
|
121
|
+
return null;
|
|
122
|
+
return data;
|
|
123
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ServerRenderContext } from "../render-context";
|
|
2
|
+
import { Dictionary, Page } from "../types/layoutDataTypes";
|
|
3
|
+
export type BaseResult = {
|
|
4
|
+
type: "page" | "redirect" | "error" | "notfound" | "unauthorized";
|
|
5
|
+
timings?: {
|
|
6
|
+
[key: string]: number;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export type PageResult = BaseResult & {
|
|
10
|
+
type: "page";
|
|
11
|
+
page: Page;
|
|
12
|
+
};
|
|
13
|
+
export type UnauthorizedResult = BaseResult & {
|
|
14
|
+
type: "unauthorized";
|
|
15
|
+
auth: string | null;
|
|
16
|
+
};
|
|
17
|
+
export type RedirectResult = BaseResult & {
|
|
18
|
+
type: "redirect";
|
|
19
|
+
location: string;
|
|
20
|
+
redirectType: "permanent" | "temporary";
|
|
21
|
+
};
|
|
22
|
+
export type ErrorResult = BaseResult & {
|
|
23
|
+
type: "error";
|
|
24
|
+
message: string;
|
|
25
|
+
errorCode: number;
|
|
26
|
+
};
|
|
27
|
+
export type NotFoundResult = BaseResult & {
|
|
28
|
+
type: "notfound";
|
|
29
|
+
};
|
|
30
|
+
export type Result = PageResult | UnauthorizedResult | RedirectResult | ErrorResult | NotFoundResult;
|
|
31
|
+
export type RouteData = {
|
|
32
|
+
context?: ServerRenderContext;
|
|
33
|
+
result: Result;
|
|
34
|
+
};
|
|
35
|
+
export type EditorRouteData = {
|
|
36
|
+
result: Result;
|
|
37
|
+
dictionary?: Dictionary;
|
|
38
|
+
timings?: {
|
|
39
|
+
[key: string]: number;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
export type FetchOptions = {
|
|
3
|
+
tags?: string[];
|
|
4
|
+
revalidate?: number | false;
|
|
5
|
+
};
|
|
6
|
+
export type FetchResult = {
|
|
7
|
+
status: number;
|
|
8
|
+
error?: string;
|
|
9
|
+
data?: any;
|
|
10
|
+
headers: Headers;
|
|
11
|
+
};
|
|
12
|
+
export type ApiConfig = {
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
headers?: Headers;
|
|
15
|
+
};
|
|
16
|
+
export declare function apiGet(route: string, config?: ApiConfig): Promise<FetchResult>;
|
|
17
|
+
export declare function apiPostObject(route: string, object: any, config?: ApiConfig): Promise<FetchResult>;
|
|
18
|
+
export declare function getApiKey(): string | undefined;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
export async function apiGet(route, config) {
|
|
4
|
+
const url = process.env.LAYOUT_SERVICE_URL + route;
|
|
5
|
+
const requestHeaders = getRequestHeaders(config);
|
|
6
|
+
try {
|
|
7
|
+
const response = await axios.get(url, {
|
|
8
|
+
headers: requestHeaders,
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
data: response.data,
|
|
12
|
+
status: response.status,
|
|
13
|
+
headers: convertAxiosHeaders(response.headers),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (error.response) {
|
|
18
|
+
return {
|
|
19
|
+
status: error.response.status,
|
|
20
|
+
error: error.response.statusText,
|
|
21
|
+
headers: convertAxiosHeaders(error.response.headers),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
return {
|
|
26
|
+
status: 500,
|
|
27
|
+
error: error.message,
|
|
28
|
+
headers: convertAxiosHeaders(error.response.headers),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function getRequestHeaders(config) {
|
|
34
|
+
var _a;
|
|
35
|
+
const requestHeaders = {};
|
|
36
|
+
// Convert Headers (node-fetch) to AxiosRequestHeaders (plain object)
|
|
37
|
+
if (config === null || config === void 0 ? void 0 : config.headers) {
|
|
38
|
+
config.headers.forEach((value, key) => {
|
|
39
|
+
requestHeaders[key] = value;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// Set default headers
|
|
43
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
44
|
+
requestHeaders["x-api-key"] = (_a = config === null || config === void 0 ? void 0 : config.apiKey) !== null && _a !== void 0 ? _a : getApiKey();
|
|
45
|
+
return requestHeaders;
|
|
46
|
+
}
|
|
47
|
+
export async function apiPostObject(route, object, config) {
|
|
48
|
+
const url = process.env.LAYOUT_SERVICE_URL + route;
|
|
49
|
+
const requestHeaders = getRequestHeaders(config);
|
|
50
|
+
const body = JSON.stringify(object);
|
|
51
|
+
try {
|
|
52
|
+
const response = await axios.post(url, body, {
|
|
53
|
+
headers: requestHeaders,
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
data: response.data,
|
|
57
|
+
status: response.status,
|
|
58
|
+
headers: convertAxiosHeaders(response.headers),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error.errors) {
|
|
63
|
+
let message = "";
|
|
64
|
+
error.errors.forEach((error) => {
|
|
65
|
+
message += error.message + " ";
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
status: 500,
|
|
69
|
+
error: message,
|
|
70
|
+
headers: new Headers(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (error.response) {
|
|
74
|
+
return {
|
|
75
|
+
status: error.response.status,
|
|
76
|
+
error: error.response.statusText,
|
|
77
|
+
headers: convertAxiosHeaders(error.response.headers),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
return {
|
|
82
|
+
status: 500,
|
|
83
|
+
error: error.message,
|
|
84
|
+
headers: new Headers(),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function getApiKey() {
|
|
90
|
+
const apiKey = process.env.ALPACA_HEADLESS_API_KEY || process.env.API_KEY;
|
|
91
|
+
if (!apiKey)
|
|
92
|
+
console.log("ERROR: Could not find API key environment variable: ALPACA_HEADLESS_API_KEY");
|
|
93
|
+
return apiKey;
|
|
94
|
+
}
|
|
95
|
+
function convertAxiosHeaders(headers) {
|
|
96
|
+
const convertedHeaders = new Headers();
|
|
97
|
+
for (const key in headers) {
|
|
98
|
+
if (headers.hasOwnProperty(key)) {
|
|
99
|
+
convertedHeaders.set(key.toLowerCase(), headers[key]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return convertedHeaders;
|
|
103
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ApiConfig } from "./api";
|
|
2
|
+
export type ExecuteGraphQLParams = {
|
|
3
|
+
query: string;
|
|
4
|
+
tags?: string[];
|
|
5
|
+
host?: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
language?: string;
|
|
8
|
+
mode?: string;
|
|
9
|
+
itemId?: string;
|
|
10
|
+
version?: number;
|
|
11
|
+
revalidate?: number;
|
|
12
|
+
siteName?: string;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
schema?: string;
|
|
15
|
+
apiConfig?: ApiConfig;
|
|
16
|
+
};
|
|
17
|
+
export declare function executeGraphQLQuery({ query, host, path, language, mode, itemId, version, siteName, schema, sessionId, apiConfig, }: ExecuteGraphQLParams): Promise<{
|
|
18
|
+
status: number;
|
|
19
|
+
errors: any;
|
|
20
|
+
data?: undefined;
|
|
21
|
+
} | {
|
|
22
|
+
data: any;
|
|
23
|
+
status: number;
|
|
24
|
+
errors: any;
|
|
25
|
+
}>;
|
|
26
|
+
export declare function getGraphQLSchema({ id, apiConfig, }: {
|
|
27
|
+
id: string;
|
|
28
|
+
apiConfig: ApiConfig;
|
|
29
|
+
}): Promise<any>;
|