@ressjs/platform 0.6.0-experimental.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.
Files changed (46) hide show
  1. package/README.md +95 -0
  2. package/dist/client.d.ts +32 -0
  3. package/dist/client.js +59 -0
  4. package/dist/compat.d.ts +9 -0
  5. package/dist/compat.js +13 -0
  6. package/dist/detect/layers.d.ts +40 -0
  7. package/dist/detect/layers.js +34 -0
  8. package/dist/detect/merge.d.ts +30 -0
  9. package/dist/detect/merge.js +50 -0
  10. package/dist/detect/override.d.ts +9 -0
  11. package/dist/detect/override.js +33 -0
  12. package/dist/detect/pipeline.d.ts +10 -0
  13. package/dist/detect/pipeline.js +75 -0
  14. package/dist/index.d.ts +26 -0
  15. package/dist/index.js +51 -0
  16. package/dist/model/defaults.d.ts +8 -0
  17. package/dist/model/defaults.js +25 -0
  18. package/dist/model/known.d.ts +31 -0
  19. package/dist/model/known.js +9 -0
  20. package/dist/model/types.d.ts +95 -0
  21. package/dist/model/types.js +2 -0
  22. package/dist/predicates.d.ts +9 -0
  23. package/dist/predicates.js +15 -0
  24. package/dist/registry/create.d.ts +17 -0
  25. package/dist/registry/create.js +87 -0
  26. package/dist/registry/defaults.d.ts +23 -0
  27. package/dist/registry/defaults.js +63 -0
  28. package/dist/registry/index.d.ts +11 -0
  29. package/dist/registry/index.js +20 -0
  30. package/dist/registry/parse.d.ts +14 -0
  31. package/dist/registry/parse.js +89 -0
  32. package/dist/registry/types.d.ts +91 -0
  33. package/dist/registry/types.js +2 -0
  34. package/dist/rules/apply.d.ts +13 -0
  35. package/dist/rules/apply.js +40 -0
  36. package/dist/rules/edge.d.ts +44 -0
  37. package/dist/rules/edge.js +66 -0
  38. package/dist/rules/headers.d.ts +48 -0
  39. package/dist/rules/headers.js +157 -0
  40. package/dist/rules/request-headers.d.ts +61 -0
  41. package/dist/rules/request-headers.js +113 -0
  42. package/dist/rules/user-agent.d.ts +25 -0
  43. package/dist/rules/user-agent.js +212 -0
  44. package/dist/serialize.d.ts +12 -0
  45. package/dist/serialize.js +23 -0
  46. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @ressjs/platform
2
+
3
+ Responde una sola pregunta: **qué es el cliente que está del otro lado**.
4
+
5
+ La responden igual el router, el puente del WebView, el cliente y cualquier
6
+ consumidor externo. Por eso vive en su propio paquete y no depende de nada.
7
+
8
+ ```ts
9
+ import { detect } from '@ressjs/platform'
10
+
11
+ const platform = detect({ userAgent, headers })
12
+ // { webview: false, os: 'tizen', device: 'tv', ... }
13
+ ```
14
+
15
+ ## El modelo
16
+
17
+ Tres ejes ortogonales. Cualquier combinación es expresable.
18
+
19
+ | Eje | Valores | Qué describe |
20
+ |---|---|---|
21
+ | `context` | `webview` | la página está incrustada dentro de una app; su ausencia es un navegador |
22
+ | `os` | `ios` `android` `tizen` `webos` `windows` `macos` `linux` | sistema operativo |
23
+ | `device` | `phone` `tablet` `tv` `desktop` | forma del aparato y modo de operación |
24
+
25
+ Los datos de la app que embebe la página —cuál es, en qué versión, sobre qué
26
+ versión del sistema— llegan en `nativeApp` y **nunca** eligen qué archivo servir.
27
+
28
+ ## Cómo se decide
29
+
30
+ Seis capas, de lo que el cliente afirma a lo que se adivina. Cada una aporta
31
+ sólo los campos que puede determinar, y un campo ya resuelto no se sobrescribe.
32
+ Ninguna capa devuelve un resultado completo ni corta la evaluación: por eso
33
+ ninguna puede volver inalcanzable a la siguiente.
34
+
35
+ 1. `override` — forzado para depurar, apagado salvo que se habilite
36
+ 2. `explicit-headers` — el contrato v2
37
+ 3. `legacy-headers` — el contrato v1 de los clientes ya publicados
38
+ 4. `client-hints` — lo que el navegador manda por su cuenta
39
+ 5. `user-agent` — la tabla de reglas
40
+ 6. `default` — lo que quede sin resolver
41
+
42
+ Cada campo del resultado declara de qué capa salió y con cuánta confianza:
43
+
44
+ ```ts
45
+ platform.detection.fields.device
46
+ // { layer: 'user-agent', confidence: 'high', rule: 'ua:android-tv' }
47
+ ```
48
+
49
+ Un `webview: false` que nadie afirmó se distingue así de uno detectado.
50
+
51
+ ## Cabeceras
52
+
53
+ | Cabecera | Aporta |
54
+ |---|---|
55
+ | `x-ressjs-webview` | `webview` |
56
+ | `x-ressjs-os` | `os` |
57
+ | `x-ressjs-device` | `device` |
58
+ | `x-ressjs-app-id` | `nativeApp.id`, e implica `webview: true` |
59
+ | `x-ressjs-app-version` | `nativeApp.version` |
60
+ | `x-ressjs-os-version` | `nativeApp.osVersion` |
61
+ | `x-ressjs-capabilities` | `capabilities`, separadas por comas |
62
+
63
+ Las del contrato anterior —`x-ressjs-platform`, `x-webview`, `x-rn-platform`,
64
+ `x-ressjs-version`— se siguen aceptando y se traducen.
65
+
66
+ ## Agregar una plataforma
67
+
68
+ Es un dato, no un cambio de código:
69
+
70
+ ```ts
71
+ const registry = createPlatformRegistry({
72
+ values: [{ token: 'kaios', axis: 'os' }],
73
+ detect: [
74
+ {
75
+ name: 'ua:kaios',
76
+ test: /KAIOS/i,
77
+ set: { os: 'kaios', device: 'phone' },
78
+ confidence: 'high',
79
+ priority: 150,
80
+ },
81
+ ],
82
+ })
83
+ ```
84
+
85
+ A partir de ahí `index.kaios.scss` es una variante válida.
86
+
87
+ ## Desde el navegador
88
+
89
+ ```ts
90
+ import { getPlatform } from '@ressjs/platform/client'
91
+ ```
92
+
93
+ Lee lo que el servidor serializó en `window.__RESS_PLATFORM__`, de modo que
94
+ cliente y servidor coinciden exactamente. Sólo detecta por su cuenta si no hay
95
+ nada serializado.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * La plataforma, del lado del navegador.
3
+ *
4
+ * Lo normal es que el servidor ya la haya resuelto y serializado en el HTML: el
5
+ * cliente la lee de ahí en vez de volver a detectarla, y así el marcado que
6
+ * hidrata coincide exactamente con el que se envió. Detectar de nuevo abriría la
7
+ * puerta a que servidor y cliente discrepen, que es la clase de desajuste que
8
+ * rompe la hidratación de React.
9
+ */
10
+ import type { PlatformInfo } from './model/types';
11
+ declare global {
12
+ interface Window {
13
+ __RESS_PLATFORM__?: PlatformInfo;
14
+ }
15
+ }
16
+ /**
17
+ * Qué plataforma es esta.
18
+ *
19
+ * Sólo detecta por su cuenta si el servidor no dejó nada, que es lo que pasa en
20
+ * una página estática servida desde un CDN.
21
+ */
22
+ export declare function getPlatform(): PlatformInfo;
23
+ /**
24
+ * Detección desde el navegador, con las señales que sólo existen acá.
25
+ *
26
+ * `window.ReactNativeWebView` es una certeza y no una heurística: el puente sólo
27
+ * existe si el contenedor lo inyectó.
28
+ */
29
+ export declare function detectClient(): PlatformInfo;
30
+ export { DEFAULT_PLATFORM } from './model/defaults';
31
+ export { hasCapability, isHandheld, isTV, isWebView } from './predicates';
32
+ export type { NativeAppInfo, PlatformDevice, PlatformInfo, PlatformOS } from './model/types';
package/dist/client.js ADDED
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * La plataforma, del lado del navegador.
4
+ *
5
+ * Lo normal es que el servidor ya la haya resuelto y serializado en el HTML: el
6
+ * cliente la lee de ahí en vez de volver a detectarla, y así el marcado que
7
+ * hidrata coincide exactamente con el que se envió. Detectar de nuevo abriría la
8
+ * puerta a que servidor y cliente discrepen, que es la clase de desajuste que
9
+ * rompe la hidratación de React.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.isWebView = exports.isTV = exports.isHandheld = exports.hasCapability = exports.DEFAULT_PLATFORM = void 0;
13
+ exports.getPlatform = getPlatform;
14
+ exports.detectClient = detectClient;
15
+ const defaults_1 = require("./model/defaults");
16
+ const pipeline_1 = require("./detect/pipeline");
17
+ /**
18
+ * Qué plataforma es esta.
19
+ *
20
+ * Sólo detecta por su cuenta si el servidor no dejó nada, que es lo que pasa en
21
+ * una página estática servida desde un CDN.
22
+ */
23
+ function getPlatform() {
24
+ if (typeof window === 'undefined')
25
+ return defaults_1.DEFAULT_PLATFORM;
26
+ return window.__RESS_PLATFORM__ ?? detectClient();
27
+ }
28
+ /**
29
+ * Detección desde el navegador, con las señales que sólo existen acá.
30
+ *
31
+ * `window.ReactNativeWebView` es una certeza y no una heurística: el puente sólo
32
+ * existe si el contenedor lo inyectó.
33
+ */
34
+ function detectClient() {
35
+ if (typeof navigator === 'undefined')
36
+ return defaults_1.DEFAULT_PLATFORM;
37
+ const base = (0, pipeline_1.detect)({ userAgent: navigator.userAgent });
38
+ const inReactNativeWebView = typeof window !== 'undefined' && 'ReactNativeWebView' in window;
39
+ if (!inReactNativeWebView || base.webview)
40
+ return base;
41
+ return {
42
+ ...base,
43
+ webview: true,
44
+ detection: {
45
+ ...base.detection,
46
+ fields: {
47
+ ...base.detection.fields,
48
+ context: { layer: 'client-signals', confidence: 'exact', rule: 'ReactNativeWebView' },
49
+ },
50
+ },
51
+ };
52
+ }
53
+ var defaults_2 = require("./model/defaults");
54
+ Object.defineProperty(exports, "DEFAULT_PLATFORM", { enumerable: true, get: function () { return defaults_2.DEFAULT_PLATFORM; } });
55
+ var predicates_1 = require("./predicates");
56
+ Object.defineProperty(exports, "hasCapability", { enumerable: true, get: function () { return predicates_1.hasCapability; } });
57
+ Object.defineProperty(exports, "isHandheld", { enumerable: true, get: function () { return predicates_1.isHandheld; } });
58
+ Object.defineProperty(exports, "isTV", { enumerable: true, get: function () { return predicates_1.isTV; } });
59
+ Object.defineProperty(exports, "isWebView", { enumerable: true, get: function () { return predicates_1.isWebView; } });
@@ -0,0 +1,9 @@
1
+ import type { PlatformInfo } from './model/types';
2
+ import type { Headers } from './rules/headers';
3
+ /**
4
+ * Firma del modelo v1, conservada para no obligar a tocar a quien ya la usa.
5
+ *
6
+ * Lo nuevo se escribe contra `detect()`, que recibe un objeto y admite el
7
+ * registro del proyecto.
8
+ */
9
+ export declare function detectPlatform(userAgent: string, headers?: Headers): PlatformInfo;
package/dist/compat.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectPlatform = detectPlatform;
4
+ const pipeline_1 = require("./detect/pipeline");
5
+ /**
6
+ * Firma del modelo v1, conservada para no obligar a tocar a quien ya la usa.
7
+ *
8
+ * Lo nuevo se escribe contra `detect()`, que recibe un objeto y admite el
9
+ * registro del proyecto.
10
+ */
11
+ function detectPlatform(userAgent, headers = {}) {
12
+ return (0, pipeline_1.detect)({ userAgent, headers });
13
+ }
@@ -0,0 +1,40 @@
1
+ import type { DetectionLayerId } from '../model/types';
2
+ import type { PlatformRegistry } from '../registry/types';
3
+ import type { Headers, LayerResult } from '../rules/headers';
4
+ /** Lo que ve una capa. */
5
+ export interface DetectionInput {
6
+ userAgent?: string;
7
+ headers?: Headers;
8
+ /** Permite forzar una plataforma para reproducir un caso. Apagado por defecto. */
9
+ allowOverride?: boolean;
10
+ /**
11
+ * Si la tabla de User-Agent participa de la decisión.
12
+ *
13
+ * Se apaga cuando la respuesta no va a declarar `Vary: User-Agent`. Resolver
14
+ * por algo que no se declara es servir la variante equivocada apenas haya una
15
+ * caché intermedia: mejor no usarlo que usarlo a escondidas.
16
+ */
17
+ useUserAgent?: boolean;
18
+ registry?: PlatformRegistry;
19
+ }
20
+ export interface DetectionLayer {
21
+ id: DetectionLayerId;
22
+ read(input: DetectionInput, registry: PlatformRegistry): LayerResult;
23
+ }
24
+ /**
25
+ * Las capas, en orden de precedencia. **Este es el único lugar donde ese orden
26
+ * existe**: no hay ninguna otra parte del sistema con un criterio propio.
27
+ *
28
+ * Van de lo que el cliente afirma a lo que se adivina:
29
+ *
30
+ * 1. `override` — forzado para depurar, sólo si está habilitado
31
+ * 2. `explicit-headers` — el contrato v2, el cliente diciendo lo que es
32
+ * 3. `legacy-headers` — el contrato v1 de los clientes ya publicados
33
+ * 4. `edge` — lo que la red de distribución ya resolvió, si el proyecto la declaró
34
+ * 5. `client-hints` — lo que el navegador manda por su cuenta
35
+ * 6. `user-agent` — la tabla de reglas
36
+ *
37
+ * La capa `default` no está en la lista porque no lee nada: es lo que queda
38
+ * cuando ninguna de las anteriores resolvió un campo.
39
+ */
40
+ export declare const DETECTION_LAYERS: readonly DetectionLayer[];
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DETECTION_LAYERS = void 0;
4
+ const headers_1 = require("../rules/headers");
5
+ const edge_1 = require("../rules/edge");
6
+ const apply_1 = require("../rules/apply");
7
+ const override_1 = require("./override");
8
+ /**
9
+ * Las capas, en orden de precedencia. **Este es el único lugar donde ese orden
10
+ * existe**: no hay ninguna otra parte del sistema con un criterio propio.
11
+ *
12
+ * Van de lo que el cliente afirma a lo que se adivina:
13
+ *
14
+ * 1. `override` — forzado para depurar, sólo si está habilitado
15
+ * 2. `explicit-headers` — el contrato v2, el cliente diciendo lo que es
16
+ * 3. `legacy-headers` — el contrato v1 de los clientes ya publicados
17
+ * 4. `edge` — lo que la red de distribución ya resolvió, si el proyecto la declaró
18
+ * 5. `client-hints` — lo que el navegador manda por su cuenta
19
+ * 6. `user-agent` — la tabla de reglas
20
+ *
21
+ * La capa `default` no está en la lista porque no lee nada: es lo que queda
22
+ * cuando ninguna de las anteriores resolvió un campo.
23
+ */
24
+ exports.DETECTION_LAYERS = [
25
+ { id: 'override', read: (input) => (input.allowOverride ? (0, override_1.readOverride)(input.headers ?? {}) : {}) },
26
+ { id: 'explicit-headers', read: (input) => (0, headers_1.readExplicitHeaders)(input.headers ?? {}) },
27
+ { id: 'legacy-headers', read: (input) => (0, headers_1.readLegacyHeaders)(input.headers ?? {}) },
28
+ { id: 'edge', read: (input, registry) => (0, edge_1.readEdgeHeaders)(input.headers ?? {}, registry.edge) },
29
+ { id: 'client-hints', read: (input) => (0, headers_1.readClientHints)(input.headers ?? {}) },
30
+ {
31
+ id: 'user-agent',
32
+ read: (input, registry) => (input.useUserAgent ?? true) ? (0, apply_1.readUserAgent)(input.userAgent ?? '', registry) : {},
33
+ },
34
+ ];
@@ -0,0 +1,30 @@
1
+ import type { Axis } from '../model/known';
2
+ import type { DetectionLayerId, FieldOrigin, NativeAppInfo } from '../model/types';
3
+ import type { LayerResult } from '../rules/headers';
4
+ /**
5
+ * Lo que el pipeline va llenando: cada eje se fija una sola vez.
6
+ *
7
+ * Es un acumulador y no un `PlatformInfo` a medio construir a propósito: los
8
+ * campos que todavía nadie resolvió están ausentes, no puestos en un valor por
9
+ * defecto que después habría que distinguir del valor real.
10
+ */
11
+ export interface Accumulator {
12
+ webview?: boolean;
13
+ os?: string;
14
+ device?: string;
15
+ nativeApp?: Partial<NativeAppInfo>;
16
+ capabilities: string[];
17
+ fields: Partial<Record<Axis, FieldOrigin>>;
18
+ }
19
+ export declare function createAccumulator(): Accumulator;
20
+ /**
21
+ * Incorpora lo que aportó una capa. **El primero que fija un campo gana.**
22
+ *
23
+ * Esa regla es lo que vuelve imposible el defecto que motivaba esta spec: no hay
24
+ * ninguna capa que devuelva un resultado completo ni que corte la evaluación, así
25
+ * que ninguna puede volver inalcanzable a la siguiente. Cada una aporta lo que
26
+ * sabe y lo que no sabe lo resuelven las de abajo.
27
+ */
28
+ export declare function absorb(acc: Accumulator, layer: DetectionLayerId, result: LayerResult): void;
29
+ /** Los tres ejes ya resueltos, o `false` si alguno falta. */
30
+ export declare function isComplete(acc: Accumulator): boolean;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAccumulator = createAccumulator;
4
+ exports.absorb = absorb;
5
+ exports.isComplete = isComplete;
6
+ function createAccumulator() {
7
+ return { capabilities: [], fields: {} };
8
+ }
9
+ /**
10
+ * Incorpora lo que aportó una capa. **El primero que fija un campo gana.**
11
+ *
12
+ * Esa regla es lo que vuelve imposible el defecto que motivaba esta spec: no hay
13
+ * ninguna capa que devuelva un resultado completo ni que corte la evaluación, así
14
+ * que ninguna puede volver inalcanzable a la siguiente. Cada una aporta lo que
15
+ * sabe y lo que no sabe lo resuelven las de abajo.
16
+ */
17
+ function absorb(acc, layer, result) {
18
+ if (result.webview !== undefined && acc.webview === undefined) {
19
+ acc.webview = result.webview.value;
20
+ acc.fields.context = origin(layer, result.webview.confidence, result.webview.rule);
21
+ }
22
+ if (result.os !== undefined && acc.os === undefined) {
23
+ acc.os = result.os.value;
24
+ acc.fields.os = origin(layer, result.os.confidence, result.os.rule);
25
+ }
26
+ if (result.device !== undefined && acc.device === undefined) {
27
+ acc.device = result.device.value;
28
+ acc.fields.device = origin(layer, result.device.confidence, result.device.rule);
29
+ }
30
+ // Los metadatos se acumulan campo a campo, con la misma regla: el primero que
31
+ // lo dijo gana. Un cliente puede declarar su versión por el contrato nuevo y
32
+ // su identificador por el viejo.
33
+ if (result.nativeApp) {
34
+ const merged = { ...result.nativeApp, ...acc.nativeApp };
35
+ acc.nativeApp = Object.fromEntries(Object.entries(merged).filter(([, v]) => v !== undefined));
36
+ }
37
+ if (result.capabilities?.length) {
38
+ for (const capability of result.capabilities) {
39
+ if (!acc.capabilities.includes(capability))
40
+ acc.capabilities.push(capability);
41
+ }
42
+ }
43
+ }
44
+ function origin(layer, confidence, rule) {
45
+ return rule ? { layer, confidence, rule } : { layer, confidence };
46
+ }
47
+ /** Los tres ejes ya resueltos, o `false` si alguno falta. */
48
+ function isComplete(acc) {
49
+ return acc.webview !== undefined && acc.os !== undefined && acc.device !== undefined;
50
+ }
@@ -0,0 +1,9 @@
1
+ import { type Headers, type LayerResult } from '../rules/headers';
2
+ /**
3
+ * Forzado de plataforma para reproducir un caso sin tener el aparato a mano.
4
+ *
5
+ * Está apagado salvo que quien monta el servidor lo habilite explícitamente: es
6
+ * una cabecera que cualquiera puede mandar, y con ella se elige qué variante
7
+ * recibe. En producción eso es una superficie de ataque, no una comodidad.
8
+ */
9
+ export declare function readOverride(headers: Headers): LayerResult;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readOverride = readOverride;
4
+ const headers_1 = require("../rules/headers");
5
+ const request_headers_1 = require("../rules/request-headers");
6
+ /**
7
+ * Forzado de plataforma para reproducir un caso sin tener el aparato a mano.
8
+ *
9
+ * Está apagado salvo que quien monta el servidor lo habilite explícitamente: es
10
+ * una cabecera que cualquiera puede mandar, y con ella se elige qué variante
11
+ * recibe. En producción eso es una superficie de ataque, no una comodidad.
12
+ */
13
+ function readOverride(headers) {
14
+ const raw = (0, headers_1.header)(headers, request_headers_1.HEADER_OVERRIDE);
15
+ if (!raw)
16
+ return {};
17
+ const result = {};
18
+ for (const part of raw.split(/[,;]/)) {
19
+ const [key, value] = part.split('=').map((s) => s.trim().toLowerCase());
20
+ if (!key || !value)
21
+ continue;
22
+ if (key === 'webview') {
23
+ result.webview = { value: value !== 'false' && value !== '0', confidence: 'exact', rule: 'override' };
24
+ }
25
+ else if (key === 'os') {
26
+ result.os = { value, confidence: 'exact', rule: 'override' };
27
+ }
28
+ else if (key === 'device') {
29
+ result.device = { value, confidence: 'exact', rule: 'override' };
30
+ }
31
+ }
32
+ return result;
33
+ }
@@ -0,0 +1,10 @@
1
+ import type { PlatformInfo } from '../model/types';
2
+ import { type DetectionInput } from './layers';
3
+ /**
4
+ * Qué es el cliente que está del otro lado.
5
+ *
6
+ * Es una función pura: las mismas entradas producen siempre la misma salida, sin
7
+ * estado, sin reloj, sin red ni disco. Es lo que permite correrla igual en el
8
+ * servidor y en el navegador, y probarla sin montar nada.
9
+ */
10
+ export declare function detect(input?: DetectionInput): PlatformInfo;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detect = detect;
4
+ const defaults_1 = require("../model/defaults");
5
+ const create_1 = require("../registry/create");
6
+ const merge_1 = require("./merge");
7
+ const layers_1 = require("./layers");
8
+ /**
9
+ * Qué es el cliente que está del otro lado.
10
+ *
11
+ * Es una función pura: las mismas entradas producen siempre la misma salida, sin
12
+ * estado, sin reloj, sin red ni disco. Es lo que permite correrla igual en el
13
+ * servidor y en el navegador, y probarla sin montar nada.
14
+ */
15
+ function detect(input = {}) {
16
+ const registry = input.registry ?? create_1.defaultPlatformRegistry;
17
+ const acc = (0, merge_1.createAccumulator)();
18
+ for (const layer of layers_1.DETECTION_LAYERS) {
19
+ if ((0, merge_1.isComplete)(acc) && acc.capabilities.length > 0)
20
+ break;
21
+ (0, merge_1.absorb)(acc, layer.id, layer.read(input, registry));
22
+ }
23
+ const fields = {
24
+ context: acc.fields.context ?? defaults_1.DEFAULT_PLATFORM.detection.fields.context,
25
+ os: acc.fields.os ?? defaults_1.DEFAULT_PLATFORM.detection.fields.os,
26
+ device: acc.fields.device ?? defaults_1.DEFAULT_PLATFORM.detection.fields.device,
27
+ };
28
+ const info = {
29
+ webview: acc.webview ?? defaults_1.DEFAULT_PLATFORM.webview,
30
+ os: acc.os ?? defaults_1.DEFAULT_PLATFORM.os,
31
+ device: acc.device ?? defaults_1.DEFAULT_PLATFORM.device,
32
+ capabilities: acc.capabilities,
33
+ detection: {
34
+ source: weakestLayer(fields),
35
+ confidence: weakestConfidence(fields),
36
+ fields,
37
+ },
38
+ source: cameFromHeaders(fields) ? 'headers' : 'user-agent',
39
+ };
40
+ // Basta con que el cliente haya declarado algo: un contrato viejo puede
41
+ // aportar la versión sin poder aportar el identificador.
42
+ if (acc.nativeApp && Object.keys(acc.nativeApp).length > 0) {
43
+ info.nativeApp = { ...acc.nativeApp };
44
+ }
45
+ return info;
46
+ }
47
+ /** Orden de las capas, de la que más afirma a la que menos. */
48
+ const LAYER_ORDER = [
49
+ 'override',
50
+ 'explicit-headers',
51
+ 'legacy-headers',
52
+ 'edge',
53
+ 'client-hints',
54
+ 'user-agent',
55
+ 'client-signals',
56
+ 'default',
57
+ ];
58
+ const CONFIDENCE_ORDER = ['exact', 'high', 'medium', 'low', 'none'];
59
+ /**
60
+ * La confianza del resultado es la del eje peor resuelto.
61
+ *
62
+ * Reportar el promedio, o la del mejor, escondería justamente el campo del que
63
+ * hay que desconfiar.
64
+ */
65
+ function weakestConfidence(fields) {
66
+ return Object.values(fields).reduce((worst, f) => CONFIDENCE_ORDER.indexOf(f.confidence) > CONFIDENCE_ORDER.indexOf(worst)
67
+ ? f.confidence
68
+ : worst, 'exact');
69
+ }
70
+ function weakestLayer(fields) {
71
+ return Object.values(fields).reduce((worst, f) => LAYER_ORDER.indexOf(f.layer) > LAYER_ORDER.indexOf(worst) ? f.layer : worst, 'override');
72
+ }
73
+ function cameFromHeaders(fields) {
74
+ return Object.values(fields).some((f) => f.layer === 'explicit-headers' || f.layer === 'legacy-headers');
75
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Modelo de plataforma de ress.js.
3
+ *
4
+ * Responde una sola pregunta —qué es el cliente que está del otro lado— y la
5
+ * responde igual para todos: el router, el puente del WebView, el cliente y
6
+ * cualquier consumidor externo. Por eso vive en su propio paquete y no depende
7
+ * de nada.
8
+ */
9
+ export { detect } from './detect/pipeline';
10
+ export { detectPlatform } from './compat';
11
+ export { DETECTION_LAYERS } from './detect/layers';
12
+ export type { DetectionInput, DetectionLayer } from './detect/layers';
13
+ export { createPlatformRegistry, defaultPlatformRegistry } from './registry/create';
14
+ export { parseVariantSuffix } from './registry/parse';
15
+ export { ALL_AXES, DEFAULT_AXIS_PRECEDENCE, DEFAULT_VALUES, RETIRED_TOKENS, } from './registry/defaults';
16
+ export { DEFAULT_RULES } from './rules/user-agent';
17
+ export { EDGE_PRESETS, readEdgeHeaders } from './rules/edge';
18
+ export type { EdgeHintRule } from './rules/edge';
19
+ export { HEADER_OVERRIDE, HEADERS_CLIENT_HINTS, HEADERS_LEGACY, HEADERS_V2, PLATFORM_REQUEST_HEADERS, PLATFORM_VARY_HEADERS, HEADERS_BY_AXIS, varyHeadersForAxes, } from './rules/request-headers';
20
+ export { DEFAULT_PLATFORM } from './model/defaults';
21
+ export { hasCapability, isHandheld, isTV, isWebView } from './predicates';
22
+ export { platformScriptTag, serializePlatform } from './serialize';
23
+ export type { Axis, KnownContext, KnownDevice, KnownOS } from './model/known';
24
+ export type { Confidence, DetectionLayerId, FieldOrigin, NativeAppInfo, PlatformDevice, PlatformInfo, PlatformOS, PlatformValueMap, } from './model/types';
25
+ export type { AxisValueDef, PlatformRegistry, RegistryOptions, RetiredTokenDef, RuleEffect, UserAgentRule, VariantTag, } from './registry/types';
26
+ export type { Headers } from './rules/headers';
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * Modelo de plataforma de ress.js.
4
+ *
5
+ * Responde una sola pregunta —qué es el cliente que está del otro lado— y la
6
+ * responde igual para todos: el router, el puente del WebView, el cliente y
7
+ * cualquier consumidor externo. Por eso vive en su propio paquete y no depende
8
+ * de nada.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.serializePlatform = exports.platformScriptTag = exports.isWebView = exports.isTV = exports.isHandheld = exports.hasCapability = exports.DEFAULT_PLATFORM = exports.varyHeadersForAxes = exports.HEADERS_BY_AXIS = exports.PLATFORM_VARY_HEADERS = exports.PLATFORM_REQUEST_HEADERS = exports.HEADERS_V2 = exports.HEADERS_LEGACY = exports.HEADERS_CLIENT_HINTS = exports.HEADER_OVERRIDE = exports.readEdgeHeaders = exports.EDGE_PRESETS = exports.DEFAULT_RULES = exports.RETIRED_TOKENS = exports.DEFAULT_VALUES = exports.DEFAULT_AXIS_PRECEDENCE = exports.ALL_AXES = exports.parseVariantSuffix = exports.defaultPlatformRegistry = exports.createPlatformRegistry = exports.DETECTION_LAYERS = exports.detectPlatform = exports.detect = void 0;
12
+ var pipeline_1 = require("./detect/pipeline");
13
+ Object.defineProperty(exports, "detect", { enumerable: true, get: function () { return pipeline_1.detect; } });
14
+ var compat_1 = require("./compat");
15
+ Object.defineProperty(exports, "detectPlatform", { enumerable: true, get: function () { return compat_1.detectPlatform; } });
16
+ var layers_1 = require("./detect/layers");
17
+ Object.defineProperty(exports, "DETECTION_LAYERS", { enumerable: true, get: function () { return layers_1.DETECTION_LAYERS; } });
18
+ var create_1 = require("./registry/create");
19
+ Object.defineProperty(exports, "createPlatformRegistry", { enumerable: true, get: function () { return create_1.createPlatformRegistry; } });
20
+ Object.defineProperty(exports, "defaultPlatformRegistry", { enumerable: true, get: function () { return create_1.defaultPlatformRegistry; } });
21
+ var parse_1 = require("./registry/parse");
22
+ Object.defineProperty(exports, "parseVariantSuffix", { enumerable: true, get: function () { return parse_1.parseVariantSuffix; } });
23
+ var defaults_1 = require("./registry/defaults");
24
+ Object.defineProperty(exports, "ALL_AXES", { enumerable: true, get: function () { return defaults_1.ALL_AXES; } });
25
+ Object.defineProperty(exports, "DEFAULT_AXIS_PRECEDENCE", { enumerable: true, get: function () { return defaults_1.DEFAULT_AXIS_PRECEDENCE; } });
26
+ Object.defineProperty(exports, "DEFAULT_VALUES", { enumerable: true, get: function () { return defaults_1.DEFAULT_VALUES; } });
27
+ Object.defineProperty(exports, "RETIRED_TOKENS", { enumerable: true, get: function () { return defaults_1.RETIRED_TOKENS; } });
28
+ var user_agent_1 = require("./rules/user-agent");
29
+ Object.defineProperty(exports, "DEFAULT_RULES", { enumerable: true, get: function () { return user_agent_1.DEFAULT_RULES; } });
30
+ var edge_1 = require("./rules/edge");
31
+ Object.defineProperty(exports, "EDGE_PRESETS", { enumerable: true, get: function () { return edge_1.EDGE_PRESETS; } });
32
+ Object.defineProperty(exports, "readEdgeHeaders", { enumerable: true, get: function () { return edge_1.readEdgeHeaders; } });
33
+ var request_headers_1 = require("./rules/request-headers");
34
+ Object.defineProperty(exports, "HEADER_OVERRIDE", { enumerable: true, get: function () { return request_headers_1.HEADER_OVERRIDE; } });
35
+ Object.defineProperty(exports, "HEADERS_CLIENT_HINTS", { enumerable: true, get: function () { return request_headers_1.HEADERS_CLIENT_HINTS; } });
36
+ Object.defineProperty(exports, "HEADERS_LEGACY", { enumerable: true, get: function () { return request_headers_1.HEADERS_LEGACY; } });
37
+ Object.defineProperty(exports, "HEADERS_V2", { enumerable: true, get: function () { return request_headers_1.HEADERS_V2; } });
38
+ Object.defineProperty(exports, "PLATFORM_REQUEST_HEADERS", { enumerable: true, get: function () { return request_headers_1.PLATFORM_REQUEST_HEADERS; } });
39
+ Object.defineProperty(exports, "PLATFORM_VARY_HEADERS", { enumerable: true, get: function () { return request_headers_1.PLATFORM_VARY_HEADERS; } });
40
+ Object.defineProperty(exports, "HEADERS_BY_AXIS", { enumerable: true, get: function () { return request_headers_1.HEADERS_BY_AXIS; } });
41
+ Object.defineProperty(exports, "varyHeadersForAxes", { enumerable: true, get: function () { return request_headers_1.varyHeadersForAxes; } });
42
+ var defaults_2 = require("./model/defaults");
43
+ Object.defineProperty(exports, "DEFAULT_PLATFORM", { enumerable: true, get: function () { return defaults_2.DEFAULT_PLATFORM; } });
44
+ var predicates_1 = require("./predicates");
45
+ Object.defineProperty(exports, "hasCapability", { enumerable: true, get: function () { return predicates_1.hasCapability; } });
46
+ Object.defineProperty(exports, "isHandheld", { enumerable: true, get: function () { return predicates_1.isHandheld; } });
47
+ Object.defineProperty(exports, "isTV", { enumerable: true, get: function () { return predicates_1.isTV; } });
48
+ Object.defineProperty(exports, "isWebView", { enumerable: true, get: function () { return predicates_1.isWebView; } });
49
+ var serialize_1 = require("./serialize");
50
+ Object.defineProperty(exports, "platformScriptTag", { enumerable: true, get: function () { return serialize_1.platformScriptTag; } });
51
+ Object.defineProperty(exports, "serializePlatform", { enumerable: true, get: function () { return serialize_1.serializePlatform; } });
@@ -0,0 +1,8 @@
1
+ import type { PlatformInfo } from './types';
2
+ /**
3
+ * Lo que se responde cuando nadie dijo nada.
4
+ *
5
+ * Cada eje queda marcado con confianza `none`, que es lo que distingue este
6
+ * `webview: false` de uno que una capa afirmó.
7
+ */
8
+ export declare const DEFAULT_PLATFORM: PlatformInfo;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_PLATFORM = void 0;
4
+ /**
5
+ * Lo que se responde cuando nadie dijo nada.
6
+ *
7
+ * Cada eje queda marcado con confianza `none`, que es lo que distingue este
8
+ * `webview: false` de uno que una capa afirmó.
9
+ */
10
+ exports.DEFAULT_PLATFORM = Object.freeze({
11
+ webview: false,
12
+ os: 'unknown',
13
+ device: 'desktop',
14
+ capabilities: Object.freeze([]),
15
+ detection: Object.freeze({
16
+ source: 'default',
17
+ confidence: 'none',
18
+ fields: Object.freeze({
19
+ context: Object.freeze({ layer: 'default', confidence: 'none' }),
20
+ os: Object.freeze({ layer: 'default', confidence: 'none' }),
21
+ device: Object.freeze({ layer: 'default', confidence: 'none' }),
22
+ }),
23
+ }),
24
+ source: 'user-agent',
25
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Vocabulario que el framework conoce de fábrica.
3
+ *
4
+ * Un proyecto puede agregar valores a los ejes `os` y `device` desde su propia
5
+ * configuración; lo que no puede es agregar un eje. Agregar un valor es un dato,
6
+ * agregar un eje es un cambio de arquitectura.
7
+ */
8
+ /**
9
+ * Las tres preguntas con las que se describe un cliente.
10
+ *
11
+ * - `context`: ¿la página está incrustada dentro de una app?
12
+ * - `os`: ¿qué sistema operativo corre debajo?
13
+ * - `device`: ¿qué forma tiene el aparato y cómo se lo opera?
14
+ *
15
+ * Son ortogonales: cualquier combinación es expresable. Una WebView dentro de
16
+ * una app de Android TV es `webview` + `android` + `tv`.
17
+ */
18
+ export type Axis = 'context' | 'os' | 'device';
19
+ /**
20
+ * Único token del eje `context`.
21
+ *
22
+ * No hay un token para "navegador" porque su ausencia ya lo dice, y porque un
23
+ * token que nombra la situación normal obliga a escribirlo en todas partes.
24
+ */
25
+ export type KnownContext = 'webview';
26
+ /**
27
+ * `unknown` no es un token del registro: nadie puede escribir `index.unknown.scss`.
28
+ * Es el valor que toma `os` cuando ninguna capa de detección lo resolvió.
29
+ */
30
+ export type KnownOS = 'ios' | 'android' | 'tizen' | 'webos' | 'windows' | 'macos' | 'linux' | 'unknown';
31
+ export type KnownDevice = 'phone' | 'tablet' | 'tv' | 'desktop';