@tallyrow/safesignal 1.0.1-rc.1

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 John Goure
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 ADDED
@@ -0,0 +1,345 @@
1
+ # SafeSignal
2
+
3
+ **SafeSignal** is a browser-first, vendor-neutral structured logging
4
+ facade and safety boundary for browser applications and federated
5
+ frontend modules. Secure-by-default sanitization, URL scrubbing,
6
+ key + shape redaction, control-character escaping, and a pluggable
7
+ transport boundary — all applied before any transport sees an event.
8
+ Published on npm as `@tallyrow/safesignal` (TallyRow is the
9
+ publishing organization; SafeSignal is the product).
10
+
11
+ ## Why SafeSignal
12
+
13
+ - **Secure-by-default**: token / cookie / authorization-header / known-PII fields stripped before any transport sees an event. Fail-closed redaction — a redactor failure drops the field, never emits unredacted.
14
+ - **Never-throw boundary**: no transport, redactor, or sanitizer failure propagates into your `log.info(...)` call site. Logging cannot break rendering, navigation, or state updates.
15
+ - **Vendor-neutral transport**: ship to Datadog, Honeycomb, your own ingestion, or the built-in `./transport-beacon` subpath for body-only HTTPS delivery — same API regardless of destination.
16
+ - **Federated-runtime aware**: host owns the configured runtime; modules import loggers without re-configuring. Hundreds of `Logger` instances per page stay constant-cost.
17
+ - **Lightweight**: ~8 KB gzipped default entry; structured events with bounded depth and bounded size; no global listeners, no ambient state reads, no per-instance backend init.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install @tallyrow/safesignal
23
+ ```
24
+
25
+ ## Quickstart
26
+
27
+ ```ts
28
+ import { configureLogging, createLogger, ConsoleTransport } from '@tallyrow/safesignal';
29
+
30
+ configureLogging({
31
+ application: { name: 'checkout-web', version: '2025.05.0' },
32
+ environment: 'production',
33
+ transports: [ConsoleTransport()],
34
+ });
35
+
36
+ const log = createLogger();
37
+ log.info('checkout opened', { cartItems: 3 });
38
+ ```
39
+
40
+ > Previously known as `@your-org/frontend-logging-sdk`? See [Migration history](#migration-history) for the install + import upgrade path.
41
+
42
+ ## What this package does NOT do (in v1)
43
+
44
+ - Ship an HTTP/beacon transport in the default entry — use the
45
+ `./transport-beacon` subpath for the first-party body-only HTTPS
46
+ transport, or implement `Transport` yourself for a custom
47
+ delivery primitive.
48
+ - Read `process.env.NODE_ENV`, `import.meta.env`, `location`, or
49
+ `document.cookie` — pass `environment` explicitly.
50
+ - Install global listeners or singletons (RUM-style automatic
51
+ error capture, view tracking, web vitals, network
52
+ instrumentation are forward-looking; see Roadmap below).
53
+ - Persist events to IndexedDB or any storage layer.
54
+ - Batch, sample, or deduplicate events by default (opt-in
55
+ batching is available via the `./transport-beacon` subpath).
56
+
57
+ ## Ship logs over HTTPS — `./transport-beacon` subpath
58
+
59
+ For body-only HTTPS delivery, import the first-party
60
+ `createBeaconTransport` from the `./transport-beacon` subpath. It
61
+ satisfies the transport security contract (T-S1..T-S5) by
62
+ construction — see
63
+ [`docs/safe-logging.md`](docs/safe-logging.md) for the full
64
+ write-up.
65
+
66
+ ```ts
67
+ import { configureLogging, createLogger } from '@tallyrow/safesignal';
68
+ import { createBeaconTransport } from '@tallyrow/safesignal/transport-beacon';
69
+
70
+ const onInternalError = (err: Error): void => myReporter.captureException(err);
71
+
72
+ configureLogging({
73
+ application: { name: 'payments', version: '2.4.1' },
74
+ environment: 'production',
75
+ transports: [
76
+ createBeaconTransport({
77
+ endpoint: 'https://logs.example.com/ingest',
78
+ onInternalError, // ← inner hook for async beacon drops
79
+ }),
80
+ ],
81
+ onInternalError, // ← outer hook for SafeTransport failures
82
+ });
83
+
84
+ const logger = createLogger();
85
+ logger.warn('payment retry exceeded threshold', { attemptCount: 4 });
86
+ logger.error('payment processor 5xx', { orderId: 'ord_9f3' }, new Error('upstream timeout'));
87
+ ```
88
+
89
+ The transport:
90
+
91
+ - Refuses non-HTTPS endpoints at construction time (loopback dev
92
+ endpoints opt in via `allowInsecureLoopback: true`).
93
+ - Prefers `navigator.sendBeacon`; falls back once to `fetch` with
94
+ `keepalive: true` and `credentials: 'same-origin'`.
95
+ - Installs a single `pagehide` listener lazily on first `send()`;
96
+ removes it on `shutdown()`.
97
+ - Supports optional opt-in batching for high-volume pages — see
98
+ the [Beacon transport batching](docs/safe-logging.md#beacon-transport-batching-opt-in)
99
+ section of `docs/safe-logging.md` for the envelope shape and
100
+ `maxBatchSize × per-event-size < 64 KiB` sizing rule.
101
+ - Surfaces every drop through `onInternalError` with a documented
102
+ `BeaconErrorCode` — `oversized_event`, `transport_send_failed`,
103
+ `beacon_batch_drop`, `beacon_unavailable`,
104
+ `transport_shutdown_failed`. Wire the hook to **both** layers
105
+ above for full coverage.
106
+
107
+ ## Level configuration
108
+
109
+ In `production`, `debug` and `info` are dropped by default. Raise the
110
+ threshold per environment:
111
+
112
+ ```ts
113
+ configureLogging({
114
+ environment: 'production',
115
+ level: { production: 'info', development: 'debug', test: 'warn' },
116
+ transports: [ConsoleTransport()],
117
+ });
118
+ ```
119
+
120
+ ## Logging safely
121
+
122
+ The constitution requires the **safe path** to be the **easy path**. A few
123
+ patterns the package's types and pipeline enforce:
124
+
125
+ ### DO — structured attributes, fixed-string messages
126
+
127
+ ```ts
128
+ log.info('order placed', {
129
+ orderId: order.id,
130
+ total: order.total,
131
+ currency: order.currency,
132
+ });
133
+
134
+ log.warn('payment declined', { reason: 'insufficient_funds', code: 'PD-12' });
135
+ ```
136
+
137
+ ### DON'T — interpolate values into the message string
138
+
139
+ ```ts
140
+ // BAD — values disappear into a string the package can't structure.
141
+ log.info(`order placed by ${user.email}`);
142
+
143
+ // GOOD — values stay structured and reviewable.
144
+ log.info('order placed', { userId: user.id });
145
+ ```
146
+
147
+ ### DON'T — dump whole objects, DOM nodes, or framework objects
148
+
149
+ ```ts
150
+ // BAD — the sanitizer (T031) will type-tag classes / DOM / Event /
151
+ // Promise rather than recurse, so this won't even produce useful data
152
+ // AND it risks pulling fields you didn't intend to log.
153
+ log.info('order placed', { order });
154
+ log.error('click handler failed', { event }); // DOM Event
155
+ log.error('reducer failed', { state }); // full app state
156
+
157
+ // GOOD — extract the fields you actually want.
158
+ log.info('order placed', { orderId: order.id, total: order.total });
159
+ ```
160
+
161
+ [`docs/safe-logging.md`](docs/safe-logging.md) covers the full
162
+ enumeration of DO / DON'T patterns, the sanitizer's bounded-input
163
+ rules, the redactor's default denylist and shape rules,
164
+ `createRedactor()` composition, `scrubUrl()` usage, the
165
+ diagnostics contract, and — per constitution Principle VI — every
166
+ documented behavior that drops, transforms, or otherwise bounds an
167
+ event before delivery (level-filter drops, fail-closed redactor
168
+ drops, sanitizer truncation markers, URL-scrubber replacements,
169
+ control-char escapes, `NoopTransport` swallowing, and the v1
170
+ no-batching / no-sampling / no-deduplication stance).
171
+
172
+ ## Transport security — body-only, HTTPS, no event data in URLs
173
+
174
+ Consumer transports MUST follow the security clauses of
175
+ `contracts/transport.md` (T-S1..T-S5). These are not stylistic — they
176
+ exist because URLs leak through proxy logs, browser history, server
177
+ access logs, referer headers, and APM dashboards. The package's
178
+ pipeline does no good if you then funnel its output through a query
179
+ string.
180
+
181
+ ### Rules
182
+
183
+ - **T-S1 — No event data in URLs.** No part of a `LogEvent` may appear
184
+ in the URL path, query string, or fragment. Not the message, not an
185
+ attribute value, not a context field.
186
+ - **T-S2 — Body-only delivery.** Use `navigator.sendBeacon(url, blob)`
187
+ with a JSON `Blob`, or `fetch(url, { method: 'POST' | 'PUT' | 'PATCH',
188
+ body: JSON.stringify(event), keepalive: true })`. Never `GET`.
189
+ - **T-S3 — HTTPS for cross-origin.** Absolute URLs MUST use `https://`.
190
+ Same-origin relative URLs (`/log`) inherit the page's scheme and are
191
+ fine.
192
+ - **T-S4 — Treat events as immutable.** Don't mutate the `LogEvent` the
193
+ transport receives. The package freezes events in `__DEV__` builds to
194
+ catch accidental writes.
195
+ - **T-S5 — Idempotent `flush()` / `shutdown()`.** Both are optional and
196
+ safe to call more than once.
197
+
198
+ ### Canonical sample
199
+
200
+ The first-party `createBeaconTransport` from
201
+ `@tallyrow/safesignal/transport-beacon` (used in the
202
+ [`./transport-beacon` subpath](#ship-logs-over-https--transport-beacon-subpath)
203
+ section above) is the body-only beacon reference both example
204
+ projects use. It tries `sendBeacon` first, falls back to `fetch`
205
+ with `keepalive: true`, and refuses non-HTTPS endpoints at
206
+ construction time.
207
+
208
+ Consumers who need a different delivery primitive implement the
209
+ `Transport` interface themselves and follow T-S1..T-S5 in their
210
+ own code.
211
+
212
+ ### Verify your transport with `assertTransportContract`
213
+
214
+ The `./testing` subpath ships a contract-test helper that runs T-S1..T-S5
215
+ against any consumer-supplied transport. Use it in your own test suite —
216
+ not in production code:
217
+
218
+ ```ts
219
+ // my-transport.test.ts
220
+ import { assertTransportContract } from '@tallyrow/safesignal/testing';
221
+ import { createBeaconTransport } from '@tallyrow/safesignal/transport-beacon';
222
+
223
+ test('my transport satisfies the security contract', async () => {
224
+ await assertTransportContract(
225
+ createBeaconTransport({ endpoint: 'https://logs.example.com/ingest' }),
226
+ );
227
+ });
228
+ ```
229
+
230
+ The helper intercepts `globalThis.fetch` and `navigator.sendBeacon` for
231
+ the duration of each check and asserts the bad-shapes that T-S1..T-S5
232
+ forbid. It throws on the first violation with a diagnostic message
233
+ naming the failing clause.
234
+
235
+ ## Federated / module-federation deployments
236
+
237
+ The host application owns `configureLogging()` by convention;
238
+ federated modules call `createLogger({ module })` against the
239
+ host's already-configured runtime. Duplicate physical copies of
240
+ the package on a page are **isolated** by design — each copy
241
+ maintains its own runtime, with no globalThis registry — and
242
+ consumers who want cross-copy sharing configure their bundler's
243
+ module-federation singleton.
244
+
245
+ The full federated story is in
246
+ [`docs/safe-logging.md`](docs/safe-logging.md):
247
+ "Configuration ownership in federated deployments", "Duplicate
248
+ package copies", and "Vendor neutrality".
249
+
250
+ ## Examples
251
+
252
+ - [`examples/host-app/`](examples/host-app/) — single-app consumer;
253
+ uses the first-party `createBeaconTransport` from
254
+ `@tallyrow/safesignal/transport-beacon` for body-only
255
+ HTTPS delivery.
256
+ - [`examples/federated-module/`](examples/federated-module/) —
257
+ federated module consumer; demonstrates `createLogger({ module })`
258
+ against a host-configured runtime, with security guidance for
259
+ module authors (no host secrets, no ambient state, no full host
260
+ state). See its [README](examples/federated-module/README.md) for
261
+ pointers into the federated docs.
262
+
263
+ ## Project resources
264
+
265
+ [![pipeline status](https://gitlab.com/tallyrow/safesignal/badges/main/pipeline.svg)](https://gitlab.com/tallyrow/safesignal/-/commits/main)
266
+
267
+ Community and legal:
268
+
269
+ - [`CONTRIBUTING.md`](CONTRIBUTING.md) — how to file issues, send MRs, sign commits (DCO)
270
+ - [`SECURITY.md`](SECURITY.md) — vulnerability disclosure policy
271
+ - [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) — Contributor Covenant 2.1
272
+ - [`GOVERNANCE.md`](GOVERNANCE.md) — how project decisions get made
273
+ - [`LICENSE`](LICENSE) — MIT license
274
+ - [`CHANGELOG.md`](CHANGELOG.md) — version-by-version release notes
275
+
276
+ Reference docs and design history:
277
+
278
+ - [`docs/safe-logging.md`](docs/safe-logging.md) — full DO/DON'T sweep,
279
+ documented drops/transforms/bounded behaviors, configuration
280
+ ownership for federated deployments, duplicate-copy classification,
281
+ vendor neutrality
282
+ - `specs/001-structured-logging-core/` — core feature spec, plan,
283
+ contracts, quickstart (public API, transport, log-event,
284
+ failure-safety, redaction, sanitization)
285
+ - `specs/002-beacon-transport/` — first-party `./transport-beacon`
286
+ feature spec, plan, contracts, quickstart
287
+ - `specs/003-rename-safesignal/` — v1.0.0 rename feature
288
+ - `.specify/memory/constitution.md` — governing principles (v1.2.0)
289
+
290
+ ## Roadmap
291
+
292
+ The following are forward-looking items (not shipping today):
293
+
294
+ - **Trace-context propagation** — W3C Trace Context (`traceparent`,
295
+ `tracestate`) for correlating frontend logs with backend traces.
296
+ - **`./transport-otlp` subpath** — OTel-formatted events; ships to
297
+ any OTLP-compatible backend (Datadog, Honeycomb, Grafana
298
+ Tempo + Loki, self-hosted ClickHouse, etc.).
299
+ - **RUM features** — Web Vitals, automatic error capture, view
300
+ tracking, network instrumentation (planned as opt-in subpaths
301
+ under `./rum-*`).
302
+
303
+ A separate sibling project, **`safesignal-server`**, is planned as
304
+ a self-hostable monitoring backend that consumes SafeSignal's
305
+ OTLP-formatted events. SafeSignal stays a small vendor-neutral
306
+ SDK; the server lives in its own repo when it ships.
307
+
308
+ ## Migration history
309
+
310
+ The v1.0.0 release on 2026-05-28 renamed the project from its
311
+ working name (`@your-org/frontend-logging-sdk`) to **SafeSignal**,
312
+ published on npm as `@tallyrow/safesignal`. The original rename
313
+ notice from that release follows verbatim for consumers arriving
314
+ via the legacy package name.
315
+
316
+ This package was previously developed under the working name
317
+ `@your-org/frontend-logging-sdk`. As of v1.0.0 it ships as
318
+ **SafeSignal**, published on npm as `@tallyrow/safesignal`.
319
+
320
+ **Migration**:
321
+
322
+ ```bash
323
+ # Install the new package
324
+ npm install @tallyrow/safesignal
325
+ ```
326
+
327
+ ```ts
328
+ // Update every import:
329
+ // Before
330
+ import { createLogger } from '@your-org/frontend-logging-sdk';
331
+ import { createBeaconTransport } from '@your-org/frontend-logging-sdk/transport-beacon';
332
+ import { assertTransportContract } from '@your-org/frontend-logging-sdk/testing';
333
+
334
+ // After
335
+ import { createLogger } from '@tallyrow/safesignal';
336
+ import { createBeaconTransport } from '@tallyrow/safesignal/transport-beacon';
337
+ import { assertTransportContract } from '@tallyrow/safesignal/testing';
338
+ ```
339
+
340
+ Subpaths (`/testing`, `/transport-beacon`) are unchanged — only the
341
+ package-name segment moves. No runtime behavior, public API,
342
+ redaction default, sanitizer limit, URL-scrubber behavior, or
343
+ transport-security contract change in this release. Bundle sizes
344
+ remain within ±1 KiB of the pre-rename baseline. See
345
+ [`CHANGELOG.md`](CHANGELOG.md) for the release entry.