@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.
@@ -0,0 +1,433 @@
1
+ import { AxiosInstance } from "axios";
2
+ import { StoreApi } from "zustand/vanilla";
3
+ type EmergencyPhaseName = 'idle' | 'initializing' | 'ready' | 'activating' | 'simulating' | 'active' | 'refreshing' | 'aborting' | 'error';
4
+ /**
5
+ * The lifecycle phase. It is a discriminated union so consumers can switch on
6
+ * `kind` and read the data that belongs to that phase without casting.
7
+ */
8
+ type EmergencyPhase = {
9
+ kind: 'idle';
10
+ } | {
11
+ kind: 'initializing';
12
+ } | {
13
+ kind: 'ready';
14
+ } | {
15
+ kind: 'activating';
16
+ } | {
17
+ kind: 'simulating';
18
+ } | {
19
+ kind: 'active';
20
+ emergencyId: string;
21
+ simulated: boolean;
22
+ } | {
23
+ kind: 'refreshing';
24
+ emergencyId: string;
25
+ } | {
26
+ kind: 'aborting';
27
+ emergencyId: string;
28
+ } | {
29
+ kind: 'error';
30
+ error: EmergencyError;
31
+ };
32
+ type ResponderStatus = 'pending' | 'sent' | 'delivered' | 'read' | 'failed';
33
+ interface ResponderMessage {
34
+ readonly content: string;
35
+ readonly sentAt: string | null;
36
+ }
37
+ interface Responder {
38
+ readonly phone: string;
39
+ readonly name: string;
40
+ readonly status: ResponderStatus;
41
+ readonly sent: boolean;
42
+ readonly delivered: boolean;
43
+ readonly read: boolean;
44
+ readonly failed: boolean;
45
+ readonly messageId: string | null;
46
+ readonly sentAt: string | null;
47
+ readonly deliveredAt: string | null;
48
+ readonly readAt: string | null;
49
+ readonly lastMessage: ResponderMessage | null;
50
+ }
51
+ interface EmergencyFailure {
52
+ readonly phone: string;
53
+ readonly message: string;
54
+ readonly status: number | null;
55
+ }
56
+ type MessageKind = 'info' | 'success' | 'warning' | 'error';
57
+ interface Message {
58
+ readonly id: string;
59
+ readonly kind: MessageKind;
60
+ readonly text: string;
61
+ readonly createdAt: number;
62
+ }
63
+ declare const EMERGENCY_ERROR_CODES: readonly ["configuration", "network", "cancelled", "invalid_response", "invalid_transition", "destroyed", "geolocation_denied", "geolocation_unavailable", "geolocation_timeout", "geolocation_unsupported", "validation_failed", "invalid_code", "inactive_code", "unauthenticated", "csrf_failed", "cross_origin_denied", "rate_limited", "not_found", "method_not_allowed", "emergency_already_active", "emergency_forbidden", "emergency_not_found", "missing_emergency_contacts", "missing_medical_profile", "whatsapp_delivery_failed", "upstream_error", "internal_error", "emergency_error", "unknown"];
64
+ type EmergencyErrorCode = (typeof EMERGENCY_ERROR_CODES)[number];
65
+ interface EmergencyError {
66
+ readonly code: EmergencyErrorCode;
67
+ readonly message: string;
68
+ readonly status: number | null;
69
+ readonly requestId: string | null;
70
+ readonly details: unknown;
71
+ readonly cause: unknown;
72
+ }
73
+ interface EmergencyFallback {
74
+ readonly phone: string;
75
+ readonly label: string;
76
+ }
77
+ interface EmergencyTheme {
78
+ readonly accent?: string;
79
+ readonly accentContrast?: string;
80
+ readonly surface?: string;
81
+ readonly text?: string;
82
+ readonly muted?: string;
83
+ readonly radius?: string;
84
+ }
85
+ interface EmergencyFontFamilyFlags {
86
+ /** Loads the Google Sans family. Defaults to `true`. */
87
+ readonly googleSans?: boolean;
88
+ /** Loads the Montserrat family. Defaults to `true`. */
89
+ readonly montserrat?: boolean;
90
+ /** Loads the Poppins family. Defaults to `true`. */
91
+ readonly poppins?: boolean;
92
+ }
93
+ interface EmergencyFontLoading {
94
+ /** Master switch for the runtime font request. Defaults to `true`. */
95
+ readonly load?: boolean;
96
+ /** Per-family switches. Every family defaults to `true`. */
97
+ readonly fonts?: EmergencyFontFamilyFlags;
98
+ }
99
+ interface EmergencyGeolocation {
100
+ readonly latitude: number;
101
+ readonly longitude: number;
102
+ readonly accuracy?: number;
103
+ }
104
+ interface EmergencyConfig {
105
+ /** Full BFF route prefix, for example `https://example.com/de-bff/emergency`. */
106
+ readonly bffBaseUrl: string;
107
+ /** 6-character identity code supplied by the host. */
108
+ readonly code: string;
109
+ /** Optional phone number shown when geolocation or activation fails. */
110
+ readonly fallback?: EmergencyFallback;
111
+ /** Optional theme values mapped to `--de-emergency-*` CSS variables. */
112
+ readonly theme?: EmergencyTheme;
113
+ /**
114
+ * Optional runtime loading of the panel's Google Fonts. The client injects
115
+ * the stylesheet once, before the first paint. Defaults to loading every
116
+ * family.
117
+ */
118
+ readonly fontLoading?: EmergencyFontLoading;
119
+ /** BCP 47 locale used for dates and numbers. Defaults to `es`. */
120
+ readonly locale?: string;
121
+ /** Geolocation timeout in milliseconds. Defaults to 8000. */
122
+ readonly geolocationTimeoutMs?: number;
123
+ /** Request timeout in milliseconds. Defaults to 15000. */
124
+ readonly requestTimeoutMs?: number;
125
+ }
126
+ interface EmergencySnapshot {
127
+ readonly emergencyId: string;
128
+ readonly code: string | null;
129
+ readonly active: boolean;
130
+ readonly simulated: boolean;
131
+ readonly responders: readonly Responder[];
132
+ readonly createdAt: string | null;
133
+ readonly updatedAt: string | null;
134
+ readonly raw: Readonly<Record<string, unknown>>;
135
+ }
136
+ type EmergencyOperation = 'mount' | 'session' | 'resume' | 'activate' | 'simulate' | 'refresh' | 'abort' | 'geolocation' | 'realtime';
137
+ type PanelOpenReason = 'trigger' | 'activate' | 'resume' | 'manual';
138
+ type PanelCloseReason = 'escape' | 'close' | 'abort' | 'destroy' | 'manual';
139
+ interface EmergencyEventMap {
140
+ 'session:ready': {
141
+ readonly code: string;
142
+ readonly expiresIn: number;
143
+ };
144
+ 'emergency:activated': {
145
+ readonly snapshot: EmergencySnapshot;
146
+ };
147
+ 'emergency:simulated': {
148
+ readonly responders: readonly Responder[];
149
+ readonly failures: readonly EmergencyFailure[];
150
+ };
151
+ 'emergency:resumed': {
152
+ readonly snapshot: EmergencySnapshot;
153
+ };
154
+ 'emergency:updated': {
155
+ readonly snapshot: EmergencySnapshot;
156
+ };
157
+ 'emergency:aborted': {
158
+ readonly emergencyId: string;
159
+ readonly responders: readonly Responder[];
160
+ };
161
+ 'emergency:failed': {
162
+ readonly error: EmergencyError;
163
+ readonly operation: EmergencyOperation;
164
+ };
165
+ 'emergency:fallback': {
166
+ readonly error: EmergencyError;
167
+ readonly fallback: EmergencyFallback | null;
168
+ };
169
+ 'panel:opened': {
170
+ readonly reason: PanelOpenReason;
171
+ };
172
+ 'panel:closed': {
173
+ readonly reason: PanelCloseReason;
174
+ };
175
+ }
176
+ type EmergencyEventName = keyof EmergencyEventMap;
177
+ type EmergencyEventHandler<K extends EmergencyEventName> = (payload: EmergencyEventMap[K]) => void;
178
+ type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'unavailable';
179
+ interface AbortResult {
180
+ readonly ok: boolean;
181
+ readonly message: string;
182
+ readonly at: number;
183
+ }
184
+ interface SimulationResult {
185
+ readonly ok: boolean;
186
+ readonly message: string;
187
+ readonly at: number;
188
+ }
189
+ interface EmergencyClientState {
190
+ readonly config: EmergencyConfig;
191
+ readonly phase: EmergencyPhase;
192
+ readonly panelOpen: boolean;
193
+ readonly sessionReady: boolean;
194
+ readonly resumeAvailable: boolean;
195
+ readonly snapshot: EmergencySnapshot | null;
196
+ readonly responders: readonly Responder[];
197
+ readonly failures: readonly EmergencyFailure[];
198
+ readonly messages: readonly Message[];
199
+ readonly lastError: EmergencyError | null;
200
+ readonly geolocation: EmergencyGeolocation | null;
201
+ readonly geolocationError: EmergencyError | null;
202
+ readonly realtimeStatus: RealtimeStatus;
203
+ readonly abortResult: AbortResult | null;
204
+ readonly simulationResult: SimulationResult | null;
205
+ readonly abortConfirmation: boolean;
206
+ readonly simulationConfirmation: boolean;
207
+ readonly destroyed: boolean;
208
+ }
209
+ interface EmergencyClient {
210
+ open(reason?: PanelOpenReason): void;
211
+ close(reason?: PanelCloseReason): void;
212
+ destroy(): void;
213
+ getState(): EmergencyClientState;
214
+ subscribe(listener: (state: EmergencyClientState) => void): () => void;
215
+ on<K extends EmergencyEventName>(event: K, handler: EmergencyEventHandler<K>): () => void;
216
+ off<K extends EmergencyEventName>(event: K, handler: EmergencyEventHandler<K>): void;
217
+ ensureSession(): Promise<void>;
218
+ resume(): Promise<void>;
219
+ dismissResume(): void;
220
+ activate(): Promise<void>;
221
+ simulate(): Promise<void>;
222
+ refresh(): Promise<void>;
223
+ abort(): Promise<void>;
224
+ requestAbortConfirmation(): void;
225
+ cancelAbortConfirmation(): void;
226
+ requestSimulationConfirmation(): void;
227
+ cancelSimulationConfirmation(): void;
228
+ dismissFeedback(): void;
229
+ }
230
+ type EmergencyTrigger = string | HTMLElement | ((open: () => void) => void | (() => void));
231
+ interface EmergencyMountOptions extends EmergencyConfig {
232
+ readonly root: HTMLElement | string;
233
+ readonly trigger?: EmergencyTrigger;
234
+ readonly onEvent?: <K extends EmergencyEventName>(event: K, payload: EmergencyEventMap[K]) => void;
235
+ }
236
+ /**
237
+ * Mounts the emergency client into a host element. Validation failures never
238
+ * throw into the host page: they produce a typed configuration error, a console
239
+ * message, and an inert client.
240
+ */
241
+ export declare function mount(options: EmergencyMountOptions): EmergencyClient;
242
+ interface RealtimePusherConfig {
243
+ readonly key: string;
244
+ readonly cluster: string;
245
+ readonly channel_pattern: string;
246
+ }
247
+ interface RealtimePublicConfig {
248
+ readonly mode: 'public';
249
+ readonly pusher: RealtimePusherConfig;
250
+ }
251
+ interface ConfigResponse {
252
+ readonly route_prefix: string;
253
+ readonly realtime: RealtimePublicConfig;
254
+ readonly csrf_token: string;
255
+ }
256
+ interface SessionResponse {
257
+ readonly code: string;
258
+ readonly csrf_token: string;
259
+ readonly expires_in: number;
260
+ }
261
+ interface ResponderMessagePayload {
262
+ readonly content?: string | null;
263
+ readonly sent_at?: string | null;
264
+ }
265
+ interface ResponderPayload {
266
+ readonly phone: string;
267
+ readonly name: string;
268
+ readonly sent: boolean;
269
+ readonly delivered: boolean;
270
+ readonly read: boolean;
271
+ readonly failed: boolean;
272
+ readonly message_id: string | null;
273
+ readonly sent_at?: string | null;
274
+ readonly delivered_at?: string | null;
275
+ readonly read_at?: string | null;
276
+ readonly last_messages?: readonly ResponderMessagePayload[] | null;
277
+ }
278
+ interface FailurePayload {
279
+ readonly phone: string;
280
+ readonly message: string;
281
+ readonly status?: number | null;
282
+ }
283
+ interface EmergencyResultPayload {
284
+ readonly emergency_id: string | null;
285
+ readonly simulated: boolean;
286
+ readonly responders: readonly ResponderPayload[];
287
+ readonly failures: readonly FailurePayload[];
288
+ readonly partial_failure?: boolean;
289
+ readonly total_failure?: boolean;
290
+ }
291
+ type EmergencyPayload = Readonly<Record<string, unknown>>;
292
+ interface CurrentResponse {
293
+ readonly active: boolean;
294
+ readonly emergency: EmergencyPayload | null;
295
+ readonly realtime: RealtimePublicConfig;
296
+ }
297
+ interface GeolocationRequest {
298
+ readonly latitude: number;
299
+ readonly longitude: number;
300
+ readonly accuracy?: number;
301
+ }
302
+ interface ActivateRequest {
303
+ readonly geolocation?: GeolocationRequest;
304
+ }
305
+ interface RefreshResponse {
306
+ readonly emergency: EmergencyPayload;
307
+ readonly responders: readonly ResponderPayload[];
308
+ }
309
+ interface AbortResponse extends EmergencyResultPayload {
310
+ readonly deleted: boolean;
311
+ }
312
+ interface BffClient {
313
+ config(signal?: AbortSignal): Promise<ConfigResponse>;
314
+ session(code: string, signal?: AbortSignal): Promise<SessionResponse>;
315
+ current(signal?: AbortSignal): Promise<CurrentResponse>;
316
+ activate(request: ActivateRequest, signal?: AbortSignal, idempotencyKey?: string): Promise<EmergencyResultPayload>;
317
+ simulacrum(signal?: AbortSignal, idempotencyKey?: string): Promise<EmergencyResultPayload>;
318
+ refresh(emergencyId: string, signal?: AbortSignal): Promise<RefreshResponse>;
319
+ abort(emergencyId: string, signal?: AbortSignal): Promise<AbortResponse>;
320
+ destroy(): void;
321
+ }
322
+ interface CsrfStrategy {
323
+ readonly headerName: string;
324
+ read(): string | null;
325
+ }
326
+ interface MemoryCsrfStrategy extends CsrfStrategy {
327
+ setToken(token: string | null): void;
328
+ }
329
+ interface BffClientOptions {
330
+ /** Full BFF route prefix, for example `https://example.com/de-bff/emergency`. */
331
+ readonly bffBaseUrl: string;
332
+ readonly timeoutMs?: number;
333
+ readonly withCredentials?: boolean;
334
+ readonly csrf?: CsrfStrategy;
335
+ /**
336
+ * Redirects are disabled by default: the client sends the CSRF token on every
337
+ * request, and following a cross-origin redirect would hand it to another
338
+ * host.
339
+ */
340
+ readonly maxRedirects?: number;
341
+ /** Injection point for tests; the default is a configured axios instance. */
342
+ readonly axiosInstance?: AxiosInstance;
343
+ readonly generateIdempotencyKey?: () => string;
344
+ }
345
+ export declare function createMemoryCsrfStrategy(): MemoryCsrfStrategy;
346
+ /**
347
+ * Laravel-compatible adapter. The core never assumes Laravel; a host that uses
348
+ * the `XSRF-TOKEN` cookie opts in by passing this strategy.
349
+ */
350
+ export declare function createCookieCsrfStrategy(cookieName?: string): CsrfStrategy;
351
+ export declare function createBffClient(options: BffClientOptions): BffClient;
352
+ /**
353
+ * Typed, dependency-free event bus used by the controller and by `mount()`.
354
+ * DOM custom events are not used so the core stays framework agnostic.
355
+ */
356
+ export declare const EMERGENCY_EVENT_NAMES: readonly EmergencyEventName[];
357
+ declare class EmergencyEventEmitter {
358
+ private readonly handlers;
359
+ on<K extends EmergencyEventName>(event: K, handler: EmergencyEventHandler<K>): () => void;
360
+ off<K extends EmergencyEventName>(event: K, handler: EmergencyEventHandler<K>): void;
361
+ emit<K extends EmergencyEventName>(event: K, payload: EmergencyEventMap[K]): void;
362
+ clear(): void;
363
+ }
364
+ interface GeolocationProvider {
365
+ getCurrentPosition(timeoutMs: number): Promise<EmergencyGeolocation>;
366
+ }
367
+ export declare function createBrowserGeolocationProvider(): GeolocationProvider;
368
+ interface RealtimeHandlers {
369
+ readonly onEvent: (channel: string, event: string, data: unknown) => void;
370
+ readonly onStatus: (status: RealtimeStatus) => void;
371
+ }
372
+ interface RealtimeSubscription {
373
+ disconnect(): void;
374
+ }
375
+ interface RealtimeProvider {
376
+ /**
377
+ * Subscribes to the public channels for the given responder phone numbers.
378
+ * Returns `null` when the realtime configuration is unusable, in which case
379
+ * the client keeps working in pull mode.
380
+ */
381
+ subscribe(config: RealtimePublicConfig, phones: readonly string[], handlers: RealtimeHandlers): RealtimeSubscription | null;
382
+ }
383
+ export declare function createPusherRealtimeProvider(): RealtimeProvider;
384
+ type EmergencyStore = StoreApi<EmergencyClientState>;
385
+ interface EmergencyControllerDeps {
386
+ readonly config: EmergencyConfig;
387
+ readonly bff?: ReturnType<typeof createBffClient>;
388
+ readonly geolocation?: GeolocationProvider;
389
+ readonly realtime?: RealtimeProvider;
390
+ readonly events?: EmergencyEventEmitter;
391
+ }
392
+ interface EmergencyController {
393
+ readonly client: EmergencyClient;
394
+ readonly store: EmergencyStore;
395
+ readonly events: EmergencyEventEmitter;
396
+ }
397
+ export declare function createEmergencyController(deps: EmergencyControllerDeps): EmergencyController;
398
+ export declare const EMERGENCY_PHASE_NAMES: readonly EmergencyPhaseName[];
399
+ /**
400
+ * Every allowed transition. `idle` is reachable from every phase so `destroy()`
401
+ * and a hard reset always leave the store in a known state.
402
+ */
403
+ export declare const EMERGENCY_TRANSITIONS: Readonly<Record<EmergencyPhaseName, readonly EmergencyPhaseName[]>>;
404
+ export declare function canTransition(from: EmergencyPhaseName, to: EmergencyPhaseName): boolean;
405
+ export declare const EMERGENCY_CODE_PATTERN: RegExp;
406
+ export declare function isValidEmergencyCode(code: string): boolean;
407
+ export declare function normalizeBaseUrl(baseUrl: string): string;
408
+ interface ConfigValidationResult {
409
+ readonly ok: boolean;
410
+ readonly error: EmergencyError | null;
411
+ readonly config: EmergencyConfig | null;
412
+ }
413
+ export declare function validateConfig(config: EmergencyConfig): ConfigValidationResult;
414
+ export declare function themeToCssVariables(theme: EmergencyTheme | undefined): Record<string, string>;
415
+ interface EmergencyErrorOptions {
416
+ status?: number | null;
417
+ requestId?: string | null;
418
+ details?: unknown;
419
+ cause?: unknown;
420
+ }
421
+ export declare function createEmergencyError(code: EmergencyErrorCode, message: string, options?: EmergencyErrorOptions): EmergencyError;
422
+ export declare function isEmergencyError(value: unknown): value is EmergencyError;
423
+ export declare function toEmergencyError(value: unknown, fallbackCode?: EmergencyErrorCode): EmergencyError;
424
+ /**
425
+ * Error object accepted by host integrations that prefer `throw`/`catch` while
426
+ * the library still exposes the typed error through `.error`.
427
+ */
428
+ export declare class EmergencyClientError extends Error {
429
+ readonly error: EmergencyError;
430
+ constructor(error: EmergencyError);
431
+ }
432
+ export type { AbortResponse, AbortResult, ActivateRequest, BffClient, BffClientOptions, ConfigResponse, CsrfStrategy, CurrentResponse, EmergencyClient, EmergencyClientState, EmergencyConfig, EmergencyController, EmergencyControllerDeps, EmergencyError, EmergencyErrorCode, EmergencyEventEmitter, EmergencyEventMap, EmergencyEventName, EmergencyFailure, EmergencyFallback, EmergencyFontFamilyFlags, EmergencyFontLoading, EmergencyGeolocation, EmergencyMountOptions, EmergencyOperation, EmergencyPhase, EmergencyPhaseName, EmergencyResultPayload, EmergencySnapshot, EmergencyStore, EmergencyTheme, EmergencyTrigger, FailurePayload, Message, MessageKind, PanelCloseReason, PanelOpenReason, RealtimeStatus, RefreshResponse, Responder, ResponderPayload, ResponderStatus, SessionResponse, SimulationResult };
433
+ //# sourceMappingURL=index.d.cts.map