@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 +21 -0
- package/README.md +345 -0
- package/dist/index.cjs +1240 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +167 -0
- package/dist/index.d.ts +167 -0
- package/dist/index.mjs +1232 -0
- package/dist/index.mjs.map +1 -0
- package/dist/testing.cjs +272 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +97 -0
- package/dist/testing.d.ts +97 -0
- package/dist/testing.mjs +268 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/transport-beacon.cjs +452 -0
- package/dist/transport-beacon.cjs.map +1 -0
- package/dist/transport-beacon.d.cts +68 -0
- package/dist/transport-beacon.d.ts +68 -0
- package/dist/transport-beacon.mjs +450 -0
- package/dist/transport-beacon.mjs.map +1 -0
- package/dist/types-D-xVvmvX.d.cts +227 -0
- package/dist/types-D-xVvmvX.d.ts +227 -0
- package/package.json +79 -0
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
|
+
[](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.
|