@atproto/oauth-provider 0.1.0 → 0.1.2-rc.0
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/CHANGELOG.md +20 -0
- package/dist/account/account-manager.d.ts +2 -2
- package/dist/account/account-manager.d.ts.map +1 -1
- package/dist/account/account-manager.js.map +1 -1
- package/dist/account/account-store.d.ts +19 -6
- package/dist/account/account-store.d.ts.map +1 -1
- package/dist/account/account-store.js +16 -1
- package/dist/account/account-store.js.map +1 -1
- package/dist/assets/app/bundle-manifest.json +3 -3
- package/dist/assets/app/main.css +1 -1
- package/dist/assets/app/main.js +3 -3
- package/dist/assets/app/main.js.map +1 -1
- package/dist/client/client-auth.d.ts.map +1 -1
- package/dist/client/client-auth.js +2 -2
- package/dist/client/client-auth.js.map +1 -1
- package/dist/client/client-manager.d.ts.map +1 -1
- package/dist/client/client-manager.js +3 -1
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +3 -3
- package/dist/client/client.js.map +1 -1
- package/dist/dpop/dpop-manager.d.ts.map +1 -1
- package/dist/dpop/dpop-manager.js +3 -3
- package/dist/dpop/dpop-manager.js.map +1 -1
- package/dist/errors/invalid-token-error.d.ts.map +1 -1
- package/dist/errors/invalid-token-error.js +3 -2
- package/dist/errors/invalid-token-error.js.map +1 -1
- package/dist/errors/second-authentication-factor-required-error.d.ts +13 -0
- package/dist/errors/second-authentication-factor-required-error.d.ts.map +1 -0
- package/dist/errors/second-authentication-factor-required-error.js +23 -0
- package/dist/errors/second-authentication-factor-required-error.js.map +1 -0
- package/dist/lib/util/authorization-header.js +1 -1
- package/dist/lib/util/authorization-header.js.map +1 -1
- package/dist/metadata/build-metadata.d.ts.map +1 -1
- package/dist/metadata/build-metadata.js +2 -0
- package/dist/metadata/build-metadata.js.map +1 -1
- package/dist/oauth-errors.d.ts +1 -0
- package/dist/oauth-errors.d.ts.map +1 -1
- package/dist/oauth-errors.js +3 -1
- package/dist/oauth-errors.js.map +1 -1
- package/dist/oauth-provider.d.ts +101 -4
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +98 -110
- package/dist/oauth-provider.js.map +1 -1
- package/dist/output/build-authorize-data.d.ts +40 -0
- package/dist/output/build-authorize-data.d.ts.map +1 -0
- package/dist/output/build-authorize-data.js +22 -0
- package/dist/output/build-authorize-data.js.map +1 -0
- package/dist/output/build-error-payload.d.ts.map +1 -1
- package/dist/output/build-error-payload.js +4 -3
- package/dist/output/build-error-payload.js.map +1 -1
- package/dist/output/customization.d.ts +2 -12
- package/dist/output/customization.d.ts.map +1 -1
- package/dist/output/customization.js +59 -32
- package/dist/output/customization.js.map +1 -1
- package/dist/output/output-manager.d.ts +16 -0
- package/dist/output/output-manager.d.ts.map +1 -0
- package/dist/output/output-manager.js +69 -0
- package/dist/output/output-manager.js.map +1 -0
- package/dist/output/send-web-page.d.ts +1 -1
- package/dist/output/send-web-page.d.ts.map +1 -1
- package/dist/output/send-web-page.js +3 -2
- package/dist/output/send-web-page.js.map +1 -1
- package/package.json +7 -7
- package/src/account/account-manager.ts +2 -2
- package/src/account/account-store.ts +12 -6
- package/src/assets/app/components/accept-form.tsx +86 -83
- package/src/assets/app/components/account-picker.tsx +98 -79
- package/src/assets/app/components/button.tsx +34 -0
- package/src/assets/app/components/client-identifier.tsx +12 -13
- package/src/assets/app/components/fieldset.tsx +26 -0
- package/src/assets/app/components/form-card.tsx +47 -0
- package/src/assets/app/components/help-card.tsx +1 -1
- package/src/assets/app/components/icons/alert-icon.tsx +5 -0
- package/src/assets/app/components/icons/at-symbol-icon.tsx +5 -0
- package/src/assets/app/components/icons/caret-right-icon.tsx +5 -0
- package/src/assets/app/components/icons/lock-icon.tsx +5 -0
- package/src/assets/app/components/icons/token-icon.tsx +5 -0
- package/src/assets/app/components/icons/util.tsx +17 -0
- package/src/assets/app/components/info-card.tsx +45 -0
- package/src/assets/app/components/input-checkbox.tsx +47 -0
- package/src/assets/app/components/input-container.tsx +37 -0
- package/src/assets/app/components/input-layout.tsx +47 -0
- package/src/assets/app/components/input-text.tsx +69 -0
- package/src/assets/app/components/layout-title-page.tsx +33 -16
- package/src/assets/app/components/layout-welcome.tsx +30 -14
- package/src/assets/app/components/sign-in-form.tsx +214 -196
- package/src/assets/app/components/sign-up-account-form.tsx +101 -117
- package/src/assets/app/components/sign-up-disclaimer.tsx +1 -1
- package/src/assets/app/hooks/use-api.ts +2 -0
- package/src/assets/app/lib/api.ts +49 -14
- package/src/assets/app/lib/clsx.ts +6 -1
- package/src/assets/app/lib/util.ts +3 -0
- package/src/assets/app/main.css +2 -1
- package/src/assets/app/views/accept-view.tsx +4 -3
- package/src/assets/app/views/authorize-view.tsx +8 -4
- package/src/assets/app/views/error-view.tsx +24 -15
- package/src/assets/app/views/sign-in-view.tsx +5 -15
- package/src/assets/app/views/sign-up-view.tsx +3 -10
- package/src/assets/app/views/welcome-view.tsx +11 -18
- package/src/client/client-auth.ts +3 -2
- package/src/client/client-manager.ts +2 -1
- package/src/client/client.ts +3 -1
- package/src/dpop/dpop-manager.ts +3 -2
- package/src/errors/invalid-token-error.ts +3 -1
- package/src/errors/second-authentication-factor-required-error.ts +25 -0
- package/src/lib/util/authorization-header.ts +1 -1
- package/src/metadata/build-metadata.ts +3 -0
- package/src/oauth-errors.ts +1 -0
- package/src/oauth-provider.ts +110 -99
- package/src/output/{send-authorize-page.ts → build-authorize-data.ts} +3 -43
- package/src/output/build-error-payload.ts +3 -1
- package/src/output/customization.ts +67 -45
- package/src/output/output-manager.ts +87 -0
- package/src/output/send-web-page.ts +4 -3
- package/tailwind.config.js +14 -1
- package/src/assets/app/components/error-card.tsx +0 -41
- package/src/output/send-error-page.ts +0 -41
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
// Matches colors defined in tailwind.config.js
|
|
2
|
-
const colorNames = ['
|
|
2
|
+
const colorNames = ['brand', 'error', 'warning'] as const
|
|
3
3
|
type ColorName = (typeof colorNames)[number]
|
|
4
4
|
const isColorName = (name: string): name is ColorName =>
|
|
5
5
|
(colorNames as readonly string[]).includes(name)
|
|
6
6
|
|
|
7
|
-
export type FieldDefinition = {
|
|
8
|
-
label?: string
|
|
9
|
-
placeholder?: string
|
|
10
|
-
pattern?: string
|
|
11
|
-
title?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export type ExtraFieldDefinition = FieldDefinition & {
|
|
15
|
-
type: 'text' | 'password' | 'date' | 'captcha'
|
|
16
|
-
required?: boolean
|
|
17
|
-
[_: string]: unknown
|
|
18
|
-
}
|
|
19
|
-
|
|
20
7
|
export type Customization = {
|
|
21
8
|
name?: string
|
|
22
9
|
logo?: string
|
|
@@ -41,56 +28,91 @@ export function buildCustomizationData({
|
|
|
41
28
|
}
|
|
42
29
|
|
|
43
30
|
export function buildCustomizationCss(customization?: Customization) {
|
|
44
|
-
|
|
31
|
+
const vars = Array.from(buildCustomizationVars(customization))
|
|
32
|
+
if (vars.length) return `:root { ${vars.join(' ')} }`
|
|
33
|
+
|
|
34
|
+
return ''
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function* buildCustomizationVars(customization?: Customization) {
|
|
38
|
+
if (customization?.colors) {
|
|
39
|
+
for (const [name, value] of Object.entries(customization.colors)) {
|
|
40
|
+
if (!isColorName(name)) {
|
|
41
|
+
throw new TypeError(`Invalid color name: ${name}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Skip undefined values
|
|
45
|
+
if (value === undefined) continue
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
.filter((e) => isColorName(e[0]) && e[1] != null)
|
|
48
|
-
.map(([name, value]) => [name, parseColor(value)] as const)
|
|
49
|
-
.filter((e): e is [ColorName, ParsedColor] => e[1] != null)
|
|
50
|
-
// alpha not supported by tailwind (it does not work that way)
|
|
51
|
-
.map(([name, { r, g, b }]) => `--color-${name}: ${r} ${g} ${b};`)
|
|
47
|
+
const { r, g, b, a } = parseColor(value)
|
|
52
48
|
|
|
53
|
-
|
|
49
|
+
// Tailwind does not apply alpha values to base colors
|
|
50
|
+
if (a !== undefined) throw new TypeError('Alpha not supported')
|
|
51
|
+
|
|
52
|
+
yield `--color-${name}: ${r} ${g} ${b};`
|
|
53
|
+
}
|
|
54
|
+
}
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
type
|
|
57
|
-
function parseColor(color:
|
|
57
|
+
type RgbaColor = { r: number; g: number; b: number; a?: number }
|
|
58
|
+
function parseColor(color: unknown): RgbaColor {
|
|
59
|
+
if (typeof color !== 'string') {
|
|
60
|
+
throw new TypeError(`Invalid color value: ${typeof color}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
if (color.startsWith('#')) {
|
|
59
64
|
if (color.length === 4 || color.length === 5) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
const r = parseUi8Hex(color.slice(1, 2))
|
|
66
|
+
const g = parseUi8Hex(color.slice(2, 3))
|
|
67
|
+
const b = parseUi8Hex(color.slice(3, 4))
|
|
68
|
+
const a = color.length > 4 ? parseUi8Hex(color.slice(4, 5)) : undefined
|
|
64
69
|
return { r, g, b, a }
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
if (color.length === 7 || color.length === 9) {
|
|
68
|
-
const r =
|
|
69
|
-
const g =
|
|
70
|
-
const b =
|
|
71
|
-
const a = color.length > 8 ?
|
|
73
|
+
const r = parseUi8Hex(color.slice(1, 3))
|
|
74
|
+
const g = parseUi8Hex(color.slice(3, 5))
|
|
75
|
+
const b = parseUi8Hex(color.slice(5, 7))
|
|
76
|
+
const a = color.length > 8 ? parseUi8Hex(color.slice(7, 9)) : undefined
|
|
72
77
|
return { r, g, b, a }
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
throw new TypeError(`Invalid hex color: ${color}`)
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
const rgbMatch = color.match(
|
|
83
|
+
const rgbMatch = color.match(
|
|
84
|
+
/^\s*rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/,
|
|
85
|
+
)
|
|
79
86
|
if (rgbMatch) {
|
|
80
|
-
const
|
|
81
|
-
|
|
87
|
+
const r = parseUi8Dec(rgbMatch[1])
|
|
88
|
+
const g = parseUi8Dec(rgbMatch[2])
|
|
89
|
+
const b = parseUi8Dec(rgbMatch[3])
|
|
90
|
+
return { r, g, b }
|
|
82
91
|
}
|
|
83
92
|
|
|
84
|
-
const rgbaMatch = color.match(
|
|
93
|
+
const rgbaMatch = color.match(
|
|
94
|
+
/^\s*rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/,
|
|
95
|
+
)
|
|
85
96
|
if (rgbaMatch) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
a: parseInt(a, 10),
|
|
92
|
-
}
|
|
97
|
+
const r = parseUi8Dec(rgbaMatch[1])
|
|
98
|
+
const g = parseUi8Dec(rgbaMatch[2])
|
|
99
|
+
const b = parseUi8Dec(rgbaMatch[3])
|
|
100
|
+
const a = parseUi8Dec(rgbaMatch[4])
|
|
101
|
+
return { r, g, b, a }
|
|
93
102
|
}
|
|
94
103
|
|
|
95
|
-
|
|
104
|
+
throw new TypeError(`Unsupported color format: ${color}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function parseUi8Hex(v: string) {
|
|
108
|
+
return asUi8(parseInt(v, 16))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseUi8Dec(v: string) {
|
|
112
|
+
return asUi8(parseInt(v, 10))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function asUi8(v: number) {
|
|
116
|
+
if (v >= 0 && v <= 255 && v === (v | 0)) return v
|
|
117
|
+
throw new TypeError(`Invalid color component: ${v}`)
|
|
96
118
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ServerResponse } from 'node:http'
|
|
2
|
+
|
|
3
|
+
import { Asset } from '../assets/asset.js'
|
|
4
|
+
import { getAsset } from '../assets/index.js'
|
|
5
|
+
import { cssCode, Html, html } from '../lib/html/index.js'
|
|
6
|
+
import {
|
|
7
|
+
AuthorizationResultAuthorize,
|
|
8
|
+
buildAuthorizeData,
|
|
9
|
+
} from './build-authorize-data.js'
|
|
10
|
+
import { buildErrorPayload, buildErrorStatus } from './build-error-payload.js'
|
|
11
|
+
import {
|
|
12
|
+
buildCustomizationCss,
|
|
13
|
+
buildCustomizationData,
|
|
14
|
+
Customization,
|
|
15
|
+
} from './customization.js'
|
|
16
|
+
import { declareBackendData, sendWebPage } from './send-web-page.js'
|
|
17
|
+
|
|
18
|
+
export class OutputManager {
|
|
19
|
+
readonly customizationScript: Html
|
|
20
|
+
readonly customizationStyle: Html
|
|
21
|
+
readonly customizationLinks?: Customization['links']
|
|
22
|
+
|
|
23
|
+
// Could technically cause an "UnhandledPromiseRejection", which might cause
|
|
24
|
+
// the process to exit. This is intentional, as it's a critical error. It
|
|
25
|
+
// should never happen in practice, as the built assets are bundled with the
|
|
26
|
+
// package.
|
|
27
|
+
readonly assetsPromise: Promise<[js: Asset, css: Asset]> = Promise.all([
|
|
28
|
+
getAsset('main.js'),
|
|
29
|
+
getAsset('main.css'),
|
|
30
|
+
] as const)
|
|
31
|
+
|
|
32
|
+
constructor(customization?: Customization) {
|
|
33
|
+
// Note: building this here for two reasons:
|
|
34
|
+
// 1. To avoid re-building it on every request
|
|
35
|
+
// 2. To throw during init if the customization is invalid
|
|
36
|
+
this.customizationScript = declareBackendData(
|
|
37
|
+
'__customizationData',
|
|
38
|
+
buildCustomizationData(customization),
|
|
39
|
+
)
|
|
40
|
+
this.customizationStyle = cssCode(buildCustomizationCss(customization))
|
|
41
|
+
this.customizationLinks = customization?.links
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async sendAuthorizePage(
|
|
45
|
+
res: ServerResponse,
|
|
46
|
+
data: AuthorizationResultAuthorize,
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
const [jsAsset, cssAsset] = await this.assetsPromise
|
|
49
|
+
|
|
50
|
+
return sendWebPage(res, {
|
|
51
|
+
scripts: [
|
|
52
|
+
declareBackendData('__authorizeData', buildAuthorizeData(data)),
|
|
53
|
+
this.customizationScript,
|
|
54
|
+
jsAsset, // Last (to be able to read the "backend data" variables)
|
|
55
|
+
],
|
|
56
|
+
styles: [
|
|
57
|
+
cssAsset, // First (to be overridden by customization)
|
|
58
|
+
this.customizationStyle,
|
|
59
|
+
],
|
|
60
|
+
links: this.customizationLinks,
|
|
61
|
+
htmlAttrs: { lang: 'en' },
|
|
62
|
+
title: 'Authorize',
|
|
63
|
+
body: html`<div id="root"></div>`,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async sendErrorPage(res: ServerResponse, err: unknown): Promise<void> {
|
|
68
|
+
const [jsAsset, cssAsset] = await this.assetsPromise
|
|
69
|
+
|
|
70
|
+
return sendWebPage(res, {
|
|
71
|
+
status: buildErrorStatus(err),
|
|
72
|
+
scripts: [
|
|
73
|
+
declareBackendData('__errorData', buildErrorPayload(err)),
|
|
74
|
+
this.customizationScript,
|
|
75
|
+
jsAsset, // Last (to be able to read the "backend data" variables)
|
|
76
|
+
],
|
|
77
|
+
styles: [
|
|
78
|
+
cssAsset, // First (to be overridden by customization)
|
|
79
|
+
this.customizationStyle,
|
|
80
|
+
],
|
|
81
|
+
links: this.customizationLinks,
|
|
82
|
+
htmlAttrs: { lang: 'en' },
|
|
83
|
+
title: 'Error',
|
|
84
|
+
body: html`<div id="root"></div>`,
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -18,11 +18,12 @@ export function declareBackendData(name: string, data: unknown) {
|
|
|
18
18
|
return js`window[${name}]=${data};document.currentScript.remove();`
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export function sendWebPage(
|
|
21
|
+
export async function sendWebPage(
|
|
22
22
|
res: ServerResponse,
|
|
23
23
|
{ status = 200, ...options }: BuildDocumentOptions & { status?: number },
|
|
24
|
-
): void {
|
|
24
|
+
): Promise<void> {
|
|
25
25
|
// @TODO: make these headers configurable (?)
|
|
26
|
+
res.setHeader('Permissions-Policy', 'otp-credentials=*, document-domain=()')
|
|
26
27
|
res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless')
|
|
27
28
|
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin')
|
|
28
29
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
@@ -52,7 +53,7 @@ export function sendWebPage(
|
|
|
52
53
|
|
|
53
54
|
const html = buildDocument(options)
|
|
54
55
|
|
|
55
|
-
writeHtml(res, html.toString(), status)
|
|
56
|
+
return writeHtml(res, html.toString(), status)
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
function assetToHash(asset: Html | AssetRef): string {
|
package/tailwind.config.js
CHANGED
|
@@ -2,10 +2,23 @@
|
|
|
2
2
|
export default {
|
|
3
3
|
content: ['src/assets/app/**/*.{js,ts,jsx,tsx}'],
|
|
4
4
|
theme: {
|
|
5
|
+
fontFamily: {
|
|
6
|
+
sans: [
|
|
7
|
+
'-apple-system',
|
|
8
|
+
'BlinkMacSystemFont',
|
|
9
|
+
'"Segoe UI"',
|
|
10
|
+
'Roboto',
|
|
11
|
+
'Helvetica',
|
|
12
|
+
'Arial',
|
|
13
|
+
'sans-serif',
|
|
14
|
+
],
|
|
15
|
+
mono: ['Monaco', 'mono'],
|
|
16
|
+
},
|
|
5
17
|
extend: {
|
|
6
18
|
colors: {
|
|
7
|
-
|
|
19
|
+
brand: 'rgb(var(--color-brand) / <alpha-value>)',
|
|
8
20
|
error: 'rgb(var(--color-error) / <alpha-value>)',
|
|
21
|
+
warning: 'rgb(var(--color-warning) / <alpha-value>)',
|
|
9
22
|
},
|
|
10
23
|
},
|
|
11
24
|
},
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { HtmlHTMLAttributes } from 'react'
|
|
2
|
-
import { clsx } from '../lib/clsx'
|
|
3
|
-
|
|
4
|
-
export type ErrorCardProps = {
|
|
5
|
-
message?: null | string
|
|
6
|
-
role?: 'alert' | 'status'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function ErrorCard({
|
|
10
|
-
message,
|
|
11
|
-
|
|
12
|
-
role = 'alert',
|
|
13
|
-
className,
|
|
14
|
-
...attrs
|
|
15
|
-
}: Partial<ErrorCardProps> &
|
|
16
|
-
Omit<HtmlHTMLAttributes<HTMLDivElement>, keyof ErrorCardProps | 'children'>) {
|
|
17
|
-
return (
|
|
18
|
-
<div
|
|
19
|
-
{...attrs}
|
|
20
|
-
className={clsx(
|
|
21
|
-
'flex items-center rounded bg-error py-1 px-2 text-white dark:text-black shadow-md',
|
|
22
|
-
className,
|
|
23
|
-
)}
|
|
24
|
-
role={role}
|
|
25
|
-
>
|
|
26
|
-
<svg
|
|
27
|
-
className="fill-current h-4 w-4"
|
|
28
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
29
|
-
viewBox="0 0 20 20"
|
|
30
|
-
>
|
|
31
|
-
<path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm12.73-1.41A8 8 0 1 0 4.34 4.34a8 8 0 0 0 11.32 11.32zM9 11V9h2v6H9v-4zm0-6h2v2H9V5z" />
|
|
32
|
-
</svg>
|
|
33
|
-
|
|
34
|
-
<div className="ml-4">
|
|
35
|
-
<p>
|
|
36
|
-
{typeof message === 'string' ? message : 'An unknown error occurred'}
|
|
37
|
-
</p>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { ServerResponse } from 'node:http'
|
|
2
|
-
|
|
3
|
-
import { getAsset } from '../assets/index.js'
|
|
4
|
-
import { cssCode, html } from '../lib/html/index.js'
|
|
5
|
-
import { buildErrorPayload, buildErrorStatus } from './build-error-payload.js'
|
|
6
|
-
import {
|
|
7
|
-
Customization,
|
|
8
|
-
buildCustomizationCss,
|
|
9
|
-
buildCustomizationData,
|
|
10
|
-
} from './customization.js'
|
|
11
|
-
import { declareBackendData, sendWebPage } from './send-web-page.js'
|
|
12
|
-
|
|
13
|
-
export async function sendErrorPage(
|
|
14
|
-
res: ServerResponse,
|
|
15
|
-
err: unknown,
|
|
16
|
-
customization?: Customization,
|
|
17
|
-
): Promise<void> {
|
|
18
|
-
const [jsAsset, cssAsset] = await Promise.all([
|
|
19
|
-
getAsset('main.js'),
|
|
20
|
-
getAsset('main.css'),
|
|
21
|
-
])
|
|
22
|
-
|
|
23
|
-
return sendWebPage(res, {
|
|
24
|
-
status: buildErrorStatus(err),
|
|
25
|
-
scripts: [
|
|
26
|
-
declareBackendData(
|
|
27
|
-
'__customizationData',
|
|
28
|
-
buildCustomizationData(customization),
|
|
29
|
-
),
|
|
30
|
-
declareBackendData('__errorData', buildErrorPayload(err)),
|
|
31
|
-
jsAsset, // Last (to be able to read the global variables)
|
|
32
|
-
],
|
|
33
|
-
styles: [
|
|
34
|
-
cssAsset, // First (to be overridden by customization)
|
|
35
|
-
cssCode(buildCustomizationCss(customization)),
|
|
36
|
-
],
|
|
37
|
-
htmlAttrs: { lang: 'en' },
|
|
38
|
-
title: 'Error',
|
|
39
|
-
body: html`<div id="root"></div>`,
|
|
40
|
-
})
|
|
41
|
-
}
|