@dataemergencia/emergency 0.0.0-stage → 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DataEmergencia
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 CHANGED
@@ -1,3 +1,311 @@
1
- # Temporary Holding Version
1
+ # @dataemergencia/emergency
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Client for the DataEmergencia emergency flow. It owns everything that happens
4
+ after the SOS trigger: the panel, the emergency lifecycle, the public realtime
5
+ updates and the geolocation fallback feedback.
6
+
7
+ The upstream DataEmergencia API is never called from the browser. The only
8
+ backend contract is the emergency BFF (`/de-bff/emergency`), which validates the
9
+ identity code, sends the WhatsApp messages and proxies the upstream state.
10
+
11
+ - Framework-agnostic TypeScript core (types, state machine, BFF client, store).
12
+ - React 19 presentation layer with accessible panel components.
13
+ - Mountable IIFE bundle for vanilla pages: no host React or build step.
14
+ - ESM/CJS bundles for React applications, with React kept external.
15
+ - No `localStorage`: the BFF is the source of truth and resume uses
16
+ `GET /current`.
17
+
18
+ ## Documentation
19
+
20
+ - [Documentation index](docs/README.md)
21
+ - [Client quickstart](docs/client/quickstart.md)
22
+ - [Vanilla, no-build guide](docs/client/vanilla.md)
23
+ - [Theming](docs/client/theming.md)
24
+ - [Troubleshooting](docs/client/troubleshooting.md)
25
+ - [Releasing](docs/release/releasing.md)
26
+
27
+ The BFF is a separate package:
28
+ [`dataemergencia/emergency-bff`](https://github.com/The-BoxHead-Guy/emergency-bff).
29
+
30
+ ## Requirements
31
+
32
+ - Node.js 22 or newer. CI and releases use the pinned LTS from `.nvmrc` (24.22.2).
33
+ - pnpm 10+ (the monorepo currently runs pnpm 12.8.1).
34
+ - Vite+ 1.4.0 for this package's build and test commands.
35
+
36
+ React 19 is a peer dependency for the ESM/CJS entry points. The IIFE entry
37
+ bundles React, so vanilla consumers need nothing.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ pnpm add @dataemergencia/emergency
43
+ ```
44
+
45
+ ## React / ESM quickstart
46
+
47
+ ```tsx
48
+ import { mount } from '@dataemergencia/emergency';
49
+ import '@dataemergencia/emergency/styles.css';
50
+
51
+ const client = mount({
52
+ root: '#emergency-root',
53
+ bffBaseUrl: 'https://api.dataemergencia.com/de-bff/emergency',
54
+ code: 'DABC12',
55
+ trigger: '#sos-button', // selector, element, or callback receiving open()
56
+ fallback: {
57
+ phone: '+57 300 123 4567',
58
+ label: 'Línea de emergencia',
59
+ },
60
+ onEvent: (event, payload) => {
61
+ console.log(event, payload);
62
+ },
63
+ });
64
+
65
+ // Later, or from your own UI:
66
+ client.open();
67
+ client.destroy();
68
+ ```
69
+
70
+ `mount()` never auto-opens the panel. The host decides when to call `open()` or
71
+ wires a trigger. `destroy()` unmounts React, removes the trigger listener,
72
+ cancels in-flight requests, disconnects Pusher and clears the store.
73
+
74
+ ## Vanilla / IIFE quickstart
75
+
76
+ ```html
77
+ <div id="emergency-root"></div>
78
+ <button id="sos">SOS</button>
79
+
80
+ <!-- Pin the version in production; see docs/client/installation.md for SRI. -->
81
+ <script src="https://cdn.jsdelivr.net/npm/@dataemergencia/emergency@0.1.0/dist/iife/emergency.iife.global.js"></script>
82
+ <script>
83
+ window.DataEmergenciaEmergency.mount({
84
+ root: '#emergency-root',
85
+ bffBaseUrl: 'https://api.dataemergencia.com/de-bff/emergency',
86
+ code: 'DABC12',
87
+ trigger: '#sos',
88
+ fallback: { phone: '+57 300 123 4567', label: 'Línea de emergencia' },
89
+ });
90
+ </script>
91
+ ```
92
+
93
+ The IIFE exposes `window.DataEmergenciaEmergency.mount`. React and ReactDOM are
94
+ included in the bundle; the host page does not need a build step.
95
+
96
+ For state-by-state review there is a lab page that imports the source through
97
+ Vite (HMR, CSS Modules) instead of the built IIFE. It drives every fixture
98
+ against the mock BFF: see
99
+ [`examples/vanilla/README.md`](examples/vanilla/README.md).
100
+
101
+ ## Mount options
102
+
103
+ | Option | Type | Notes |
104
+ | ---------------------- | --------------------------------------------------------- | ------------------------------------------------------ |
105
+ | `root` | `HTMLElement \| string` | Host container. Validated at mount. |
106
+ | `bffBaseUrl` | `string` | Full BFF route prefix, including `/de-bff/emergency`. |
107
+ | `code` | `string` | 6-character identity code supplied by the host. |
108
+ | `trigger` | `string \| HTMLElement \| (open) => void \| (() => void)` | Optional. Click opens the panel. |
109
+ | `fallback` | `{ phone, label }` | Optional. Emitted in `emergency:fallback` for host UI. |
110
+ | `theme` | `EmergencyTheme` | CSS variable overrides (see Theming). |
111
+ | `locale` | `string` | Defaults to `es`; used for time formatting. |
112
+ | `geolocationTimeoutMs` | `number` | Defaults to `8000`. |
113
+ | `requestTimeoutMs` | `number` | Defaults to `15000`. |
114
+ | `onEvent` | `(name, payload) => void` | Typed lifecycle events. |
115
+
116
+ Invalid roots or codes do not throw into the host page. `mount()` reports a
117
+ `configuration` error through `onEvent('emergency:failed', …)`, logs it, and
118
+ returns an inert client.
119
+
120
+ ## Client API
121
+
122
+ ```ts
123
+ client.open(reason?) // Opens the panel
124
+ client.close(reason?) // Closes the panel
125
+ client.ensureSession() // POST /session once; safe to repeat
126
+ client.resume() // GET /current and exposes resumeAvailable
127
+ client.activate() // Requests geolocation, then POST /activate
128
+ client.simulate() // POST /simulacrum; never creates an emergency
129
+ client.refresh() // POST /{id}/refresh
130
+ client.abort() // DELETE /{id} after confirmation
131
+ client.getState()
132
+ client.subscribe(listener)
133
+ client.on(event, handler)
134
+ client.destroy()
135
+ ```
136
+
137
+ Activation opens the panel immediately, requests geolocation and only reports
138
+ success after the BFF confirms. If geolocation is denied, unavailable, times out
139
+ or is unsupported, the client shows a dismissable error feedback card for five
140
+ seconds and never implies that an emergency was sent without a location.
141
+
142
+ Abort requires confirmation: `requestAbortConfirmation()` sets the state that
143
+ the UI uses for the "keep active / cancel emergency" step, and `abort()` acts on
144
+ it.
145
+
146
+ Simulation is disabled while a real emergency is active.
147
+
148
+ ## Events
149
+
150
+ `session:ready`, `emergency:activated`, `emergency:simulated`,
151
+ `emergency:resumed`, `emergency:updated`, `emergency:aborted`,
152
+ `emergency:failed`, `emergency:fallback`, `panel:opened`, `panel:closed`.
153
+
154
+ Events are typed in `EmergencyEventMap` (`src/core/types.ts`) and emitted by the
155
+ client's own emitter, not DOM custom events.
156
+
157
+ ## BFF contract
158
+
159
+ The client expects the emergency BFF documented in
160
+ `packages/emergency-bff`:
161
+
162
+ | Method | Route | Purpose |
163
+ | -------- | --------------- | ---------------------------------------------- |
164
+ | `GET` | `/config` | Initial CSRF token and public realtime config. |
165
+ | `POST` | `/session` | `{ code }` → session + rotated CSRF token. |
166
+ | `GET` | `/current` | Active emergency for the session, if any. |
167
+ | `POST` | `/activate` | `{ geolocation: { latitude, longitude } }`. |
168
+ | `POST` | `/simulacrum` | Test message, no emergency created. |
169
+ | `POST` | `/{id}/refresh` | Reconcile snapshot and responder statuses. |
170
+ | `DELETE` | `/{id}` | Abort and notify contacts. |
171
+
172
+ Every mutating request sends the CSRF token in `X-CSRF-Token`. The default
173
+ strategy keeps the token in memory and updates it from `/config` and `/session`.
174
+ For a Laravel-compatible `XSRF-TOKEN` cookie, pass
175
+ `createCookieCsrfStrategy()` through `createBffClient()` (advanced use).
176
+
177
+ `createBffClient()` normalizes errors into `EmergencyError` while preserving the
178
+ HTTP status, BFF error code, message and `request_id`.
179
+
180
+ ## Theming and visual contract
181
+
182
+ The panel reproduces the `bp.scaem.app` emergency surface. It is plain CSS
183
+ Modules: React and vanilla integrators do not need Tailwind, PostCSS,
184
+ FontAwesome or an icon package.
185
+
186
+ Local class names keep the original BEM names, for example
187
+ `.emergency-panel`, `.emergency-panel__header`,
188
+ `.emergency-panel__button--emergency`, `.emergency-status__row` and
189
+ `.emergency-resume-notification-overlay`. CSS Modules prefixes them at build
190
+ time (`de-emergency-emergency-panel-<hash>`), so target the stable BEM suffix in
191
+ overrides rather than the generated hash.
192
+
193
+ All themeable values are CSS custom properties in `src/styles/tokens.css`, and
194
+ every rule carries a literal fallback:
195
+
196
+ ```css
197
+ :root {
198
+ --de-emergency-red: #ff0000;
199
+ --de-emergency-red-hover: #ff3535;
200
+ --de-emergency-blue: #00adf6;
201
+ --de-emergency-blue-hover: #29bfff;
202
+ --de-emergency-primary: #007bff;
203
+ --de-emergency-text: #3d4461;
204
+ --de-emergency-white: #fff;
205
+ --de-emergency-white-soft: #f7f7f7;
206
+ --de-emergency-gray: #555;
207
+ --de-emergency-radius: 0.5rem;
208
+ --de-emergency-font: 'Poppins', ui-sans-serif, system-ui, sans-serif;
209
+ }
210
+ ```
211
+
212
+ The `theme` option maps `accent`, `accentContrast`, `surface`, `text`, `muted`
213
+ and `radius` onto `--de-emergency-red`, `--de-emergency-on-red`,
214
+ `--de-emergency-white`, `--de-emergency-text`, `--de-emergency-text-secondary`
215
+ and `--de-emergency-radius`.
216
+
217
+ The client injects the Google Fonts stylesheet for Google Sans, Montserrat and
218
+ Poppins at mount time, in both the ESM/CJS and IIFE builds, so the host does not
219
+ add font `<link>` tags. Use the `fontLoading` mount option to disable loading or
220
+ select families; see [Configuration](docs/client/configuration.md#fonts).
221
+
222
+ The IIFE bundle embeds this stylesheet in a single injected `<style>` tag and
223
+ removes it when the last mount is destroyed. Hosts using the IIFE must not load
224
+ a second copy of the CSS. ESM/CJS consumers import `styles.css` once.
225
+
226
+ ## Accessibility
227
+
228
+ The panel renders as `role="dialog"` with `aria-modal`, `aria-labelledby`, a
229
+ focus trap, Escape-to-close and focus restoration to the trigger. Confirmation
230
+ steps are explicit dialogs with cancel and confirm actions. All controls are
231
+ native buttons with visible focus, icons are inline SVG with
232
+ `aria-hidden`, and status surfaces use `role="status"` or `role="alert"`.
233
+
234
+ ## Development
235
+
236
+ ```bash
237
+ pnpm install
238
+ pnpm run check # format + lint + type checks
239
+ pnpm run lint
240
+ pnpm run fmt
241
+ pnpm run typecheck
242
+ pnpm run test
243
+ pnpm run build
244
+ pnpm run pack # npm pack --dry-run
245
+ ```
246
+
247
+ Examples and the visual lab:
248
+
249
+ ```bash
250
+ # Terminal 1: deterministic mock BFF on :8787
251
+ pnpm run example:vanilla:mock
252
+
253
+ # Terminal 2: build, copy the IIFE and serve the vanilla demo
254
+ pnpm run example:vanilla
255
+
256
+ # Or the HMR lab only (serves the package root)
257
+ pnpm run lab
258
+ ```
259
+
260
+ Build outputs:
261
+
262
+ | Path | Contents |
263
+ | -------------------------------------- | -------------------------------------------- |
264
+ | `dist/index.js` / `dist/index.cjs` | ESM/CJS bundles, React external. |
265
+ | `dist/index.d.ts` / `dist/index.d.cts` | Declarations. |
266
+ | `dist/styles.css` | Extracted CSS Modules for ESM/CJS consumers. |
267
+ | `dist/iife/emergency.iife.global.js` | IIFE with React bundled and CSS injected. |
268
+
269
+ The IIFE gzip budget is 150 KB (current build: ~115 KB).
270
+
271
+ ## Releasing
272
+
273
+ This package lives at `libs/emergency` in the DataEmergencia monorepo and is
274
+ published from the public split repository `The-BoxHead-Guy/emergency` as
275
+ `@dataemergencia/emergency` (npm org `dataemergencia`).
276
+
277
+ The full runbook is [Releasing the client](docs/release/releasing.md). The short
278
+ version:
279
+
280
+ ```bash
281
+ cd libs/emergency
282
+ pnpm bump 0.2.1 # package.json only; commit the bump
283
+ pnpm run check && pnpm run test && pnpm run build
284
+ git tag -a emergency-client-v0.2.1 -m "release: 0.2.1"
285
+ git push origin emergency-client-v0.2.1
286
+ ```
287
+
288
+ The tag push runs the split workflow, which mirrors this directory to the split
289
+ root, regenerates a standalone `pnpm-lock.yaml`, and pushes the split tag. Then
290
+ run **Publish to npm** from the split repository.
291
+
292
+ - Monorepo tags are project-scoped: `emergency-client-v0.2.1`.
293
+ - Split repository tags: `v0.2.1`.
294
+ - `scripts/split.sh <version> [--push] [--publish]` performs the same mirror
295
+ manually and is the fallback when Actions is unavailable.
296
+ - `scripts/release.sh <version> [--publish]` verifies the committed version and
297
+ the changelog entry, then runs check, tests and build through npm before
298
+ publishing.
299
+ - Publishing uses npm Trusted Publishing from the split repository; the original
300
+ `0.1.0` was published manually.
301
+
302
+ The published tarball contains `dist/`, `README.md`, `LICENSE` and
303
+ `package.json`; `dist/**/*.map` files are excluded. Source maps stay in the
304
+ repository and in the CI artifacts.
305
+
306
+ See [Releasing the client](docs/release/releasing.md) and
307
+ [CHANGELOG.md](CHANGELOG.md).
308
+
309
+ ## License
310
+
311
+ MIT