@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 +21 -0
- package/README.md +310 -2
- package/dist/iife/emergency.iife.global.js +78 -0
- package/dist/index.cjs +2605 -0
- package/dist/index.d.cts +433 -0
- package/dist/index.d.ts +433 -0
- package/dist/index.js +2557 -0
- package/dist/styles.css +1007 -0
- package/package.json +104 -4
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
|
-
#
|
|
1
|
+
# @dataemergencia/emergency
|
|
2
2
|
|
|
3
|
-
|
|
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
|