@8ux-co/eelzap 0.10.0 → 0.10.2
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 +79 -1
- package/README.md +68 -8
- package/dist/{chunk-2IUU53QA.js → chunk-4ET7VBJA.js} +230 -7
- package/dist/chunk-4ET7VBJA.js.map +1 -0
- package/dist/{chunk-QN53G5P7.js → chunk-6XHCA4U4.js} +25 -9
- package/dist/chunk-6XHCA4U4.js.map +1 -0
- package/dist/{chunk-VW4EH7NY.js → chunk-T6S3S5RH.js} +149 -81
- package/dist/chunk-T6S3S5RH.js.map +1 -0
- package/dist/index.cjs +94 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -12
- package/dist/index.d.ts +119 -12
- package/dist/index.js +94 -1
- package/dist/index.js.map +1 -1
- package/dist/next.d.ts +6 -0
- package/dist/next.js.map +1 -1
- package/dist/preview.d.ts +55 -7
- package/dist/preview.js +98 -39
- package/dist/preview.js.map +1 -1
- package/dist/react.js +3 -3
- package/dist/react.js.map +1 -1
- package/dist/{signin-2QOSWZFK.js → signin-XOGBCGBL.js} +16 -117
- package/dist/signin-XOGBCGBL.js.map +1 -0
- package/dist/suggest-KLS6B7AM.js +2583 -0
- package/dist/suggest-KLS6B7AM.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-2IUU53QA.js.map +0 -1
- package/dist/chunk-QN53G5P7.js.map +0 -1
- package/dist/chunk-VW4EH7NY.js.map +0 -1
- package/dist/signin-2QOSWZFK.js.map +0 -1
- package/dist/suggest-5RHURHED.js +0 -1256
- package/dist/suggest-5RHURHED.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,85 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [0.10.
|
|
8
|
+
## [0.10.2] - Unreleased
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Live-site «Editar» edits every simple field type on the page: options (ENUM), numbers, integers and amounts, dates and date-times, and yes/no. Each opens a small editor under the element; Enter saves, Esc cancels.
|
|
13
|
+
- Fields edited only in Zap (rich text, images, galleries, video, files) show «Este campo se edita en Zap» with «Abrir en Zap» at that field and «Comentar».
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Editors and cards keep clear of the bar wherever it sits; key caps match the suite's `Kbd`.
|
|
18
|
+
- A tab whose draft session was lost re-enters it once before reporting no editable fields.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Amounts use ISO 4217 minor units (COP in hundredths), not the locale's display digits.
|
|
23
|
+
|
|
24
|
+
## [0.10.1] - 2026-10-07
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- The site client's on-page read returns the viewer's own `email` and an `editorUrl` for the record the page resolves to («Abrir en Zap»), plus `records` naming the other records the page shows.
|
|
29
|
+
- **`webhookChanges` covers SEO, collection, schema and site events**, so a
|
|
30
|
+
site that revalidates on webhooks also refreshes when they change:
|
|
31
|
+
- `zap.seo.updated` gives the same `item` or `document` change an edit
|
|
32
|
+
does (action `updated`), one per entry or document;
|
|
33
|
+
- `zap.collection.created`, `updated` and `deleted` give a `collection`
|
|
34
|
+
change carrying `collectionKey`;
|
|
35
|
+
- `zap.schema.field_changed` (action `field_changed`) gives one
|
|
36
|
+
`collection` change per collection whose fields changed, with
|
|
37
|
+
`resourceKey` and `collectionKey` set to the collection's key, and one
|
|
38
|
+
`document` change per document whose fields changed, named by the
|
|
39
|
+
document's key. Zap now sends `collection_key` and `document_key` on
|
|
40
|
+
every schema change. An older payload without them names a collection
|
|
41
|
+
by its id (no `collectionKey`) and widens a document's fields to one
|
|
42
|
+
`site` change;
|
|
43
|
+
- `zap.site.updated` gives a `site` change.
|
|
44
|
+
- `WebhookEventType` adds `'collection'` and `'site'`, and `WebhookAction`
|
|
45
|
+
adds `'field_changed'`. The item, document and media changes keep their
|
|
46
|
+
shape.
|
|
47
|
+
- Typed `data` for these events: `WebhookSeoEventData`,
|
|
48
|
+
`WebhookCollectionEventData`, `WebhookSchemaEventData` (with optional
|
|
49
|
+
`collection_key` and `document_key`) and `WebhookSiteEventData`.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- **`canonicalUrl` takes a path or a full URL** (`SeoInput`, docs only; the
|
|
54
|
+
type is still `string | null`). A path such as `/blog/original-post` is
|
|
55
|
+
stored as sent and resolved against the site URL on delivery, so it
|
|
56
|
+
follows a change of address; a full https URL (http only on localhost) is
|
|
57
|
+
kept as sent, for content first published on another domain. Leave it
|
|
58
|
+
unset and delivery uses the record's own URL. Zap refuses `//host`,
|
|
59
|
+
`javascript:` and other schemes, whitespace and credentials with a 400
|
|
60
|
+
whose `details[].message` says why.
|
|
61
|
+
- `Seo.canonicalUrl` on delivery is always an absolute URL or null, as
|
|
62
|
+
before; a stored path arrives resolved. `VersionSeo.canonicalUrl` and the
|
|
63
|
+
SEO routes return the value as stored.
|
|
64
|
+
- The canonical is an SEO field only: it no longer changes the page Zap's
|
|
65
|
+
preview opens. Where a record lives is its preview path.
|
|
66
|
+
- **A document without a preview path is site-wide** (a header and footer
|
|
67
|
+
document, say): `Seo.canonicalUrl` and `ogUrl` are null for it unless an
|
|
68
|
+
editor set a canonical, instead of `/{documentKey}`. A document that is a
|
|
69
|
+
page keeps its preview path's URL.
|
|
70
|
+
- **Zap keeps every version.** The per-site version limit is gone, so
|
|
71
|
+
`WebhookSiteEventData.changed` on `zap.site.updated` never lists
|
|
72
|
+
`maxVersionsPerEntry`, and `itemVersions.list()` and `documentVersions.list()`
|
|
73
|
+
return the whole history. No SDK type carried the setting, so no code changes.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- `webhookChanges` returns an empty list for `zap.item.draft_updated` and
|
|
78
|
+
`zap.document.draft_updated`: saving a draft changes nothing the live site
|
|
79
|
+
serves.
|
|
80
|
+
- **The preview client pauses instead of running away.** When the tagged
|
|
81
|
+
count passes 5,000, or grows on three passes in a row that nothing on the
|
|
82
|
+
page explains, the client stops observing the page and writing values,
|
|
83
|
+
logs one warning and tells Zap's editor (`zap:paused`), which offers a
|
|
84
|
+
reload. Values carrying stega markers no longer add markers on each pass.
|
|
85
|
+
|
|
86
|
+
## [0.10.0] - 2026-10-06
|
|
9
87
|
|
|
10
88
|
The first release as **`@8ux-co/eelzap`**, which replaces
|
|
11
89
|
`@8ux-co/eelzap-api-sdk-ts` (its last release, 0.9.1, points here). One
|
package/README.md
CHANGED
|
@@ -761,20 +761,77 @@ never include comment text. Their types are `WebhookCommentCreatedData`,
|
|
|
761
761
|
|
|
762
762
|
### Cache invalidation
|
|
763
763
|
|
|
764
|
-
`webhookChanges(payload)` flattens an
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
764
|
+
`webhookChanges(payload)` flattens an event into the changes a site
|
|
765
|
+
revalidates by, `{ type, action, id, resourceKey, collectionKey?, siteKey }`:
|
|
766
|
+
|
|
767
|
+
| `type` | From | `resourceKey` | `collectionKey` |
|
|
768
|
+
| ------------ | ----------------------------------------------------------------------- | -------------------- | --------------- |
|
|
769
|
+
| `item` | `zap.item.*`, and `zap.seo.updated` on entries (action `updated`) | the entry's slug | its collection |
|
|
770
|
+
| `document` | `zap.document.*`, and `zap.seo.updated` on documents (action `updated`) | the document's key | |
|
|
771
|
+
| `document` | `zap.schema.field_changed` on a document's fields (`field_changed`) | the document's key | |
|
|
772
|
+
| `media` | `zap.media.*` | the file's id | |
|
|
773
|
+
| `collection` | `zap.collection.*` | the collection's key | the same key |
|
|
774
|
+
| `collection` | `zap.schema.field_changed` on a collection's fields (`field_changed`) | the collection's key | the same key |
|
|
775
|
+
| `site` | `zap.site.updated` | the site's key | |
|
|
776
|
+
|
|
777
|
+
One event can name several entries, documents or files: each is its own
|
|
778
|
+
change. A `collection` change means everything that shows that collection; a
|
|
779
|
+
`site` change means the whole site. Drafts (`draft_updated`), assignments,
|
|
780
|
+
comments, API keys, `ping`, and sites created or deleted give an empty list.
|
|
781
|
+
|
|
782
|
+
A `zap.schema.field_changed` sent before the event carried `collection_key`
|
|
783
|
+
and `document_key` names its owners by id only: a collection's fields give a
|
|
784
|
+
`collection` change whose `resourceKey` is the collection's id, with no
|
|
785
|
+
`collectionKey`, and a document's fields give one `site` change.
|
|
786
|
+
|
|
787
|
+
#### Subscribing a live site
|
|
788
|
+
|
|
789
|
+
Subscribe the site's webhook, narrowed to the site with `site_ids`, to:
|
|
790
|
+
|
|
791
|
+
- the `zap.item.*`, `zap.document.*` and `zap.media.*` events, except
|
|
792
|
+
`zap.item.draft_updated`, `zap.document.draft_updated`, `zap.item.assigned`
|
|
793
|
+
and `zap.document.assigned`;
|
|
794
|
+
- `zap.collection.created`, `zap.collection.updated` and
|
|
795
|
+
`zap.collection.deleted`;
|
|
796
|
+
- `zap.seo.updated`, `zap.schema.field_changed` and `zap.site.updated`.
|
|
797
|
+
|
|
798
|
+
Do not subscribe to `draft_updated` or `assigned`: a saved draft and a new
|
|
799
|
+
Responsable change nothing the site serves, and drafts save often.
|
|
800
|
+
|
|
801
|
+
#### Revalidating
|
|
802
|
+
|
|
803
|
+
A Next.js route handler for a site that serves documents at `/<key>` and
|
|
804
|
+
entries at `/<collection>/<slug>`:
|
|
768
805
|
|
|
769
806
|
```ts
|
|
807
|
+
// app/api/zap-webhook/route.ts
|
|
808
|
+
import { revalidatePath } from 'next/cache'
|
|
770
809
|
import {
|
|
771
|
-
MemoryCacheAdapter,
|
|
772
810
|
verifyWebhookSignature,
|
|
773
811
|
webhookChanges,
|
|
812
|
+
type WebhookChange,
|
|
774
813
|
type WebhookPayload,
|
|
775
814
|
} from '@8ux-co/eelzap'
|
|
776
815
|
|
|
777
|
-
|
|
816
|
+
function revalidate(change: WebhookChange) {
|
|
817
|
+
switch (change.type) {
|
|
818
|
+
case 'item':
|
|
819
|
+
revalidatePath(`/${change.collectionKey}/${change.resourceKey}`)
|
|
820
|
+
revalidatePath(`/${change.collectionKey}`) // the listing
|
|
821
|
+
return
|
|
822
|
+
case 'document':
|
|
823
|
+
revalidatePath(`/${change.resourceKey}`)
|
|
824
|
+
return
|
|
825
|
+
case 'collection':
|
|
826
|
+
// An older field change without the key names the collection by id: refresh the whole site.
|
|
827
|
+
revalidatePath(change.collectionKey ? `/${change.collectionKey}` : '/', 'layout')
|
|
828
|
+
return
|
|
829
|
+
case 'media':
|
|
830
|
+
case 'site':
|
|
831
|
+
revalidatePath('/', 'layout')
|
|
832
|
+
return
|
|
833
|
+
}
|
|
834
|
+
}
|
|
778
835
|
|
|
779
836
|
export async function POST(request: Request) {
|
|
780
837
|
const payload = await request.text()
|
|
@@ -785,13 +842,16 @@ export async function POST(request: Request) {
|
|
|
785
842
|
}
|
|
786
843
|
|
|
787
844
|
for (const change of webhookChanges(JSON.parse(payload) as WebhookPayload)) {
|
|
788
|
-
|
|
789
|
-
if (change.type === 'item') cache.delete(`${change.collectionKey}:${change.resourceKey}`)
|
|
845
|
+
revalidate(change)
|
|
790
846
|
}
|
|
791
847
|
return new Response('ok')
|
|
792
848
|
}
|
|
793
849
|
```
|
|
794
850
|
|
|
851
|
+
With `cachedFetch` and a `MemoryCacheAdapter`, do the same with
|
|
852
|
+
`cache.delete(key)` for an item or a document, and `cache.clear()` for a
|
|
853
|
+
`collection`, `media` or `site` change.
|
|
854
|
+
|
|
795
855
|
`WEBHOOK_SIGNATURE_HEADER`, `WEBHOOK_TIMESTAMP_HEADER` and
|
|
796
856
|
`WEBHOOK_TOLERANCE_SECONDS` export the header names and the default window.
|
|
797
857
|
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PENDING_STORAGE_KEY,
|
|
3
|
+
REFRESH_LIFETIME_MS,
|
|
4
|
+
SITE_CLIENT_SCOPE,
|
|
5
|
+
STATE_PREFIX,
|
|
6
|
+
__publicField
|
|
7
|
+
} from "./chunk-6XHCA4U4.js";
|
|
8
|
+
|
|
1
9
|
// src/preview/site/messages.ts
|
|
2
10
|
var plural = (count, one, other) => (count === 1 ? one : other).replace("{n}", String(count));
|
|
3
11
|
var SIGNIN_MESSAGES = {
|
|
@@ -35,6 +43,8 @@ var SIGNIN_MESSAGES = {
|
|
|
35
43
|
var SUGGEST_MESSAGES = {
|
|
36
44
|
es: {
|
|
37
45
|
toolbar: "Zap en esta p\xE1gina",
|
|
46
|
+
move: "Mover la barra",
|
|
47
|
+
moveTip: "Arrastra para mover la barra",
|
|
38
48
|
tools: "Herramienta",
|
|
39
49
|
navigate: "Navegar",
|
|
40
50
|
edit: "Editar",
|
|
@@ -45,6 +55,17 @@ var SUGGEST_MESSAGES = {
|
|
|
45
55
|
noneOpen: "No hay comentarios abiertos en esta p\xE1gina.",
|
|
46
56
|
changeRequest: "Solicitud de cambio",
|
|
47
57
|
exit: "Salir",
|
|
58
|
+
account: (name) => `${name}, cuenta`,
|
|
59
|
+
accountMenu: "Cuenta",
|
|
60
|
+
openInZap: "Abrir en Zap",
|
|
61
|
+
signOut: "Cerrar sesi\xF3n en Zap",
|
|
62
|
+
hide: "Ocultar barra",
|
|
63
|
+
show: "Mostrar barra",
|
|
64
|
+
showBar: (n) => plural(n, "Mostrar la barra de Zap, {n} abierto", "Mostrar la barra de Zap, {n} abiertos"),
|
|
65
|
+
paused: "Vista previa pausada",
|
|
66
|
+
pausedRunaway: "La p\xE1gina no dejaba de crecer, as\xED que dejamos de actualizarla.",
|
|
67
|
+
pausedCap: "La p\xE1gina tiene demasiados campos para seguirlos.",
|
|
68
|
+
reload: "Recargar",
|
|
48
69
|
newComment: "Nuevo comentario",
|
|
49
70
|
commentTitle: "Comentario",
|
|
50
71
|
selected: (n) => plural(n, "{n} elemento seleccionado", "{n} elementos seleccionados"),
|
|
@@ -69,8 +90,30 @@ var SUGGEST_MESSAGES = {
|
|
|
69
90
|
sentRequestBody: (name, proposal) => `${name ? `${name} la ver\xE1` : "La persona responsable la ver\xE1"} en Zap${proposal ? " con tu texto propuesto" : ""}. La p\xE1gina no cambia hasta que se publique.`,
|
|
70
91
|
saving: "Guardando",
|
|
71
92
|
saved: "Guardado en el borrador",
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
editInZap: "Este campo se edita en Zap",
|
|
94
|
+
/**
|
|
95
|
+
* The field named, what it is, and where it changes («Historia es texto
|
|
96
|
+
* con formato. Cámbialo en el editor de la entrada.»).
|
|
97
|
+
*/
|
|
98
|
+
editInZapBody: (label, type, document) => {
|
|
99
|
+
const where = `en el editor ${document ? "del documento" : "de la entrada"}`;
|
|
100
|
+
const kind = {
|
|
101
|
+
IMAGE: ["una imagen", "C\xE1mbiala"],
|
|
102
|
+
GALLERY: ["una galer\xEDa", "C\xE1mbiala"],
|
|
103
|
+
RICH_TEXT: ["texto con formato", "C\xE1mbialo"],
|
|
104
|
+
VIDEO: ["un video", "C\xE1mbialo"],
|
|
105
|
+
FILE: ["un archivo", "C\xE1mbialo"]
|
|
106
|
+
}[type];
|
|
107
|
+
return kind ? `${label} es ${kind[0]}. ${kind[1]} ${where}.` : `${label} se cambia ${where}.`;
|
|
108
|
+
},
|
|
109
|
+
enterSaves: "guarda",
|
|
110
|
+
escCancels: "cancela",
|
|
111
|
+
yes: "S\xED",
|
|
112
|
+
no: "No",
|
|
113
|
+
options: (label) => `Opciones de ${label}`,
|
|
114
|
+
inRecord: (name) => `en ${name}`,
|
|
115
|
+
noFields: "No hay campos para editar aqu\xED",
|
|
116
|
+
noFieldsBody: "Esta p\xE1gina no marca sus campos. \xC1brela desde la vista previa de Zap o usa Comentar.",
|
|
74
117
|
editOffBody: "Un administrador desactiv\xF3 Editar en este sitio.",
|
|
75
118
|
renewed: "Sesi\xF3n renovada",
|
|
76
119
|
renewedAs: (name) => `Sigues como ${name}`,
|
|
@@ -91,6 +134,8 @@ var SUGGEST_MESSAGES = {
|
|
|
91
134
|
},
|
|
92
135
|
en: {
|
|
93
136
|
toolbar: "Zap on this page",
|
|
137
|
+
move: "Move the bar",
|
|
138
|
+
moveTip: "Drag to move the bar",
|
|
94
139
|
tools: "Tool",
|
|
95
140
|
navigate: "Browse",
|
|
96
141
|
edit: "Edit",
|
|
@@ -101,6 +146,17 @@ var SUGGEST_MESSAGES = {
|
|
|
101
146
|
noneOpen: "No open comments on this page.",
|
|
102
147
|
changeRequest: "Change request",
|
|
103
148
|
exit: "Sign out",
|
|
149
|
+
account: (name) => `${name}, account`,
|
|
150
|
+
accountMenu: "Account",
|
|
151
|
+
openInZap: "Open in Zap",
|
|
152
|
+
signOut: "Sign out of Zap",
|
|
153
|
+
hide: "Hide the bar",
|
|
154
|
+
show: "Show the bar",
|
|
155
|
+
showBar: (n) => plural(n, "Show the Zap bar, {n} open", "Show the Zap bar, {n} open"),
|
|
156
|
+
paused: "Preview paused",
|
|
157
|
+
pausedRunaway: "The page kept growing, so we stopped updating it.",
|
|
158
|
+
pausedCap: "The page has too many fields to follow.",
|
|
159
|
+
reload: "Reload",
|
|
104
160
|
newComment: "New comment",
|
|
105
161
|
commentTitle: "Comment",
|
|
106
162
|
selected: (n) => plural(n, "{n} element selected", "{n} elements selected"),
|
|
@@ -125,8 +181,26 @@ var SUGGEST_MESSAGES = {
|
|
|
125
181
|
sentRequestBody: (name, proposal) => `${name ?? "The person responsible"} will see it in Zap${proposal ? " with your proposed text" : ""}. The page does not change until it is published.`,
|
|
126
182
|
saving: "Saving",
|
|
127
183
|
saved: "Saved to the draft",
|
|
128
|
-
|
|
129
|
-
|
|
184
|
+
editInZap: "This field is edited in Zap",
|
|
185
|
+
editInZapBody: (label, type, document) => {
|
|
186
|
+
const where = `in the ${document ? "document's" : "entry's"} editor`;
|
|
187
|
+
const kind = {
|
|
188
|
+
IMAGE: "an image",
|
|
189
|
+
GALLERY: "a gallery",
|
|
190
|
+
RICH_TEXT: "formatted text",
|
|
191
|
+
VIDEO: "a video",
|
|
192
|
+
FILE: "a file"
|
|
193
|
+
}[type];
|
|
194
|
+
return kind ? `${label} is ${kind}. Change it ${where}.` : `${label} is changed ${where}.`;
|
|
195
|
+
},
|
|
196
|
+
enterSaves: "saves",
|
|
197
|
+
escCancels: "cancels",
|
|
198
|
+
yes: "Yes",
|
|
199
|
+
no: "No",
|
|
200
|
+
options: (label) => `${label} options`,
|
|
201
|
+
inRecord: (name) => `in ${name}`,
|
|
202
|
+
noFields: "Nothing to edit here",
|
|
203
|
+
noFieldsBody: "This page does not mark its fields. Open it from the Zap preview or use Comment.",
|
|
130
204
|
editOffBody: "An administrator turned off Edit for this site.",
|
|
131
205
|
renewed: "Session renewed",
|
|
132
206
|
renewedAs: (name) => `You are still ${name}`,
|
|
@@ -147,6 +221,114 @@ var SUGGEST_MESSAGES = {
|
|
|
147
221
|
}
|
|
148
222
|
};
|
|
149
223
|
|
|
224
|
+
// src/preview/site/oauth.ts
|
|
225
|
+
function base64url(bytes) {
|
|
226
|
+
let binary = "";
|
|
227
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
228
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
229
|
+
}
|
|
230
|
+
function randomToken(win, size) {
|
|
231
|
+
return base64url(win.crypto.getRandomValues(new Uint8Array(size)));
|
|
232
|
+
}
|
|
233
|
+
function createVerifier(win) {
|
|
234
|
+
return randomToken(win, 32);
|
|
235
|
+
}
|
|
236
|
+
async function challengeFor(win, verifier) {
|
|
237
|
+
const digest = await win.crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
238
|
+
return base64url(new Uint8Array(digest));
|
|
239
|
+
}
|
|
240
|
+
function isReturnPath(value) {
|
|
241
|
+
return /^\/(?![/\\])/.test(value) && !/[\\\s]/.test(value) && value.length <= 2048;
|
|
242
|
+
}
|
|
243
|
+
function createState(win, returnPath) {
|
|
244
|
+
const nonce = randomToken(win, 16);
|
|
245
|
+
if (returnPath === void 0 || !isReturnPath(returnPath)) return `${STATE_PREFIX}${nonce}`;
|
|
246
|
+
return `${STATE_PREFIX}${nonce}.${base64url(new TextEncoder().encode(returnPath))}`;
|
|
247
|
+
}
|
|
248
|
+
function returnPathOf(state) {
|
|
249
|
+
const encoded = state.slice(STATE_PREFIX.length).split(".")[1];
|
|
250
|
+
if (!encoded) return null;
|
|
251
|
+
try {
|
|
252
|
+
const binary = atob(encoded.replace(/-/g, "+").replace(/_/g, "/"));
|
|
253
|
+
const path = new TextDecoder().decode(Uint8Array.from(binary, (c) => c.charCodeAt(0)));
|
|
254
|
+
return isReturnPath(path) ? path : null;
|
|
255
|
+
} catch {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function authorizeUrl(ctx, state, challenge) {
|
|
260
|
+
const url = new URL("/oauth/authorize", ctx.authOrigin);
|
|
261
|
+
url.search = new URLSearchParams({
|
|
262
|
+
response_type: "code",
|
|
263
|
+
client_id: ctx.clientId,
|
|
264
|
+
redirect_uri: ctx.redirectUri,
|
|
265
|
+
code_challenge: challenge,
|
|
266
|
+
code_challenge_method: "S256",
|
|
267
|
+
state,
|
|
268
|
+
scope: SITE_CLIENT_SCOPE
|
|
269
|
+
}).toString();
|
|
270
|
+
return url.toString();
|
|
271
|
+
}
|
|
272
|
+
function checkCallback(callback, pending, authOrigin) {
|
|
273
|
+
if (!pending || !callback || callback.state !== pending.state) return "state";
|
|
274
|
+
if (callback.iss !== authOrigin) return "iss";
|
|
275
|
+
if (typeof callback.code !== "string" || !callback.code || callback.code.length > 512) {
|
|
276
|
+
return "code";
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
var ExchangeError = class extends Error {
|
|
281
|
+
constructor(status) {
|
|
282
|
+
super(`token exchange failed (${status})`);
|
|
283
|
+
__publicField(this, "status", status);
|
|
284
|
+
this.name = "ExchangeError";
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
async function exchangeCode(ctx, code, verifier, now = Date.now()) {
|
|
288
|
+
const response = await ctx.win.fetch(`${ctx.authOrigin}/api/oauth/token`, {
|
|
289
|
+
method: "POST",
|
|
290
|
+
credentials: "omit",
|
|
291
|
+
body: new URLSearchParams({
|
|
292
|
+
grant_type: "authorization_code",
|
|
293
|
+
code,
|
|
294
|
+
client_id: ctx.clientId,
|
|
295
|
+
redirect_uri: ctx.redirectUri,
|
|
296
|
+
code_verifier: verifier
|
|
297
|
+
})
|
|
298
|
+
});
|
|
299
|
+
if (!response.ok) throw new ExchangeError(response.status);
|
|
300
|
+
return readTokenAnswer(response, ctx.siteId, now);
|
|
301
|
+
}
|
|
302
|
+
async function readTokenAnswer(response, siteId, now = Date.now()) {
|
|
303
|
+
const body = await response.json().catch(() => null);
|
|
304
|
+
const token = body?.access_token;
|
|
305
|
+
const expiresIn = body?.expires_in;
|
|
306
|
+
if (typeof token !== "string" || !/^eel_at_[A-Za-z0-9_-]{8,200}$/.test(token) || typeof expiresIn !== "number" || !(expiresIn > 0)) {
|
|
307
|
+
throw new ExchangeError(502);
|
|
308
|
+
}
|
|
309
|
+
const refresh = body?.refresh_token;
|
|
310
|
+
const withRefresh = typeof refresh === "string" && /^eel_rt_[A-Za-z0-9_-]{8,200}$/.test(refresh) ? { refresh, rexp: now + REFRESH_LIFETIME_MS } : {};
|
|
311
|
+
return { token, exp: now + Math.min(expiresIn, 3600) * 1e3, site: siteId, ...withRefresh };
|
|
312
|
+
}
|
|
313
|
+
function savePending(win, pending) {
|
|
314
|
+
try {
|
|
315
|
+
win.sessionStorage.setItem(PENDING_STORAGE_KEY, JSON.stringify(pending));
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function takePending(win) {
|
|
320
|
+
try {
|
|
321
|
+
const raw = win.sessionStorage.getItem(PENDING_STORAGE_KEY);
|
|
322
|
+
win.sessionStorage.removeItem(PENDING_STORAGE_KEY);
|
|
323
|
+
const value = raw ? JSON.parse(raw) : null;
|
|
324
|
+
if (value && typeof value.state === "string" && typeof value.verifier === "string" && (value.mode === "popup" || value.mode === "redirect")) {
|
|
325
|
+
return { state: value.state, verifier: value.verifier, mode: value.mode };
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
150
332
|
// src/preview/site/ui.ts
|
|
151
333
|
var SITE_HOST_TAG = "eel-zap-site";
|
|
152
334
|
var ZAP_BOLT = '<svg viewBox="0 0 512 512"><g transform="matrix(27.611111,0,0,24.833333,-74.833333,-42)"><path d="M13,2L3,14L10.036,11.906L10,22L20.964,9.235L12.913,11.973L13,2Z" fill="#F1A10D" stroke="#F1A10D" stroke-width="0.5"/><path d="M13,2L6.518,12.953L10.036,11.906L12.913,11.973L13,2Z" fill="#FFC03A"/><path d="M10.036,11.906L10,22L20.964,9.235L12.913,11.973L10.036,11.906Z" fill="#D98A05"/><path d="M10,22L20.964,9.235L16.939,10.604L10,22Z" fill="#BF7500"/></g></svg>';
|
|
@@ -176,7 +358,20 @@ var ICONS = {
|
|
|
176
358
|
),
|
|
177
359
|
check: stroke('<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>'),
|
|
178
360
|
alert: stroke('<circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/>'),
|
|
179
|
-
field: stroke('<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>')
|
|
361
|
+
field: stroke('<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>'),
|
|
362
|
+
chevron: stroke('<path d="m6 9 6 6 6-6"/>'),
|
|
363
|
+
tick: stroke('<path d="M20 6 9 17l-5-5"/>'),
|
|
364
|
+
info: stroke('<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>'),
|
|
365
|
+
/** Lucide `eye-off`, the suite's «Ocultar». */
|
|
366
|
+
eyeOff: stroke(
|
|
367
|
+
'<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/>'
|
|
368
|
+
),
|
|
369
|
+
external: stroke(
|
|
370
|
+
'<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>'
|
|
371
|
+
),
|
|
372
|
+
grip: stroke(
|
|
373
|
+
'<circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/>'
|
|
374
|
+
)
|
|
180
375
|
};
|
|
181
376
|
function icon(doc, name, size = 15) {
|
|
182
377
|
const span = doc.createElement("span");
|
|
@@ -243,8 +438,25 @@ var TOKENS = {
|
|
|
243
438
|
};
|
|
244
439
|
var T = TOKENS;
|
|
245
440
|
var FONT = "Poppins, ui-sans-serif, system-ui, sans-serif";
|
|
441
|
+
function kbd(doc, ...keys) {
|
|
442
|
+
return keys.length === 1 ? el(doc, "kbd", { text: keys[0] }) : el(
|
|
443
|
+
doc,
|
|
444
|
+
"span",
|
|
445
|
+
{ class: "kbd-group" },
|
|
446
|
+
// A space between caps: invisible in the flex group, but the text
|
|
447
|
+
// (and a screen reader) reads «Shift Z», not «ShiftZ».
|
|
448
|
+
keys.flatMap((key, i) => [
|
|
449
|
+
...i ? [doc.createTextNode(" ")] : [],
|
|
450
|
+
el(doc, "kbd", { text: key })
|
|
451
|
+
])
|
|
452
|
+
);
|
|
453
|
+
}
|
|
246
454
|
var BASE_CSS = `
|
|
247
455
|
:host { all: initial; }
|
|
456
|
+
kbd { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; height: 20px; min-width: 20px;
|
|
457
|
+
padding: 0 6px; border: 1px solid ${T.border}; border-radius: 4px; background: ${T.bg}; color: ${T.muted};
|
|
458
|
+
font: 500 11px/12px ${FONT}; user-select: none; -webkit-user-select: none; pointer-events: none; }
|
|
459
|
+
.kbd-group { display: inline-flex; align-items: center; gap: 2px; }
|
|
248
460
|
* { box-sizing: border-box; }
|
|
249
461
|
.layer { position: fixed; inset: 0; pointer-events: none; font-family: ${FONT}; color: ${T.fg};
|
|
250
462
|
font-size: 13px; line-height: 1.45; -webkit-font-smoothing: antialiased; }
|
|
@@ -274,10 +486,21 @@ p { margin: 0; }
|
|
|
274
486
|
export {
|
|
275
487
|
SIGNIN_MESSAGES,
|
|
276
488
|
SUGGEST_MESSAGES,
|
|
489
|
+
createVerifier,
|
|
490
|
+
challengeFor,
|
|
491
|
+
createState,
|
|
492
|
+
returnPathOf,
|
|
493
|
+
authorizeUrl,
|
|
494
|
+
checkCallback,
|
|
495
|
+
exchangeCode,
|
|
496
|
+
readTokenAnswer,
|
|
497
|
+
savePending,
|
|
498
|
+
takePending,
|
|
277
499
|
icon,
|
|
278
500
|
el,
|
|
279
501
|
mountHost,
|
|
280
502
|
initials,
|
|
281
|
-
TOKENS
|
|
503
|
+
TOKENS,
|
|
504
|
+
kbd
|
|
282
505
|
};
|
|
283
|
-
//# sourceMappingURL=chunk-
|
|
506
|
+
//# sourceMappingURL=chunk-4ET7VBJA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/preview/site/messages.ts","../src/preview/site/oauth.ts","../src/preview/site/ui.ts"],"sourcesContent":["/**\n * The site tools' own words («Editar» and «Comentar», zap-cms-v2 §3.4), Spanish and English (Spanish by default, as\n * the suite; `<html lang=\"en…\">` or `initZap({ locale: 'en' })` picks\n * English). Two objects, one per chunk, so each chunk carries only its own\n * copy. Plain strings and functions: the core has no i18n runtime, and these\n * never reach the server. Copy rules (`.docs/ui-patterns/copy.md`): no\n * ellipses, em dashes, middots or parentheses.\n */\n\ntype Locale = 'es' | 'en'\n\nconst plural = (count: number, one: string, other: string) =>\n (count === 1 ? one : other).replace('{n}', String(count))\n\nexport const SIGNIN_MESSAGES = {\n es: {\n launcher: 'Editar o comentar',\n hintBefore: 'Pulsa',\n hintAfter: 'para editar o comentar',\n title: 'Edita o comenta esta página',\n body: 'Escribe sobre el texto de la página o deja un comentario. Lo que edites queda en el borrador y se publica desde Zap.',\n signIn: 'Entrar con Eel',\n note: 'Solo para personas con Zap en este sitio. Los visitantes no ven nada de esto.',\n waiting: 'Esperando a Eel',\n waitingHelp: 'Termina en la ventana de Eel. Si la cerraste, vuelve a intentarlo.',\n retry: 'Intentar de nuevo',\n close: 'Cerrar',\n expired: 'Tu sesión terminó. Entra de nuevo para seguir.',\n failed: 'No pudimos completar la entrada. Intenta de nuevo.',\n },\n en: {\n launcher: 'Edit or comment',\n hintBefore: 'Press',\n hintAfter: 'to edit or comment',\n title: 'Edit or comment on this page',\n body: 'Type over the text on the page or leave a comment. What you edit goes to the draft and is published from Zap.',\n signIn: 'Sign in with Eel',\n note: 'Only for people with Zap on this site. Visitors see none of this.',\n waiting: 'Waiting for Eel',\n waitingHelp: 'Finish in the Eel window. If you closed it, try again.',\n retry: 'Try again',\n close: 'Close',\n expired: 'Your session ended. Sign in again to keep going.',\n failed: 'We could not finish signing you in. Try again.',\n },\n} satisfies Record<Locale, Record<string, string>>\n\nexport const SUGGEST_MESSAGES = {\n es: {\n toolbar: 'Zap en esta página',\n move: 'Mover la barra',\n moveTip: 'Arrastra para mover la barra',\n tools: 'Herramienta',\n navigate: 'Navegar',\n edit: 'Editar',\n comment: 'Comentar',\n editOff: 'Desactivado por un administrador',\n open: (n: number) => plural(n, '{n} abierto', '{n} abiertos'),\n openTitle: 'Comentarios abiertos en esta página',\n noneOpen: 'No hay comentarios abiertos en esta página.',\n changeRequest: 'Solicitud de cambio',\n exit: 'Salir',\n account: (name: string) => `${name}, cuenta`,\n accountMenu: 'Cuenta',\n openInZap: 'Abrir en Zap',\n signOut: 'Cerrar sesión en Zap',\n hide: 'Ocultar barra',\n show: 'Mostrar barra',\n showBar: (n: number) =>\n plural(n, 'Mostrar la barra de Zap, {n} abierto', 'Mostrar la barra de Zap, {n} abiertos'),\n paused: 'Vista previa pausada',\n pausedRunaway: 'La página no dejaba de crecer, así que dejamos de actualizarla.',\n pausedCap: 'La página tiene demasiados campos para seguirlos.',\n reload: 'Recargar',\n newComment: 'Nuevo comentario',\n commentTitle: 'Comentario',\n selected: (n: number) => plural(n, '{n} elemento seleccionado', '{n} elementos seleccionados'),\n clearSelection: 'Cancelar',\n commentLabel: 'Comentario',\n commentPlaceholder: 'Escribe tu comentario',\n requestChange: 'Solicitar cambio',\n requestHelp: 'Queda abierta hasta que alguien la resuelva o aplique el texto propuesto.',\n recipient: 'La recibe la persona responsable en Zap.',\n propose: 'Proponer texto',\n proposalFor: 'Texto propuesto para',\n removeProposal: 'Quitar texto propuesto',\n now: 'Ahora:',\n proposal: 'Texto propuesto',\n cancel: 'Cancelar',\n close: 'Cerrar',\n sending: 'Enviando',\n save: 'Guardar',\n sentComment: 'Comentario enviado',\n sentCommentBody: 'Tu equipo lo verá en Zap.',\n sentRequest: 'Solicitud de cambio enviada',\n sentRequestBody: (name: string | null, proposal: boolean) =>\n `${name ? `${name} la verá` : 'La persona responsable la verá'} en Zap${\n proposal ? ' con tu texto propuesto' : ''\n }. La página no cambia hasta que se publique.`,\n saving: 'Guardando',\n saved: 'Guardado en el borrador',\n editInZap: 'Este campo se edita en Zap',\n /**\n * The field named, what it is, and where it changes («Historia es texto\n * con formato. Cámbialo en el editor de la entrada.»).\n */\n editInZapBody: (label: string, type: string, document: boolean) => {\n const where = `en el editor ${document ? 'del documento' : 'de la entrada'}`\n const kind = (\n {\n IMAGE: ['una imagen', 'Cámbiala'],\n GALLERY: ['una galería', 'Cámbiala'],\n RICH_TEXT: ['texto con formato', 'Cámbialo'],\n VIDEO: ['un video', 'Cámbialo'],\n FILE: ['un archivo', 'Cámbialo'],\n } as Record<string, [string, string]>\n )[type]\n return kind ? `${label} es ${kind[0]}. ${kind[1]} ${where}.` : `${label} se cambia ${where}.`\n },\n enterSaves: 'guarda',\n escCancels: 'cancela',\n yes: 'Sí',\n no: 'No',\n options: (label: string) => `Opciones de ${label}`,\n inRecord: (name: string) => `en ${name}`,\n noFields: 'No hay campos para editar aquí',\n noFieldsBody:\n 'Esta página no marca sus campos. Ábrela desde la vista previa de Zap o usa Comentar.',\n editOffBody: 'Un administrador desactivó Editar en este sitio.',\n renewed: 'Sesión renovada',\n renewedAs: (name: string) => `Sigues como ${name}`,\n renewedBody: 'Sigues donde ibas.',\n dismiss: 'Cerrar aviso',\n someone: 'Alguien',\n refusedTitle: 'No puedes editar ni comentar en este sitio',\n refusedBody: 'Tu cuenta no tiene acceso a Zap aquí, o el sitio dejó de permitirlo.',\n rateLimited: (n: number) =>\n plural(\n n,\n 'Enviaste muchos seguidos. Intenta de nuevo en {n} segundo.',\n 'Enviaste muchos seguidos. Intenta de nuevo en {n} segundos.',\n ),\n failed: 'No se pudo enviar. Revisa el texto e intenta de nuevo.',\n saveFailed: 'No se pudo guardar. El texto volvió a como estaba.',\n noRecord: 'Esta parte de la página no es contenido de Zap.',\n invalidNumber: 'Escribe un número.',\n },\n en: {\n toolbar: 'Zap on this page',\n move: 'Move the bar',\n moveTip: 'Drag to move the bar',\n tools: 'Tool',\n navigate: 'Browse',\n edit: 'Edit',\n comment: 'Comment',\n editOff: 'Turned off by an administrator',\n open: (n: number) => plural(n, '{n} open', '{n} open'),\n openTitle: 'Open comments on this page',\n noneOpen: 'No open comments on this page.',\n changeRequest: 'Change request',\n exit: 'Sign out',\n account: (name: string) => `${name}, account`,\n accountMenu: 'Account',\n openInZap: 'Open in Zap',\n signOut: 'Sign out of Zap',\n hide: 'Hide the bar',\n show: 'Show the bar',\n showBar: (n: number) => plural(n, 'Show the Zap bar, {n} open', 'Show the Zap bar, {n} open'),\n paused: 'Preview paused',\n pausedRunaway: 'The page kept growing, so we stopped updating it.',\n pausedCap: 'The page has too many fields to follow.',\n reload: 'Reload',\n newComment: 'New comment',\n commentTitle: 'Comment',\n selected: (n: number) => plural(n, '{n} element selected', '{n} elements selected'),\n clearSelection: 'Cancel',\n commentLabel: 'Comment',\n commentPlaceholder: 'Write your comment',\n requestChange: 'Request a change',\n requestHelp: 'It stays open until someone resolves it or applies the proposed text.',\n recipient: 'The person responsible in Zap receives it.',\n propose: 'Propose text',\n proposalFor: 'Proposed text for',\n removeProposal: 'Remove proposed text',\n now: 'Now:',\n proposal: 'Proposed text',\n cancel: 'Cancel',\n close: 'Close',\n sending: 'Sending',\n save: 'Save',\n sentComment: 'Comment sent',\n sentCommentBody: 'Your team will see it in Zap.',\n sentRequest: 'Change request sent',\n sentRequestBody: (name: string | null, proposal: boolean) =>\n `${name ?? 'The person responsible'} will see it in Zap${\n proposal ? ' with your proposed text' : ''\n }. The page does not change until it is published.`,\n saving: 'Saving',\n saved: 'Saved to the draft',\n editInZap: 'This field is edited in Zap',\n editInZapBody: (label: string, type: string, document: boolean) => {\n const where = `in the ${document ? \"document's\" : \"entry's\"} editor`\n const kind = (\n {\n IMAGE: 'an image',\n GALLERY: 'a gallery',\n RICH_TEXT: 'formatted text',\n VIDEO: 'a video',\n FILE: 'a file',\n } as Record<string, string>\n )[type]\n return kind ? `${label} is ${kind}. Change it ${where}.` : `${label} is changed ${where}.`\n },\n enterSaves: 'saves',\n escCancels: 'cancels',\n yes: 'Yes',\n no: 'No',\n options: (label: string) => `${label} options`,\n inRecord: (name: string) => `in ${name}`,\n noFields: 'Nothing to edit here',\n noFieldsBody:\n 'This page does not mark its fields. Open it from the Zap preview or use Comment.',\n editOffBody: 'An administrator turned off Edit for this site.',\n renewed: 'Session renewed',\n renewedAs: (name: string) => `You are still ${name}`,\n renewedBody: 'Pick up where you left off.',\n dismiss: 'Dismiss',\n someone: 'Someone',\n refusedTitle: 'You cannot edit or comment on this site',\n refusedBody: 'Your account has no Zap access here, or the site stopped allowing it.',\n rateLimited: (n: number) =>\n plural(\n n,\n 'You sent many in a row. Try again in {n} second.',\n 'You sent many in a row. Try again in {n} seconds.',\n ),\n failed: 'Could not send. Check the text and try again.',\n saveFailed: 'Could not save. The text is back to what it was.',\n noRecord: 'This part of the page is not Zap content.',\n invalidNumber: 'Enter a number.',\n },\n}\n\nexport type SuggestMessages = (typeof SUGGEST_MESSAGES)['es']\nexport type SigninMessages = (typeof SIGNIN_MESSAGES)['es']\n","import {\n PENDING_STORAGE_KEY,\n SITE_CLIENT_SCOPE,\n STATE_PREFIX,\n type CallbackParams,\n type SiteContext,\n} from './config'\nimport { REFRESH_LIFETIME_MS, type SiteToken } from './token'\n\n/**\n * The site client's side of OAuth 2.1 with Nest (ADR 024, ADR 041): a public\n * client, PKCE S256, `state`, and an `iss` check (RFC 9207). Pure functions\n * plus the one `fetch`, so every refusal is a unit spec.\n *\n * - `state` is `zap1.<128 random bits>` for the popup, and\n * `zap1.<random>.<return path>` for the full-page redirect (spec §3.4 step 4:\n * \"the return path in `state`\"). The callback must bring back EXACTLY the\n * state this tab minted; the return path is honoured only as a same-origin\n * path.\n * - `iss` must equal the compiled-in issuer exactly.\n * - The code is exchanged once, with the verifier only this tab holds, as a\n * form POST with no custom header (no preflight; Nest answers CORS to the\n * client's own redirect origins only) and no cookies.\n * - Nest answers an hour of access and, since ADR 041's amendment of\n * 2026-10-06, a short rotating refresh token (eight hours, redeemable only\n * inside the remembered consent and from this origin). Both are kept in\n * `sessionStorage` only (`token.ts`); `session.ts` spends the refresh.\n */\n\nexport interface PendingSignIn {\n state: string\n verifier: string\n mode: 'popup' | 'redirect'\n}\n\nexport type CallbackProblem = 'state' | 'iss' | 'code'\n\nfunction base64url(bytes: Uint8Array): string {\n let binary = ''\n for (const byte of bytes) binary += String.fromCharCode(byte)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction randomToken(win: Window, size: number): string {\n return base64url(win.crypto.getRandomValues(new Uint8Array(size)))\n}\n\n/** A 43-character verifier (32 random bytes), RFC 7636 §4.1. */\nexport function createVerifier(win: Window): string {\n return randomToken(win, 32)\n}\n\n/** `BASE64URL(SHA256(verifier))`, RFC 7636 §4.2. */\nexport async function challengeFor(win: Window, verifier: string): Promise<string> {\n const digest = await win.crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier))\n return base64url(new Uint8Array(digest))\n}\n\n/** A same-origin path: one leading slash, no scheme, no `//host`, no backslash tricks. */\nexport function isReturnPath(value: string): boolean {\n return /^\\/(?![/\\\\])/.test(value) && !/[\\\\\\s]/.test(value) && value.length <= 2048\n}\n\nexport function createState(win: Window, returnPath?: string): string {\n const nonce = randomToken(win, 16)\n if (returnPath === undefined || !isReturnPath(returnPath)) return `${STATE_PREFIX}${nonce}`\n return `${STATE_PREFIX}${nonce}.${base64url(new TextEncoder().encode(returnPath))}`\n}\n\n/** The return path a redirect `state` carries, or null. */\nexport function returnPathOf(state: string): string | null {\n const encoded = state.slice(STATE_PREFIX.length).split('.')[1]\n if (!encoded) return null\n try {\n const binary = atob(encoded.replace(/-/g, '+').replace(/_/g, '/'))\n const path = new TextDecoder().decode(Uint8Array.from(binary, (c) => c.charCodeAt(0)))\n return isReturnPath(path) ? path : null\n } catch {\n return null\n }\n}\n\nexport function authorizeUrl(\n ctx: Pick<SiteContext, 'authOrigin' | 'clientId' | 'redirectUri'>,\n state: string,\n challenge: string,\n): string {\n const url = new URL('/oauth/authorize', ctx.authOrigin)\n url.search = new URLSearchParams({\n response_type: 'code',\n client_id: ctx.clientId,\n redirect_uri: ctx.redirectUri,\n code_challenge: challenge,\n code_challenge_method: 'S256',\n state,\n scope: SITE_CLIENT_SCOPE,\n }).toString()\n return url.toString()\n}\n\n/**\n * Null when the callback answers `pending`; otherwise what is wrong with it.\n * Exact comparisons only: a callback for another sign-in (another tab, an\n * attacker's code pushed at us) has another state; a code from another\n * issuer has another `iss`.\n */\nexport function checkCallback(\n callback: Partial<CallbackParams> | null | undefined,\n pending: PendingSignIn | null,\n authOrigin: string,\n): CallbackProblem | null {\n if (!pending || !callback || callback.state !== pending.state) return 'state'\n if (callback.iss !== authOrigin) return 'iss'\n if (typeof callback.code !== 'string' || !callback.code || callback.code.length > 512) {\n return 'code'\n }\n return null\n}\n\nexport class ExchangeError extends Error {\n constructor(readonly status: number) {\n super(`token exchange failed (${status})`)\n this.name = 'ExchangeError'\n }\n}\n\n/** Trade the code for the access token (and its refresh token). Never sends cookies. */\nexport async function exchangeCode(\n ctx: Pick<SiteContext, 'win' | 'authOrigin' | 'clientId' | 'redirectUri' | 'siteId'>,\n code: string,\n verifier: string,\n now = Date.now(),\n): Promise<SiteToken> {\n const response = await ctx.win.fetch(`${ctx.authOrigin}/api/oauth/token`, {\n method: 'POST',\n credentials: 'omit',\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n client_id: ctx.clientId,\n redirect_uri: ctx.redirectUri,\n code_verifier: verifier,\n }),\n })\n if (!response.ok) throw new ExchangeError(response.status)\n return readTokenAnswer(response, ctx.siteId, now)\n}\n\n/**\n * The token endpoint's answer, checked: an `eel_at_` token and its lifetime\n * (at most the hour the ADR promises, whatever the answer claims), and the\n * `eel_rt_` refresh token when there is one (eight hours at most).\n */\nexport async function readTokenAnswer(\n response: Response,\n siteId: string,\n now = Date.now(),\n): Promise<SiteToken> {\n const body = (await response.json().catch(() => null)) as {\n access_token?: unknown\n expires_in?: unknown\n refresh_token?: unknown\n } | null\n const token = body?.access_token\n const expiresIn = body?.expires_in\n if (\n typeof token !== 'string' ||\n !/^eel_at_[A-Za-z0-9_-]{8,200}$/.test(token) ||\n typeof expiresIn !== 'number' ||\n !(expiresIn > 0)\n ) {\n throw new ExchangeError(502)\n }\n const refresh = body?.refresh_token\n const withRefresh =\n typeof refresh === 'string' && /^eel_rt_[A-Za-z0-9_-]{8,200}$/.test(refresh)\n ? { refresh, rexp: now + REFRESH_LIFETIME_MS }\n : {}\n return { token, exp: now + Math.min(expiresIn, 3600) * 1000, site: siteId, ...withRefresh }\n}\n\n// ── The pending full-page sign-in (sessionStorage, this tab only) ───────────\n\nexport function savePending(win: Window, pending: PendingSignIn): void {\n try {\n win.sessionStorage.setItem(PENDING_STORAGE_KEY, JSON.stringify(pending))\n } catch {\n // Blocked storage: the redirect cannot be finished; the popup path still works.\n }\n}\n\n/** Read AND forget: a pending sign-in is good for one callback. */\nexport function takePending(win: Window): PendingSignIn | null {\n try {\n const raw = win.sessionStorage.getItem(PENDING_STORAGE_KEY)\n win.sessionStorage.removeItem(PENDING_STORAGE_KEY)\n const value = raw ? (JSON.parse(raw) as Partial<PendingSignIn>) : null\n if (\n value &&\n typeof value.state === 'string' &&\n typeof value.verifier === 'string' &&\n (value.mode === 'popup' || value.mode === 'redirect')\n ) {\n return { state: value.state, verifier: value.verifier, mode: value.mode }\n }\n } catch {\n // Nothing usable.\n }\n return null\n}\n","/**\n * The site tools' building blocks, shared by the `signin` and `suggest`\n * chunks (zap-cms-v2 §2.7, §7.3):\n *\n * - **A closed Shadow DOM** on a custom element appended to `<html>`, styled\n * `all: initial` with `!important` inline, so the site's CSS cannot restyle\n * it, it cannot restyle the site, and site scripts get no `shadowRoot`.\n * - **Eel tokens inlined** (`TOKENS`): the boards' colours, radii and Poppins\n * with system fallbacks; nothing is fetched.\n * - **Text only.** `el()` takes text as text: strings become text nodes, never\n * markup. The single `innerHTML` is `icon()`, which reads a constant SVG from\n * `ICONS` by name; no network string ever reaches it.\n */\n\nexport const SITE_HOST_TAG = 'eel-zap-site'\n\nconst ZAP_BOLT =\n '<svg viewBox=\"0 0 512 512\"><g transform=\"matrix(27.611111,0,0,24.833333,-74.833333,-42)\"><path d=\"M13,2L3,14L10.036,11.906L10,22L20.964,9.235L12.913,11.973L13,2Z\" fill=\"#F1A10D\" stroke=\"#F1A10D\" stroke-width=\"0.5\"/><path d=\"M13,2L6.518,12.953L10.036,11.906L12.913,11.973L13,2Z\" fill=\"#FFC03A\"/><path d=\"M10.036,11.906L10,22L20.964,9.235L12.913,11.973L10.036,11.906Z\" fill=\"#D98A05\"/><path d=\"M10,22L20.964,9.235L16.939,10.604L10,22Z\" fill=\"#BF7500\"/></g></svg>'\n\nconst EEL_MARK =\n '<svg viewBox=\"0 0 3000 3000\" fill=\"currentColor\"><g transform=\"matrix(1.01772,-0.122618,0.122163,1.013948,-241.899218,117.348723)\"><g transform=\"matrix(1.192409,0,0,1.144788,-367.077609,-248.030951)\"><path d=\"M2789.774,1920.921C2642.929,2454.806 2170.478,2846 1611,2846C934.352,2846 385,2273.795 385,1569C385,864.205 934.352,292 1611,292C1849.726,292 2072.607,363.224 2261.109,486.366C2132.562,436.833 1991.613,409.484 1843.83,409.484C1242.576,409.484 754.435,862.18 754.435,1419.774C754.435,1977.368 1242.576,2430.064 1843.83,2430.064C2248.492,2430.064 2601.918,2225.006 2789.774,1920.921Z\"/></g><g transform=\"matrix(1.434867,0,-0,0.933341,-1232.183743,68.591241)\"><path d=\"M2196.648,369.772C2557.019,431.746 2832,753.678 2832,1141C2832,1572.598 2490.559,1923 2070,1923C1813.847,1923 1587.045,1793.01 1448.867,1593.857C1561.131,1687.675 1700.963,1743.353 1852.482,1743.353C2222.082,1743.353 2522.15,1412.061 2522.15,1004.003C2522.15,734.845 2391.596,499.086 2196.648,369.772Z\"/></g></g></svg>'\n\nconst stroke = (paths: string) =>\n `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${paths}</svg>`\n\n/** Constant markup only. Adding an icon means adding a literal here. */\nexport const ICONS = {\n zap: ZAP_BOLT,\n eel: EEL_MARK,\n x: stroke('<path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/>'),\n lock: stroke(\n '<rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/>',\n ),\n pointer: stroke(\n '<path d=\"M12.586 12.586 19 19\"/><path d=\"M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z\"/>',\n ),\n pencil: stroke(\n '<path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\"/>',\n ),\n commentPlus: stroke(\n '<path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/><path d=\"M12 7v6\"/><path d=\"M9 10h6\"/>',\n ),\n thread: stroke(\n '<path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/><path d=\"M13 8H7\"/><path d=\"M17 12H7\"/>',\n ),\n exit: stroke(\n '<path d=\"m16 17 5-5-5-5\"/><path d=\"M21 12H9\"/><path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/>',\n ),\n check: stroke('<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/>'),\n alert: stroke('<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4\"/><path d=\"M12 16h.01\"/>'),\n field: stroke('<path d=\"M4 7V4h16v3\"/><path d=\"M9 20h6\"/><path d=\"M12 4v16\"/>'),\n chevron: stroke('<path d=\"m6 9 6 6 6-6\"/>'),\n tick: stroke('<path d=\"M20 6 9 17l-5-5\"/>'),\n info: stroke('<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/>'),\n /** Lucide `eye-off`, the suite's «Ocultar». */\n eyeOff: stroke(\n '<path d=\"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\"/><path d=\"M14.084 14.158a3 3 0 0 1-4.242-4.242\"/><path d=\"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\"/><path d=\"m2 2 20 20\"/>',\n ),\n external: stroke(\n '<path d=\"M15 3h6v6\"/><path d=\"M10 14 21 3\"/><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/>',\n ),\n grip: stroke(\n '<circle cx=\"9\" cy=\"12\" r=\"1\"/><circle cx=\"9\" cy=\"5\" r=\"1\"/><circle cx=\"9\" cy=\"19\" r=\"1\"/><circle cx=\"15\" cy=\"12\" r=\"1\"/><circle cx=\"15\" cy=\"5\" r=\"1\"/><circle cx=\"15\" cy=\"19\" r=\"1\"/>',\n ),\n} as const\n\nexport type IconName = keyof typeof ICONS\n\nexport function icon(doc: Document, name: IconName, size = 15): HTMLElement {\n const span = doc.createElement('span')\n span.className = 'icon'\n span.style.width = `${size}px`\n span.style.height = `${size}px`\n span.setAttribute('aria-hidden', 'true')\n // A constant from ICONS, never network data (§7.3).\n span.innerHTML = ICONS[name]\n return span\n}\n\ntype Child = Node | string | number | null | undefined | false\n\nexport interface ElProps {\n class?: string\n attrs?: Record<string, string | null | undefined>\n text?: string\n on?: Partial<Record<string, (event: Event) => void>>\n}\n\n/** An element whose children are nodes or TEXT. Strings are never parsed as markup. */\nexport function el<K extends keyof HTMLElementTagNameMap>(\n doc: Document,\n tag: K,\n props: ElProps = {},\n children: Child[] = [],\n): HTMLElementTagNameMap[K] {\n const node = doc.createElement(tag)\n if (props.class) node.className = props.class\n for (const [name, value] of Object.entries(props.attrs ?? {})) {\n if (value !== null && value !== undefined) node.setAttribute(name, value)\n }\n if (props.text !== undefined) node.textContent = props.text\n for (const [type, handler] of Object.entries(props.on ?? {})) {\n if (handler) node.addEventListener(type, handler)\n }\n for (const child of children) {\n if (child === null || child === undefined || child === false) continue\n node.append(typeof child === 'string' || typeof child === 'number' ? String(child) : child)\n }\n return node\n}\n\nexport interface SiteHost {\n host: HTMLElement\n root: ShadowRoot\n destroy(): void\n}\n\n/** The closed shadow host, on `<html>` so a site that replaces `<body>` keeps it. */\nexport function mountHost(doc: Document, css: string): SiteHost {\n const host = doc.createElement(SITE_HOST_TAG)\n const style: Record<string, string> = {\n all: 'initial',\n position: 'fixed',\n inset: '0',\n 'pointer-events': 'none',\n 'z-index': '2147483647',\n display: 'block',\n }\n for (const [name, value] of Object.entries(style))\n host.style.setProperty(name, value, 'important')\n const root = host.attachShadow({ mode: 'closed' })\n const sheet = doc.createElement('style')\n sheet.textContent = BASE_CSS + css\n root.append(sheet)\n doc.documentElement.appendChild(host)\n return { host, root, destroy: () => host.remove() }\n}\n\n/** Initials for an avatar: first letters of the first two words. */\nexport function initials(name: string | null | undefined): string {\n const words = (name ?? '').trim().split(/\\s+/).filter(Boolean)\n return words\n .slice(0, 2)\n .map((word) => word.charAt(0).toUpperCase())\n .join('')\n}\n\n/** Eel tokens, inlined (boards SitioLanzador to SitioRenovacion). */\nexport const TOKENS = {\n fg: '#010313',\n muted: '#5A6C85',\n border: '#E2E8F0',\n bg: '#FFFFFF',\n subtle: '#EAEEF3',\n primary: '#287CCF',\n primaryBorder: '#4B97DE',\n dark: '#0F172B',\n gold: '#9A6200',\n goldTint: '#FDF3E1',\n goldText: '#7A4D00',\n danger: '#C62828',\n focus: 'rgba(144,161,185,0.5)',\n}\n\nconst T = TOKENS\nconst FONT = 'Poppins, ui-sans-serif, system-ui, sans-serif'\n\n/**\n * A key cap («Enter», «Shift Z»), the ONE way the site tools draw a key.\n * Mirrors `Kbd` in `packages/ui-web/src/kbd.tsx` (no React in a closed\n * shadow root): 20px tall, at least 20px wide, 0 6px, 4px radius, a border,\n * the background colour, muted 11px medium text, never selectable.\n */\nexport function kbd(doc: Document, ...keys: string[]): HTMLElement {\n // Several keys pressed together are a group, as `KbdGroup` (2px apart).\n return keys.length === 1\n ? el(doc, 'kbd', { text: keys[0] })\n : el(\n doc,\n 'span',\n { class: 'kbd-group' },\n // A space between caps: invisible in the flex group, but the text\n // (and a screen reader) reads «Shift Z», not «ShiftZ».\n keys.flatMap((key, i) => [\n ...(i ? [doc.createTextNode(' ')] : []),\n el(doc, 'kbd', { text: key }),\n ]),\n )\n}\n\nconst BASE_CSS = `\n:host { all: initial; }\nkbd { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; height: 20px; min-width: 20px;\n padding: 0 6px; border: 1px solid ${T.border}; border-radius: 4px; background: ${T.bg}; color: ${T.muted};\n font: 500 11px/12px ${FONT}; user-select: none; -webkit-user-select: none; pointer-events: none; }\n.kbd-group { display: inline-flex; align-items: center; gap: 2px; }\n* { box-sizing: border-box; }\n.layer { position: fixed; inset: 0; pointer-events: none; font-family: ${FONT}; color: ${T.fg};\n font-size: 13px; line-height: 1.45; -webkit-font-smoothing: antialiased; }\n.icon { display: inline-flex; flex-shrink: 0; }\n.icon svg { width: 100%; height: 100%; display: block; }\nbutton { font: 500 13px ${FONT}; color: inherit; cursor: pointer; border: 0; background: transparent;\n display: inline-flex; align-items: center; gap: 6px; padding: 0; margin: 0; }\nbutton:focus-visible, textarea:focus-visible, input:focus-visible { outline: none; box-shadow: 0 0 0 3px ${T.focus}; }\nbutton:disabled { opacity: .55; cursor: default; }\n.card { pointer-events: auto; position: fixed; background: ${T.bg}; border: 1px solid ${T.border};\n border-radius: 15px; box-shadow: 0 20px 25px -5px rgba(15,23,42,.12), 0 8px 10px -6px rgba(15,23,42,.08); }\n.muted { color: ${T.muted}; }\nh2 { margin: 0; font: 600 15px ${FONT}; color: ${T.fg}; display: flex; align-items: center; gap: 8px; }\np { margin: 0; }\n.iconbtn { width: 28px; height: 28px; justify-content: center; border-radius: 7px; color: ${T.muted}; }\n.iconbtn:hover { background: ${T.subtle}; }\n.btn { height: 34px; padding: 0 14px 0 11px; border-radius: 9px; white-space: nowrap; flex-shrink: 0;\n box-shadow: 0 1px 2px rgba(15,23,42,.05); }\n.btn-outline { border: 1px solid ${T.border}; background: ${T.bg}; color: ${T.fg}; }\n.btn-primary { border: 1px solid ${T.primaryBorder}; background: ${T.primary}; color: #FAFAFA; }\n.btn-dark { width: 100%; height: 38px; justify-content: center; gap: 8px; border-radius: 9px;\n background: ${T.dark}; color: #FFFFFF; }\n.error { color: ${T.danger}; font-size: 12px; }\n@media (max-width: 480px) { .card.sheet { left: 8px !important; right: 8px !important; width: auto !important; } }\n`\n"],"mappings":"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","names":[]}
|