@czap/edge 0.1.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/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/client-hints.d.ts +145 -0
- package/dist/client-hints.d.ts.map +1 -0
- package/dist/client-hints.js +268 -0
- package/dist/client-hints.js.map +1 -0
- package/dist/edge-tier.d.ts +67 -0
- package/dist/edge-tier.d.ts.map +1 -0
- package/dist/edge-tier.js +60 -0
- package/dist/edge-tier.js.map +1 -0
- package/dist/host-adapter.d.ts +139 -0
- package/dist/host-adapter.d.ts.map +1 -0
- package/dist/host-adapter.js +89 -0
- package/dist/host-adapter.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/kv-cache.d.ts +105 -0
- package/dist/kv-cache.d.ts.map +1 -0
- package/dist/kv-cache.js +133 -0
- package/dist/kv-cache.js.map +1 -0
- package/dist/theme-compiler.d.ts +69 -0
- package/dist/theme-compiler.d.ts.map +1 -0
- package/dist/theme-compiler.js +94 -0
- package/dist/theme-compiler.js.map +1 -0
- package/package.json +55 -0
- package/src/client-hints.ts +338 -0
- package/src/edge-tier.ts +93 -0
- package/src/host-adapter.ts +217 -0
- package/src/index.ts +33 -0
- package/src/kv-cache.ts +189 -0
- package/src/theme-compiler.ts +151 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Eassa Ayoub <eassa@heyoub.dev>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @czap/edge
|
|
2
|
+
|
|
3
|
+
CDN-edge: Client Hints, tier detection, KV boundary cache, theme compilation.
|
|
4
|
+
|
|
5
|
+
## Docs
|
|
6
|
+
|
|
7
|
+
- [Naming & vocabulary](../../docs/GLOSSARY.md) — LiteShip, CZAP, `@czap/*`
|
|
8
|
+
|
|
9
|
+
- [API reference](https://github.com/heyoub/LiteShip/tree/main/docs/api/edge/) — generated from source TSDoc
|
|
10
|
+
- [Architecture index](https://github.com/heyoub/LiteShip/blob/main/docs/ARCHITECTURE.md)
|
|
11
|
+
- [ADRs](https://github.com/heyoub/LiteShip/tree/main/docs/adr/)
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @czap/edge
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Part of [LiteShip](https://github.com/heyoub/LiteShip#readme)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client Hints header parsing for edge-side device capability detection.
|
|
3
|
+
*
|
|
4
|
+
* Converts HTTP Client Hints headers into the same `ExtendedDeviceCapabilities`
|
|
5
|
+
* structure that `@czap/detect` uses, enabling reuse of the pure tier mapping
|
|
6
|
+
* functions at the edge without browser APIs.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { ExtendedDeviceCapabilities } from '@czap/detect';
|
|
11
|
+
/**
|
|
12
|
+
* Plain-object header bag accepted by {@link ClientHints.parseClientHints}.
|
|
13
|
+
*
|
|
14
|
+
* All names are lowercased because Client Hints headers are always lowercase
|
|
15
|
+
* in spec. Values that are missing simply fall back to conservative
|
|
16
|
+
* defaults during parsing.
|
|
17
|
+
*/
|
|
18
|
+
export interface ClientHintsHeaders {
|
|
19
|
+
/** `Sec-CH-UA-Platform` (e.g. `"macOS"`, `"Windows"`). */
|
|
20
|
+
readonly 'sec-ch-ua-platform'?: string;
|
|
21
|
+
/** `Sec-CH-Device-Memory` in GiB (one of the standard buckets). */
|
|
22
|
+
readonly 'sec-ch-device-memory'?: string;
|
|
23
|
+
/** `Sec-CH-DPR` — devicePixelRatio as a decimal string. */
|
|
24
|
+
readonly 'sec-ch-dpr'?: string;
|
|
25
|
+
/** `Sec-CH-Viewport-Width` in CSS pixels. */
|
|
26
|
+
readonly 'sec-ch-viewport-width'?: string;
|
|
27
|
+
/** `Sec-CH-Viewport-Height` in CSS pixels. */
|
|
28
|
+
readonly 'sec-ch-viewport-height'?: string;
|
|
29
|
+
/** `Sec-CH-Prefers-Reduced-Motion` (`reduce` / `no-preference`). */
|
|
30
|
+
readonly 'sec-ch-prefers-reduced-motion'?: string;
|
|
31
|
+
/** `Sec-CH-Prefers-Color-Scheme` (`light` / `dark`). */
|
|
32
|
+
readonly 'sec-ch-prefers-color-scheme'?: string;
|
|
33
|
+
/** `Sec-CH-UA-Mobile` as a structured boolean (`?1` / `?0`). */
|
|
34
|
+
readonly 'sec-ch-ua-mobile'?: string;
|
|
35
|
+
/** `Sec-CH-UA` — full user-agent brand list. */
|
|
36
|
+
readonly 'sec-ch-ua'?: string;
|
|
37
|
+
/** `Save-Data` (`on`). */
|
|
38
|
+
readonly 'save-data'?: string;
|
|
39
|
+
/** `Downlink` estimate in Mb/s. */
|
|
40
|
+
readonly downlink?: string;
|
|
41
|
+
/** `ECT` effective connection type. */
|
|
42
|
+
readonly ect?: string;
|
|
43
|
+
/** `RTT` round-trip-time estimate in ms. */
|
|
44
|
+
readonly rtt?: string;
|
|
45
|
+
/** `User-Agent` fallback for GPU-tier heuristics. */
|
|
46
|
+
readonly 'user-agent'?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse Client Hints headers into an {@link ExtendedDeviceCapabilities} structure.
|
|
50
|
+
*
|
|
51
|
+
* For properties that cannot be determined from headers (GPU tier, WebGPU
|
|
52
|
+
* support, CPU cores), conservative defaults are used.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { ClientHints } from '@czap/edge';
|
|
57
|
+
*
|
|
58
|
+
* const caps = ClientHints.parseClientHints({
|
|
59
|
+
* 'sec-ch-device-memory': '8',
|
|
60
|
+
* 'sec-ch-dpr': '2',
|
|
61
|
+
* 'sec-ch-viewport-width': '1440',
|
|
62
|
+
* 'sec-ch-prefers-color-scheme': 'dark',
|
|
63
|
+
* 'sec-ch-ua-mobile': '?0',
|
|
64
|
+
* });
|
|
65
|
+
* console.log(caps.memory); // 8
|
|
66
|
+
* console.log(caps.devicePixelRatio); // 2
|
|
67
|
+
* console.log(caps.prefersColorScheme); // 'dark'
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @param headers - Client Hints headers (plain object or Web API Headers)
|
|
71
|
+
* @returns An {@link ExtendedDeviceCapabilities} structure
|
|
72
|
+
*/
|
|
73
|
+
declare function parseClientHints(headers: ClientHintsHeaders | Headers): ExtendedDeviceCapabilities;
|
|
74
|
+
/**
|
|
75
|
+
* Generate the `Accept-CH` header value for requesting all useful Client Hints
|
|
76
|
+
* on subsequent requests.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* import { ClientHints } from '@czap/edge';
|
|
81
|
+
*
|
|
82
|
+
* const response = new Response('OK', {
|
|
83
|
+
* headers: { 'Accept-CH': ClientHints.acceptCHHeader() },
|
|
84
|
+
* });
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* @returns A comma-separated list of Client Hint header names
|
|
88
|
+
*/
|
|
89
|
+
declare function acceptCHHeader(): string;
|
|
90
|
+
/**
|
|
91
|
+
* Generate the `Critical-CH` header value for hints needed on the very first
|
|
92
|
+
* request (triggers a browser retry if missing).
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* import { ClientHints } from '@czap/edge';
|
|
97
|
+
*
|
|
98
|
+
* const response = new Response('OK', {
|
|
99
|
+
* headers: {
|
|
100
|
+
* 'Accept-CH': ClientHints.acceptCHHeader(),
|
|
101
|
+
* 'Critical-CH': ClientHints.criticalCHHeader(),
|
|
102
|
+
* },
|
|
103
|
+
* });
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @returns A comma-separated list of critical Client Hint header names
|
|
107
|
+
*/
|
|
108
|
+
declare function criticalCHHeader(): string;
|
|
109
|
+
/**
|
|
110
|
+
* Client Hints namespace.
|
|
111
|
+
*
|
|
112
|
+
* Parses HTTP Client Hints headers into the same
|
|
113
|
+
* {@link ExtendedDeviceCapabilities} structure used by `@czap/detect`,
|
|
114
|
+
* enabling server-side / edge-side tier mapping without browser APIs.
|
|
115
|
+
* Also generates the `Accept-CH` and `Critical-CH` response headers needed
|
|
116
|
+
* to request hints from the browser.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* import { ClientHints } from '@czap/edge';
|
|
121
|
+
*
|
|
122
|
+
* // In an edge handler:
|
|
123
|
+
* const caps = ClientHints.parseClientHints(request.headers);
|
|
124
|
+
* const response = new Response(body, {
|
|
125
|
+
* headers: {
|
|
126
|
+
* 'Accept-CH': ClientHints.acceptCHHeader(),
|
|
127
|
+
* 'Critical-CH': ClientHints.criticalCHHeader(),
|
|
128
|
+
* },
|
|
129
|
+
* });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export declare const ClientHints: {
|
|
133
|
+
/** Parse Client Hints headers into {@link ExtendedDeviceCapabilities}. */
|
|
134
|
+
readonly parseClientHints: typeof parseClientHints;
|
|
135
|
+
/** Produce the `Accept-CH` response header value listing all useful hints. */
|
|
136
|
+
readonly acceptCHHeader: typeof acceptCHHeader;
|
|
137
|
+
/** Produce the `Critical-CH` response header value listing boot-required hints. */
|
|
138
|
+
readonly criticalCHHeader: typeof criticalCHHeader;
|
|
139
|
+
};
|
|
140
|
+
export declare namespace ClientHints {
|
|
141
|
+
/** Alias for {@link ClientHintsHeaders} — plain-object header bag shape. */
|
|
142
|
+
type Headers = ClientHintsHeaders;
|
|
143
|
+
}
|
|
144
|
+
export {};
|
|
145
|
+
//# sourceMappingURL=client-hints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-hints.d.ts","sourceRoot":"","sources":["../src/client-hints.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAW,MAAM,cAAc,CAAC;AAMxE;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,mEAAmE;IACnE,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,8CAA8C;IAC9C,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAC3C,oEAAoE;IACpE,QAAQ,CAAC,+BAA+B,CAAC,EAAE,MAAM,CAAC;IAClD,wDAAwD;IACxD,QAAQ,CAAC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IAChD,gEAAgE;IAChE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,gDAAgD;IAChD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAmHD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,iBAAS,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,GAAG,0BAA0B,CA+D3F;AAED;;;;;;;;;;;;;;GAcG;AACH,iBAAS,cAAc,IAAI,MAAM,CAEhC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,iBAAS,gBAAgB,IAAI,MAAM,CAElC;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,WAAW;IACtB,0EAA0E;;IAE1E,8EAA8E;;IAE9E,mFAAmF;;CAE3E,CAAC;AAEX,MAAM,CAAC,OAAO,WAAW,WAAW,CAAC;IACnC,4EAA4E;IAC5E,KAAY,OAAO,GAAG,kBAAkB,CAAC;CAC1C"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client Hints header parsing for edge-side device capability detection.
|
|
3
|
+
*
|
|
4
|
+
* Converts HTTP Client Hints headers into the same `ExtendedDeviceCapabilities`
|
|
5
|
+
* structure that `@czap/detect` uses, enabling reuse of the pure tier mapping
|
|
6
|
+
* functions at the edge without browser APIs.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Internal helpers
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
/**
|
|
14
|
+
* Type guard for Web API Headers objects (fetch Headers).
|
|
15
|
+
* Checks for the `get` method that distinguishes Headers from plain objects.
|
|
16
|
+
*/
|
|
17
|
+
function isWebHeaders(value) {
|
|
18
|
+
return typeof value.get === 'function';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Normalise a Headers-like input (Web API Headers or plain object) into
|
|
22
|
+
* a case-insensitive getter function.
|
|
23
|
+
*/
|
|
24
|
+
function headerGetter(headers) {
|
|
25
|
+
if (isWebHeaders(headers)) {
|
|
26
|
+
return (name) => headers.get(name) ?? undefined;
|
|
27
|
+
}
|
|
28
|
+
// Client Hints headers are always lowercase in spec, but normalise anyway
|
|
29
|
+
const lower = {};
|
|
30
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
31
|
+
if (v !== undefined)
|
|
32
|
+
lower[k.toLowerCase()] = v;
|
|
33
|
+
}
|
|
34
|
+
return (name) => lower[name.toLowerCase()];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse a numeric header, returning undefined for missing / malformed values.
|
|
38
|
+
*/
|
|
39
|
+
function parseFloat_(get, name) {
|
|
40
|
+
const raw = get(name);
|
|
41
|
+
if (raw === undefined || raw === '')
|
|
42
|
+
return undefined;
|
|
43
|
+
const n = Number.parseFloat(raw);
|
|
44
|
+
return Number.isFinite(n) ? n : undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Clamp device memory to the set of valid values browsers actually report.
|
|
48
|
+
*/
|
|
49
|
+
function clampMemory(raw) {
|
|
50
|
+
const buckets = [0.25, 0.5, 1, 2, 4, 8];
|
|
51
|
+
let closest = buckets[0];
|
|
52
|
+
for (const b of buckets) {
|
|
53
|
+
if (Math.abs(b - raw) < Math.abs(closest - raw))
|
|
54
|
+
closest = b;
|
|
55
|
+
}
|
|
56
|
+
return closest;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Crude GPU tier heuristic from User-Agent string.
|
|
60
|
+
* Without WebGL renderer info we can only make rough guesses.
|
|
61
|
+
*/
|
|
62
|
+
function gpuTierFromUA(ua) {
|
|
63
|
+
if (!ua)
|
|
64
|
+
return 1;
|
|
65
|
+
const lower = ua.toLowerCase();
|
|
66
|
+
// Very low-end indicators
|
|
67
|
+
if (/kaios|nokia|feature/i.test(lower))
|
|
68
|
+
return 0;
|
|
69
|
+
// High-end mobile
|
|
70
|
+
if (/iphone\s*1[4-9]|iphone\s*[2-9]\d/i.test(lower))
|
|
71
|
+
return 2;
|
|
72
|
+
if (/sm-s9|sm-s2[4-9]|pixel\s*[8-9]/i.test(lower))
|
|
73
|
+
return 2;
|
|
74
|
+
// Desktop with common high-end hints
|
|
75
|
+
if (/windows nt.*win64|macintosh.*mac os x 1[4-9]/i.test(lower))
|
|
76
|
+
return 2;
|
|
77
|
+
// Default to low-mid -- conservative
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Map the ECT (effective connection type) string to a normalised form.
|
|
82
|
+
*/
|
|
83
|
+
function normaliseECT(ect) {
|
|
84
|
+
if (!ect)
|
|
85
|
+
return '4g';
|
|
86
|
+
const lower = ect.toLowerCase().trim();
|
|
87
|
+
if (['slow-2g', '2g', '3g', '4g'].includes(lower))
|
|
88
|
+
return lower;
|
|
89
|
+
return '4g';
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Accept-CH / Critical-CH header values
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
const ALL_HINTS = [
|
|
95
|
+
'Sec-CH-Device-Memory',
|
|
96
|
+
'Sec-CH-DPR',
|
|
97
|
+
'Sec-CH-Viewport-Width',
|
|
98
|
+
'Sec-CH-Viewport-Height',
|
|
99
|
+
'Sec-CH-Prefers-Reduced-Motion',
|
|
100
|
+
'Sec-CH-Prefers-Color-Scheme',
|
|
101
|
+
'Sec-CH-UA-Mobile',
|
|
102
|
+
'Sec-CH-UA',
|
|
103
|
+
'Sec-CH-UA-Platform',
|
|
104
|
+
'Save-Data',
|
|
105
|
+
'Downlink',
|
|
106
|
+
'ECT',
|
|
107
|
+
'RTT',
|
|
108
|
+
];
|
|
109
|
+
const CRITICAL_HINTS = [
|
|
110
|
+
'Sec-CH-Prefers-Reduced-Motion',
|
|
111
|
+
'Sec-CH-Prefers-Color-Scheme',
|
|
112
|
+
'Sec-CH-UA-Mobile',
|
|
113
|
+
'Sec-CH-Device-Memory',
|
|
114
|
+
];
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Public API -- namespace object pattern
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
/**
|
|
119
|
+
* Parse Client Hints headers into an {@link ExtendedDeviceCapabilities} structure.
|
|
120
|
+
*
|
|
121
|
+
* For properties that cannot be determined from headers (GPU tier, WebGPU
|
|
122
|
+
* support, CPU cores), conservative defaults are used.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* import { ClientHints } from '@czap/edge';
|
|
127
|
+
*
|
|
128
|
+
* const caps = ClientHints.parseClientHints({
|
|
129
|
+
* 'sec-ch-device-memory': '8',
|
|
130
|
+
* 'sec-ch-dpr': '2',
|
|
131
|
+
* 'sec-ch-viewport-width': '1440',
|
|
132
|
+
* 'sec-ch-prefers-color-scheme': 'dark',
|
|
133
|
+
* 'sec-ch-ua-mobile': '?0',
|
|
134
|
+
* });
|
|
135
|
+
* console.log(caps.memory); // 8
|
|
136
|
+
* console.log(caps.devicePixelRatio); // 2
|
|
137
|
+
* console.log(caps.prefersColorScheme); // 'dark'
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* @param headers - Client Hints headers (plain object or Web API Headers)
|
|
141
|
+
* @returns An {@link ExtendedDeviceCapabilities} structure
|
|
142
|
+
*/
|
|
143
|
+
function parseClientHints(headers) {
|
|
144
|
+
const get = headerGetter(headers);
|
|
145
|
+
// Memory
|
|
146
|
+
const rawMemory = parseFloat_(get, 'sec-ch-device-memory');
|
|
147
|
+
const memory = rawMemory !== undefined ? clampMemory(rawMemory) : 4;
|
|
148
|
+
// DPR
|
|
149
|
+
const dpr = parseFloat_(get, 'sec-ch-dpr') ?? 1;
|
|
150
|
+
// Viewport
|
|
151
|
+
const viewportWidth = parseFloat_(get, 'sec-ch-viewport-width') ?? 1920;
|
|
152
|
+
const viewportHeight = parseFloat_(get, 'sec-ch-viewport-height') ?? 1080;
|
|
153
|
+
// Preferences
|
|
154
|
+
const reducedMotionRaw = get('sec-ch-prefers-reduced-motion');
|
|
155
|
+
const prefersReducedMotion = reducedMotionRaw === 'reduce' || reducedMotionRaw === '"reduce"';
|
|
156
|
+
const colorSchemeRaw = get('sec-ch-prefers-color-scheme');
|
|
157
|
+
const prefersColorScheme = colorSchemeRaw === 'dark' || colorSchemeRaw === '"dark"' ? 'dark' : 'light';
|
|
158
|
+
// Touch (mobile hint)
|
|
159
|
+
const mobileRaw = get('sec-ch-ua-mobile');
|
|
160
|
+
const touchPrimary = mobileRaw === '?1' || mobileRaw === 'true';
|
|
161
|
+
// Save-Data
|
|
162
|
+
const saveDataRaw = get('save-data');
|
|
163
|
+
const saveData = saveDataRaw === 'on' || saveDataRaw === '1' || saveDataRaw === 'true';
|
|
164
|
+
// Network
|
|
165
|
+
const downlink = parseFloat_(get, 'downlink') ?? 10;
|
|
166
|
+
const ect = normaliseECT(get('ect'));
|
|
167
|
+
// GPU tier heuristic from UA
|
|
168
|
+
const gpu = gpuTierFromUA(get('user-agent'));
|
|
169
|
+
return {
|
|
170
|
+
// Base DeviceCapabilities
|
|
171
|
+
gpu,
|
|
172
|
+
cores: 4, // Conservative default -- not available via Client Hints
|
|
173
|
+
memory,
|
|
174
|
+
webgpu: false, // Cannot determine from headers
|
|
175
|
+
touchPrimary,
|
|
176
|
+
prefersReducedMotion,
|
|
177
|
+
prefersColorScheme,
|
|
178
|
+
viewportWidth,
|
|
179
|
+
viewportHeight,
|
|
180
|
+
devicePixelRatio: dpr,
|
|
181
|
+
connection: {
|
|
182
|
+
effectiveType: ect,
|
|
183
|
+
downlink,
|
|
184
|
+
saveData,
|
|
185
|
+
},
|
|
186
|
+
// Extended properties -- conservative defaults for edge
|
|
187
|
+
prefersContrast: 'no-preference',
|
|
188
|
+
forcedColors: false,
|
|
189
|
+
prefersReducedTransparency: false,
|
|
190
|
+
dynamicRange: 'standard',
|
|
191
|
+
colorGamut: 'srgb',
|
|
192
|
+
updateRate: 'fast',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Generate the `Accept-CH` header value for requesting all useful Client Hints
|
|
197
|
+
* on subsequent requests.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* import { ClientHints } from '@czap/edge';
|
|
202
|
+
*
|
|
203
|
+
* const response = new Response('OK', {
|
|
204
|
+
* headers: { 'Accept-CH': ClientHints.acceptCHHeader() },
|
|
205
|
+
* });
|
|
206
|
+
* ```
|
|
207
|
+
*
|
|
208
|
+
* @returns A comma-separated list of Client Hint header names
|
|
209
|
+
*/
|
|
210
|
+
function acceptCHHeader() {
|
|
211
|
+
return ALL_HINTS.join(', ');
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Generate the `Critical-CH` header value for hints needed on the very first
|
|
215
|
+
* request (triggers a browser retry if missing).
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* import { ClientHints } from '@czap/edge';
|
|
220
|
+
*
|
|
221
|
+
* const response = new Response('OK', {
|
|
222
|
+
* headers: {
|
|
223
|
+
* 'Accept-CH': ClientHints.acceptCHHeader(),
|
|
224
|
+
* 'Critical-CH': ClientHints.criticalCHHeader(),
|
|
225
|
+
* },
|
|
226
|
+
* });
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* @returns A comma-separated list of critical Client Hint header names
|
|
230
|
+
*/
|
|
231
|
+
function criticalCHHeader() {
|
|
232
|
+
return CRITICAL_HINTS.join(', ');
|
|
233
|
+
}
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Namespace export
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
/**
|
|
238
|
+
* Client Hints namespace.
|
|
239
|
+
*
|
|
240
|
+
* Parses HTTP Client Hints headers into the same
|
|
241
|
+
* {@link ExtendedDeviceCapabilities} structure used by `@czap/detect`,
|
|
242
|
+
* enabling server-side / edge-side tier mapping without browser APIs.
|
|
243
|
+
* Also generates the `Accept-CH` and `Critical-CH` response headers needed
|
|
244
|
+
* to request hints from the browser.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* import { ClientHints } from '@czap/edge';
|
|
249
|
+
*
|
|
250
|
+
* // In an edge handler:
|
|
251
|
+
* const caps = ClientHints.parseClientHints(request.headers);
|
|
252
|
+
* const response = new Response(body, {
|
|
253
|
+
* headers: {
|
|
254
|
+
* 'Accept-CH': ClientHints.acceptCHHeader(),
|
|
255
|
+
* 'Critical-CH': ClientHints.criticalCHHeader(),
|
|
256
|
+
* },
|
|
257
|
+
* });
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
export const ClientHints = {
|
|
261
|
+
/** Parse Client Hints headers into {@link ExtendedDeviceCapabilities}. */
|
|
262
|
+
parseClientHints,
|
|
263
|
+
/** Produce the `Accept-CH` response header value listing all useful hints. */
|
|
264
|
+
acceptCHHeader,
|
|
265
|
+
/** Produce the `Critical-CH` response header value listing boot-required hints. */
|
|
266
|
+
criticalCHHeader,
|
|
267
|
+
};
|
|
268
|
+
//# sourceMappingURL=client-hints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-hints.js","sourceRoot":"","sources":["../src/client-hints.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-side tier detection -- wraps the pure tier mapping functions from
|
|
3
|
+
* `@czap/detect` for use with HTTP Client Hints headers at the edge.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import type { CapLevel } from '@czap/core';
|
|
8
|
+
import type { DesignTier, MotionTier } from '@czap/detect';
|
|
9
|
+
import type { ClientHintsHeaders } from './client-hints.js';
|
|
10
|
+
/**
|
|
11
|
+
* Outcome of an edge-side tier detection sweep.
|
|
12
|
+
*
|
|
13
|
+
* All three fields use the same branded tier types as the client runtime,
|
|
14
|
+
* so downstream boundary evaluation and output gating reuse the exact
|
|
15
|
+
* code paths from `@czap/detect`.
|
|
16
|
+
*/
|
|
17
|
+
export interface EdgeTierResult {
|
|
18
|
+
/** Highest {@link CapLevel} the device qualifies for. */
|
|
19
|
+
readonly capLevel: CapLevel;
|
|
20
|
+
/** Motion complexity tier permitted for this device. */
|
|
21
|
+
readonly motionTier: MotionTier;
|
|
22
|
+
/** Visual fidelity tier permitted for this device. */
|
|
23
|
+
readonly designTier: DesignTier;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Detect capability tiers from HTTP headers using Client Hints parsing
|
|
27
|
+
* and the same pure tier mapping functions used on the client.
|
|
28
|
+
*/
|
|
29
|
+
declare function detectTier(headers: Headers | ClientHintsHeaders): EdgeTierResult;
|
|
30
|
+
/**
|
|
31
|
+
* Generate HTML data attribute string for injection into the `<html>` element.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```
|
|
35
|
+
* tierDataAttributes(result)
|
|
36
|
+
* // => 'data-czap-cap="reactive" data-czap-motion="animations" data-czap-design="enhanced"'
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function tierDataAttributes(result: EdgeTierResult): string;
|
|
40
|
+
/**
|
|
41
|
+
* Edge tier detection namespace.
|
|
42
|
+
*
|
|
43
|
+
* Pairs {@link ClientHints.parseClientHints} with the pure tier-mapping
|
|
44
|
+
* functions from `@czap/detect` so the edge and the browser produce the
|
|
45
|
+
* same `capLevel`/`motionTier`/`designTier` triple for a given device.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { EdgeTier } from '@czap/edge';
|
|
50
|
+
*
|
|
51
|
+
* const result = EdgeTier.detectTier(request.headers);
|
|
52
|
+
* const html = `<html ${EdgeTier.tierDataAttributes(result)}>`;
|
|
53
|
+
* // `<html data-czap-cap="reactive" data-czap-motion="animations" data-czap-design="enhanced">`
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare const EdgeTier: {
|
|
57
|
+
/** Detect {@link EdgeTierResult} from a `Headers`-like bag. */
|
|
58
|
+
readonly detectTier: typeof detectTier;
|
|
59
|
+
/** Render an `EdgeTierResult` into `data-czap-*` attributes for the root HTML element. */
|
|
60
|
+
readonly tierDataAttributes: typeof tierDataAttributes;
|
|
61
|
+
};
|
|
62
|
+
export declare namespace EdgeTier {
|
|
63
|
+
/** Alias for {@link EdgeTierResult}. */
|
|
64
|
+
type Result = EdgeTierResult;
|
|
65
|
+
}
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=edge-tier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-tier.d.ts","sourceRoot":"","sources":["../src/edge-tier.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAM5D;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC;AAMD;;;GAGG;AACH,iBAAS,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,kBAAkB,GAAG,cAAc,CAMzE;AAED;;;;;;;;GAQG;AACH,iBAAS,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAE1D;AAMD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,QAAQ;IACnB,+DAA+D;;IAE/D,0FAA0F;;CAElF,CAAC;AAEX,MAAM,CAAC,OAAO,WAAW,QAAQ,CAAC;IAChC,wCAAwC;IACxC,KAAY,MAAM,GAAG,cAAc,CAAC;CACrC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-side tier detection -- wraps the pure tier mapping functions from
|
|
3
|
+
* `@czap/detect` for use with HTTP Client Hints headers at the edge.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { tierFromCapabilities, motionTierFromCapabilities, designTierFromCapabilities } from '@czap/detect';
|
|
8
|
+
import { ClientHints } from './client-hints.js';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Public API
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/**
|
|
13
|
+
* Detect capability tiers from HTTP headers using Client Hints parsing
|
|
14
|
+
* and the same pure tier mapping functions used on the client.
|
|
15
|
+
*/
|
|
16
|
+
function detectTier(headers) {
|
|
17
|
+
const caps = ClientHints.parseClientHints(headers);
|
|
18
|
+
const capLevel = tierFromCapabilities(caps);
|
|
19
|
+
const motionTier = motionTierFromCapabilities(caps);
|
|
20
|
+
const designTier = designTierFromCapabilities(caps);
|
|
21
|
+
return { capLevel, motionTier, designTier };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Generate HTML data attribute string for injection into the `<html>` element.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```
|
|
28
|
+
* tierDataAttributes(result)
|
|
29
|
+
* // => 'data-czap-cap="reactive" data-czap-motion="animations" data-czap-design="enhanced"'
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function tierDataAttributes(result) {
|
|
33
|
+
return `data-czap-cap="${result.capLevel}" data-czap-motion="${result.motionTier}" data-czap-design="${result.designTier}"`;
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Namespace export
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
/**
|
|
39
|
+
* Edge tier detection namespace.
|
|
40
|
+
*
|
|
41
|
+
* Pairs {@link ClientHints.parseClientHints} with the pure tier-mapping
|
|
42
|
+
* functions from `@czap/detect` so the edge and the browser produce the
|
|
43
|
+
* same `capLevel`/`motionTier`/`designTier` triple for a given device.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* import { EdgeTier } from '@czap/edge';
|
|
48
|
+
*
|
|
49
|
+
* const result = EdgeTier.detectTier(request.headers);
|
|
50
|
+
* const html = `<html ${EdgeTier.tierDataAttributes(result)}>`;
|
|
51
|
+
* // `<html data-czap-cap="reactive" data-czap-motion="animations" data-czap-design="enhanced">`
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export const EdgeTier = {
|
|
55
|
+
/** Detect {@link EdgeTierResult} from a `Headers`-like bag. */
|
|
56
|
+
detectTier,
|
|
57
|
+
/** Render an `EdgeTierResult` into `data-czap-*` attributes for the root HTML element. */
|
|
58
|
+
tierDataAttributes,
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=edge-tier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-tier.js","sourceRoot":"","sources":["../src/edge-tier.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE5G,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAuBhD,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;GAGG;AACH,SAAS,UAAU,CAAC,OAAqC;IACvD,MAAM,IAAI,GAAG,WAAW,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,0BAA0B,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,0BAA0B,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,kBAAkB,CAAC,MAAsB;IAChD,OAAO,kBAAkB,MAAM,CAAC,QAAQ,uBAAuB,MAAM,CAAC,UAAU,uBAAuB,MAAM,CAAC,UAAU,GAAG,CAAC;AAC9H,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,+DAA+D;IAC/D,UAAU;IACV,0FAA0F;IAC1F,kBAAkB;CACV,CAAC"}
|