boosten-runtime 0.1.0-alpha.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.
Potentially problematic release.
This version of boosten-runtime might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/chunk-6F4PWJZI.js +0 -0
- package/dist/chunk-7TGINAZV.js +19 -0
- package/dist/chunk-VFNN22DR.js +212 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +50 -0
- package/dist/no-pii.d.ts +29 -0
- package/dist/no-pii.js +12 -0
- package/dist/transmit.d.ts +24 -0
- package/dist/transmit.js +7 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Boosten contributors
|
|
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,141 @@
|
|
|
1
|
+
# @boosten/runtime
|
|
2
|
+
|
|
3
|
+
> React Native performance runtime. Zero-config in-memory mode. Optional
|
|
4
|
+
> server upload. AI-readable diagnosis output.
|
|
5
|
+
|
|
6
|
+
> **⚠️ USE AT YOUR OWN RISK.** Boosten is provided "as is", with all faults, and without warranty of any kind. The Publisher is not liable for any loss or damage arising from your use of Boosten. See `LICENSE` and `/TERMS.md` Section 0 for the full risk-allocation language. By installing this package you accept these terms.
|
|
7
|
+
|
|
8
|
+
## No-PII guarantee (enforced by code)
|
|
9
|
+
|
|
10
|
+
Boosten's privacy promise — *no personally identifying data is ever collected* — is enforced by code, not just by prose in the privacy policy.
|
|
11
|
+
|
|
12
|
+
Every outgoing payload is run through `assertNoPII()`, which scans field names against a denylist of PII-shaped tokens (`email`, `userId`, `ip`, `lat`, `password`, `deviceId`, `idfa`, `address`, `phone`, etc.) and **throws** if any match. A companion test suite (`src/no-pii.test.ts`, 45 tests) fails the build if the guard ever stops catching a known forbidden field — making it impossible for a future contributor to silently introduce PII without the build going red.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { assertNoPII, checkNoPII, PIIDetectedError } from "@boosten/runtime";
|
|
16
|
+
|
|
17
|
+
// Throwing variant — recommended at transmit boundary:
|
|
18
|
+
assertNoPII(sample);
|
|
19
|
+
await fetch(endpoint, { body: JSON.stringify(sample) });
|
|
20
|
+
|
|
21
|
+
// Non-throwing variant — useful when you'd rather log and drop:
|
|
22
|
+
const err = checkNoPII(sample);
|
|
23
|
+
if (err) console.warn("Boosten dropped a sample:", err.path, err.fieldName);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The full denylist is exported as `PII_DENYLIST` for inspection. Run the tests with `pnpm --filter @boosten/runtime test`.
|
|
27
|
+
|
|
28
|
+
### One transmit boundary, by design
|
|
29
|
+
|
|
30
|
+
Every outgoing payload should leave the device through one chokepoint: `safeTransmit()`. It runs `assertNoPII()` first and **only** issues the network call if the payload is clean — if a PII-shaped field is detected, `fetch` is never called.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { safeTransmit } from "@boosten/runtime";
|
|
34
|
+
|
|
35
|
+
await safeTransmit("https://your-endpoint/boosten/ingest", sample, {
|
|
36
|
+
headers: { authorization: `Bearer ${token}` },
|
|
37
|
+
});
|
|
38
|
+
// → throws PIIDetectedError BEFORE any network I/O if `sample` contains PII
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Boosten's default in-memory mode never reaches this boundary. If you (or a Boosten Cloud integration) wire a server-upload code path, route it through `safeTransmit` rather than calling `fetch` directly. This means a reviewer can verify the entire privacy promise by reading **one ~30-line file** (`src/transmit.ts`).
|
|
42
|
+
|
|
43
|
+
## Status
|
|
44
|
+
|
|
45
|
+
**0.1.0-alpha.** The type contracts and constants in this package are stable
|
|
46
|
+
and safe to depend on today. The hooks and diagnoser implementations land in
|
|
47
|
+
0.1.0 (file-move release from the Rival reference codebase).
|
|
48
|
+
|
|
49
|
+
## Why Boosten
|
|
50
|
+
|
|
51
|
+
Most React Native perf tools either:
|
|
52
|
+
|
|
53
|
+
1. Cost money, ship telemetry to a vendor, and need a PM signoff to install
|
|
54
|
+
(Sentry, Firebase Performance, Embrace), **or**
|
|
55
|
+
2. Are abandoned single-purpose npm packages (`react-native-performance`,
|
|
56
|
+
`why-did-you-render` clones).
|
|
57
|
+
|
|
58
|
+
Boosten is the third path: code you own, in your repo, no PII off-device, no
|
|
59
|
+
monthly bill. Built around an opinionated, AI-readable diagnosis schema so an
|
|
60
|
+
LLM agent (Cursor, Claude Code, Replit Agent) can read a perf report and
|
|
61
|
+
propose patches automatically.
|
|
62
|
+
|
|
63
|
+
## What's in the box (when 0.1.0 ships)
|
|
64
|
+
|
|
65
|
+
| Module | What it does |
|
|
66
|
+
|------------------------|---------------------------------------------------------------|
|
|
67
|
+
| `usePerfTracker(name)` | Drop into every screen. Captures TTFF / TTI / paint / IM. |
|
|
68
|
+
| `markBoot(kind)` | Boot ladder: bundleLoaded → rootRendered → firstScreen → IA. |
|
|
69
|
+
| `diagnose(events)` | Per-screen diagnosis with score, rating, root-cause hints. |
|
|
70
|
+
| `frameSampler` | Long-session jankFraction + p99 frame time (60-second window).|
|
|
71
|
+
| Novel detectors | Ghost-mount, thundering-herd, stranded-interval, perception. |
|
|
72
|
+
| `computeScreenScore()` | Indeed-style composite (TTFF×.25 + TTI×.45 + FID×.30). |
|
|
73
|
+
|
|
74
|
+
## Demo (before / after)
|
|
75
|
+
|
|
76
|
+
> **Note:** the published 0.1.0 release will ship a `before/after.gif` in this
|
|
77
|
+
> README captured from the Rival reference app. The demo flow:
|
|
78
|
+
>
|
|
79
|
+
> 1. `boosten init` adds the tracker to a stock Expo screen.
|
|
80
|
+
> 2. Open the in-app `/dev/perf` viewer — see TTFF=380ms / TTI=720ms / score=68.
|
|
81
|
+
> 3. Boosten surfaces "ghost-mount on TeamCard" + a fix template.
|
|
82
|
+
> 4. Apply the suggested fix.
|
|
83
|
+
> 5. Re-run, see TTFF=180ms / TTI=420ms / score=92.
|
|
84
|
+
>
|
|
85
|
+
> The whole loop takes under 5 minutes from `npm install` and never touches
|
|
86
|
+
> a third-party server.
|
|
87
|
+
|
|
88
|
+
## Install
|
|
89
|
+
|
|
90
|
+
> **Pre-publish alpha — not yet on npm.** Inside this monorepo, add
|
|
91
|
+
> `"@boosten/runtime": "workspace:*"` to your package's `dependencies` and
|
|
92
|
+
> `pnpm install`. After 0.1.0 first publish:
|
|
93
|
+
>
|
|
94
|
+
> ```bash
|
|
95
|
+
> npm i @boosten/runtime @boosten/checklist
|
|
96
|
+
> ```
|
|
97
|
+
|
|
98
|
+
## Use today (type-only)
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import type { PerfEvent, PerfSample } from "@boosten/runtime";
|
|
102
|
+
import { SCORE_THRESHOLDS, SCORE_WEIGHTS } from "@boosten/runtime";
|
|
103
|
+
|
|
104
|
+
// Wire your own ingest endpoint against the stable PerfSample shape.
|
|
105
|
+
function isValidSample(s: unknown): s is PerfSample {
|
|
106
|
+
if (!s || typeof s !== "object") return false;
|
|
107
|
+
const o = s as Record<string, unknown>;
|
|
108
|
+
return (
|
|
109
|
+
typeof o.screen === "string" &&
|
|
110
|
+
typeof o.appVersion === "string" &&
|
|
111
|
+
typeof o.score === "number" &&
|
|
112
|
+
typeof o.ts === "number"
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Use after 0.1.0 (full hooks)
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { usePerfTracker } from "@boosten/runtime";
|
|
121
|
+
|
|
122
|
+
export default function TeamScreen() {
|
|
123
|
+
usePerfTracker("team/[id]"); // first line of every screen
|
|
124
|
+
return <TeamView />;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Coupling we are removing before 0.1.0
|
|
129
|
+
|
|
130
|
+
The reference implementation has three known coupling points being addressed
|
|
131
|
+
before the file-move release:
|
|
132
|
+
|
|
133
|
+
1. `usePerfTracker` uses `@/lib/...` path aliases → being parameterized.
|
|
134
|
+
2. `prodSampler` hardcodes `EXPO_PUBLIC_BOOSTEN_INGEST_URL/TOKEN` env-var
|
|
135
|
+
names → becoming `boosten.config.json`-driven.
|
|
136
|
+
3. AsyncStorage refs in `perfBoot` → routed through `perfPlatform` so a Node
|
|
137
|
+
test runner can swap in a memory shim.
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertNoPII
|
|
3
|
+
} from "./chunk-VFNN22DR.js";
|
|
4
|
+
|
|
5
|
+
// src/transmit.ts
|
|
6
|
+
async function safeTransmit(endpoint, payload, opts = {}) {
|
|
7
|
+
assertNoPII(payload);
|
|
8
|
+
const fetchFn = opts.fetchImpl ?? fetch;
|
|
9
|
+
return fetchFn(endpoint, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: { "content-type": "application/json", ...opts.headers ?? {} },
|
|
12
|
+
body: JSON.stringify(payload),
|
|
13
|
+
signal: opts.signal
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
safeTransmit
|
|
19
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// src/no-pii.ts
|
|
2
|
+
var PII_DENYLIST = Object.freeze([
|
|
3
|
+
// identity
|
|
4
|
+
"email",
|
|
5
|
+
"mail",
|
|
6
|
+
"username",
|
|
7
|
+
"userid",
|
|
8
|
+
"user_id",
|
|
9
|
+
"uid",
|
|
10
|
+
"firstname",
|
|
11
|
+
"first_name",
|
|
12
|
+
"lastname",
|
|
13
|
+
"last_name",
|
|
14
|
+
"fullname",
|
|
15
|
+
"full_name",
|
|
16
|
+
"phone",
|
|
17
|
+
"mobile",
|
|
18
|
+
"tel",
|
|
19
|
+
"ssn",
|
|
20
|
+
"nationalid",
|
|
21
|
+
"national_id",
|
|
22
|
+
"taxid",
|
|
23
|
+
"tax_id",
|
|
24
|
+
"dob",
|
|
25
|
+
"birthdate",
|
|
26
|
+
"birthday",
|
|
27
|
+
// account/auth secrets
|
|
28
|
+
"password",
|
|
29
|
+
"passwd",
|
|
30
|
+
"secret",
|
|
31
|
+
"apikey",
|
|
32
|
+
"api_key",
|
|
33
|
+
"token",
|
|
34
|
+
"auth",
|
|
35
|
+
"session",
|
|
36
|
+
"cookie",
|
|
37
|
+
"bearer",
|
|
38
|
+
"jwt",
|
|
39
|
+
"creditcard",
|
|
40
|
+
"credit_card",
|
|
41
|
+
"cardnumber",
|
|
42
|
+
"card_number",
|
|
43
|
+
"cvv",
|
|
44
|
+
"iban",
|
|
45
|
+
"swift",
|
|
46
|
+
"routingnumber",
|
|
47
|
+
"routing_number",
|
|
48
|
+
// device-as-identity
|
|
49
|
+
"deviceid",
|
|
50
|
+
"device_id",
|
|
51
|
+
"advertisingid",
|
|
52
|
+
"advertising_id",
|
|
53
|
+
"idfa",
|
|
54
|
+
"idfv",
|
|
55
|
+
"macaddress",
|
|
56
|
+
"mac_address",
|
|
57
|
+
"imei",
|
|
58
|
+
// network identity
|
|
59
|
+
"ipaddress",
|
|
60
|
+
"ip_address",
|
|
61
|
+
"ip",
|
|
62
|
+
"ipv4",
|
|
63
|
+
"ipv6",
|
|
64
|
+
"useragent",
|
|
65
|
+
"user_agent",
|
|
66
|
+
// location
|
|
67
|
+
"latitude",
|
|
68
|
+
"longitude",
|
|
69
|
+
"lat",
|
|
70
|
+
"lng",
|
|
71
|
+
"lon",
|
|
72
|
+
"geohash",
|
|
73
|
+
"address",
|
|
74
|
+
"street",
|
|
75
|
+
"city",
|
|
76
|
+
"zipcode",
|
|
77
|
+
"zip_code",
|
|
78
|
+
"postalcode",
|
|
79
|
+
"postal_code",
|
|
80
|
+
// free-form content (high-risk for incidental PII)
|
|
81
|
+
"message",
|
|
82
|
+
"content",
|
|
83
|
+
"body",
|
|
84
|
+
"text",
|
|
85
|
+
"comment",
|
|
86
|
+
"note",
|
|
87
|
+
"value",
|
|
88
|
+
"input",
|
|
89
|
+
"query",
|
|
90
|
+
"search"
|
|
91
|
+
]);
|
|
92
|
+
var ALLOWLIST = Object.freeze([
|
|
93
|
+
// Boosten's "screen route" is a code-defined string like "team/[id]"
|
|
94
|
+
// — never user content. Distinct from "screenName" of a person.
|
|
95
|
+
"screen",
|
|
96
|
+
"screenname",
|
|
97
|
+
"screen_name",
|
|
98
|
+
// boot/version metadata
|
|
99
|
+
"appversion",
|
|
100
|
+
"app_version",
|
|
101
|
+
"osversion",
|
|
102
|
+
"os_version",
|
|
103
|
+
"osname",
|
|
104
|
+
"os_name",
|
|
105
|
+
// performance findings
|
|
106
|
+
"findingid",
|
|
107
|
+
"finding_id",
|
|
108
|
+
"ruleid",
|
|
109
|
+
"rule_id",
|
|
110
|
+
// Boosten's score/rating values
|
|
111
|
+
"score",
|
|
112
|
+
"rating",
|
|
113
|
+
"perception"
|
|
114
|
+
]);
|
|
115
|
+
var PIIDetectedError = class extends Error {
|
|
116
|
+
path;
|
|
117
|
+
fieldName;
|
|
118
|
+
matchedFragment;
|
|
119
|
+
constructor(path, fieldName, matchedFragment) {
|
|
120
|
+
super(
|
|
121
|
+
`Boosten no-PII guard refused outgoing payload: field "${path}" (name "${fieldName}") matches denied fragment "${matchedFragment}". Boosten's privacy contract forbids transmitting personally identifying data. If this field is genuinely non-PII, rename it; otherwise remove it.`
|
|
122
|
+
);
|
|
123
|
+
this.name = "PIIDetectedError";
|
|
124
|
+
this.path = path;
|
|
125
|
+
this.fieldName = fieldName;
|
|
126
|
+
this.matchedFragment = matchedFragment;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
// Value-based PII patterns — catch sensitive data hiding in allowlisted or neutral field names.
|
|
130
|
+
var _EMAIL_RE = /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/;
|
|
131
|
+
var _IPV4_RE = /\b(?:\d{1,3}\.){3}\d{1,3}\b/;
|
|
132
|
+
var _IPV6_RE = /(?:[0-9a-f]{1,4}:){2,7}[0-9a-f]{0,4}|(?:[0-9a-f]{1,4}:)*:[0-9a-f]{1,4}/i;
|
|
133
|
+
var _JWT_RE = /^eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+$/;
|
|
134
|
+
var _BEARER_RE = /^bearer\s+\S{8,}/i;
|
|
135
|
+
var _PHONE_RE = /(?:\+\d{1,3}[\s\-.]?)?\(?\d{3}\)?[\s\-.]?\d{3}[\s\-.]?\d{4}/;
|
|
136
|
+
function findDeniedValueFragment(val) {
|
|
137
|
+
if (typeof val !== "string" || val.length < 5) return null;
|
|
138
|
+
if (_EMAIL_RE.test(val)) return "~email";
|
|
139
|
+
if (_JWT_RE.test(val)) return "~jwt";
|
|
140
|
+
if (_BEARER_RE.test(val)) return "~bearer";
|
|
141
|
+
if (_IPV4_RE.test(val)) return "~ipv4";
|
|
142
|
+
if (_IPV6_RE.test(val)) return "~ipv6";
|
|
143
|
+
if (_PHONE_RE.test(val)) return "~phone";
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function normalize(name) {
|
|
147
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
148
|
+
}
|
|
149
|
+
function tokenize(name) {
|
|
150
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((t) => t.toLowerCase());
|
|
151
|
+
}
|
|
152
|
+
function isAllowed(normalized) {
|
|
153
|
+
for (const allowed of ALLOWLIST) {
|
|
154
|
+
if (normalized === allowed) return true;
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
function findDeniedFragment(name) {
|
|
159
|
+
const normalized = normalize(name);
|
|
160
|
+
if (isAllowed(normalized)) return null;
|
|
161
|
+
const tokens = tokenize(name);
|
|
162
|
+
for (const denied of PII_DENYLIST) {
|
|
163
|
+
const d = normalize(denied);
|
|
164
|
+
if (normalized === d) return denied;
|
|
165
|
+
for (const t of tokens) {
|
|
166
|
+
if (t === d) return denied;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
function walk(value, path, seen) {
|
|
172
|
+
if (value === null || value === void 0) return;
|
|
173
|
+
if (typeof value === "string") {
|
|
174
|
+
const denied = findDeniedValueFragment(value);
|
|
175
|
+
if (denied) throw new PIIDetectedError(path, path.split(".").pop() || path, denied);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (typeof value !== "object") return;
|
|
179
|
+
if (seen.has(value)) return;
|
|
180
|
+
seen.add(value);
|
|
181
|
+
if (Array.isArray(value)) {
|
|
182
|
+
for (let i = 0; i < value.length; i++) {
|
|
183
|
+
walk(value[i], `${path}[${i}]`, seen);
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
for (const key of Object.keys(value)) {
|
|
188
|
+
const matched = findDeniedFragment(key);
|
|
189
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
190
|
+
if (matched) throw new PIIDetectedError(childPath, key, matched);
|
|
191
|
+
walk(value[key], childPath, seen);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function assertNoPII(payload) {
|
|
195
|
+
walk(payload, "", /* @__PURE__ */ new WeakSet());
|
|
196
|
+
}
|
|
197
|
+
function checkNoPII(payload) {
|
|
198
|
+
try {
|
|
199
|
+
assertNoPII(payload);
|
|
200
|
+
return null;
|
|
201
|
+
} catch (err) {
|
|
202
|
+
if (err instanceof PIIDetectedError) return err;
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export {
|
|
208
|
+
PII_DENYLIST,
|
|
209
|
+
PIIDetectedError,
|
|
210
|
+
assertNoPII,
|
|
211
|
+
checkNoPII
|
|
212
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export { BoostenPhase, BootKind, PerfEvent, PerfSample, ScreenScoreBreakdown } from './types.js';
|
|
2
|
+
export { PIIDetectedError, PII_DENYLIST, assertNoPII, checkNoPII } from './no-pii.js';
|
|
3
|
+
export { SafeTransmitOptions, safeTransmit } from './transmit.js';
|
|
4
|
+
export { BOOSTEN_CHECKLIST, BoostenChecklistEntry, CHECKLIST_COUNT, CHECKLIST_VERSION, getChecklistEntry, listChecklistIds } from 'boosten-checklist';
|
|
5
|
+
|
|
6
|
+
/** Indeed-derived mobile thresholds. ~6× stricter than Core Web Vitals. */
|
|
7
|
+
declare const SCORE_THRESHOLDS: {
|
|
8
|
+
readonly ttff: {
|
|
9
|
+
readonly good: 300;
|
|
10
|
+
readonly poor: 800;
|
|
11
|
+
};
|
|
12
|
+
readonly tti: {
|
|
13
|
+
readonly good: 500;
|
|
14
|
+
readonly poor: 1500;
|
|
15
|
+
};
|
|
16
|
+
readonly fid: {
|
|
17
|
+
readonly good: 50;
|
|
18
|
+
readonly poor: 150;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/** Lighthouse-style weights for the composite score. */
|
|
22
|
+
declare const SCORE_WEIGHTS: {
|
|
23
|
+
readonly ttff: 0.25;
|
|
24
|
+
readonly tti: 0.45;
|
|
25
|
+
readonly fid: 0.3;
|
|
26
|
+
};
|
|
27
|
+
/** Score → rating cutoffs. */
|
|
28
|
+
declare const RATING_CUTOFFS: {
|
|
29
|
+
readonly good: 85;
|
|
30
|
+
readonly needsWork: 60;
|
|
31
|
+
};
|
|
32
|
+
declare const RUNTIME_VERSION = "0.1.0-alpha.1";
|
|
33
|
+
|
|
34
|
+
export { RATING_CUTOFFS, RUNTIME_VERSION, SCORE_THRESHOLDS, SCORE_WEIGHTS };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
safeTransmit
|
|
3
|
+
} from "./chunk-7TGINAZV.js";
|
|
4
|
+
import {
|
|
5
|
+
PIIDetectedError,
|
|
6
|
+
PII_DENYLIST,
|
|
7
|
+
assertNoPII,
|
|
8
|
+
checkNoPII
|
|
9
|
+
} from "./chunk-VFNN22DR.js";
|
|
10
|
+
import "./chunk-6F4PWJZI.js";
|
|
11
|
+
|
|
12
|
+
// src/index.ts
|
|
13
|
+
import {
|
|
14
|
+
BOOSTEN_CHECKLIST,
|
|
15
|
+
CHECKLIST_COUNT,
|
|
16
|
+
CHECKLIST_VERSION,
|
|
17
|
+
getChecklistEntry,
|
|
18
|
+
listChecklistIds
|
|
19
|
+
} from "boosten-checklist";
|
|
20
|
+
var SCORE_THRESHOLDS = {
|
|
21
|
+
ttff: { good: 300, poor: 800 },
|
|
22
|
+
tti: { good: 500, poor: 1500 },
|
|
23
|
+
fid: { good: 50, poor: 150 }
|
|
24
|
+
};
|
|
25
|
+
var SCORE_WEIGHTS = {
|
|
26
|
+
ttff: 0.25,
|
|
27
|
+
tti: 0.45,
|
|
28
|
+
fid: 0.3
|
|
29
|
+
};
|
|
30
|
+
var RATING_CUTOFFS = {
|
|
31
|
+
good: 85,
|
|
32
|
+
needsWork: 60
|
|
33
|
+
};
|
|
34
|
+
var RUNTIME_VERSION = "0.1.0-alpha.1";
|
|
35
|
+
export {
|
|
36
|
+
BOOSTEN_CHECKLIST,
|
|
37
|
+
CHECKLIST_COUNT,
|
|
38
|
+
CHECKLIST_VERSION,
|
|
39
|
+
PIIDetectedError,
|
|
40
|
+
PII_DENYLIST,
|
|
41
|
+
RATING_CUTOFFS,
|
|
42
|
+
RUNTIME_VERSION,
|
|
43
|
+
SCORE_THRESHOLDS,
|
|
44
|
+
SCORE_WEIGHTS,
|
|
45
|
+
assertNoPII,
|
|
46
|
+
checkNoPII,
|
|
47
|
+
getChecklistEntry,
|
|
48
|
+
listChecklistIds,
|
|
49
|
+
safeTransmit
|
|
50
|
+
};
|
package/dist/no-pii.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Field-name fragments that are presumed to identify a person, account,
|
|
2
|
+
* device-as-identity, location, or secret. Matching is case-insensitive
|
|
3
|
+
* and substring-based. */
|
|
4
|
+
declare const PII_DENYLIST: readonly ["email", "mail", "username", "userid", "user_id", "uid", "firstname", "first_name", "lastname", "last_name", "fullname", "full_name", "phone", "mobile", "tel", "ssn", "nationalid", "national_id", "taxid", "tax_id", "dob", "birthdate", "birthday", "password", "passwd", "secret", "apikey", "api_key", "token", "auth", "session", "cookie", "bearer", "jwt", "creditcard", "credit_card", "cardnumber", "card_number", "cvv", "iban", "swift", "routingnumber", "routing_number", "deviceid", "device_id", "advertisingid", "advertising_id", "idfa", "idfv", "macaddress", "mac_address", "imei", "ipaddress", "ip_address", "ip", "ipv4", "ipv6", "useragent", "user_agent", "latitude", "longitude", "lat", "lng", "lon", "geohash", "address", "street", "city", "zipcode", "zip_code", "postalcode", "postal_code", "message", "content", "body", "text", "comment", "note", "value", "input", "query", "search"];
|
|
5
|
+
declare class PIIDetectedError extends Error {
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly fieldName: string;
|
|
8
|
+
readonly matchedFragment: string;
|
|
9
|
+
constructor(path: string, fieldName: string, matchedFragment: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Throws `PIIDetectedError` if `payload` (or any nested object) contains
|
|
13
|
+
* a field whose name matches a known PII fragment. Call this on every
|
|
14
|
+
* outgoing PerfSample before transmitting or persisting off-device.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const sample: PerfSample = collect();
|
|
18
|
+
* assertNoPII(sample); // throws if a contributor added e.g. `userEmail`
|
|
19
|
+
* await fetch(endpoint, { body: JSON.stringify(sample) });
|
|
20
|
+
*/
|
|
21
|
+
declare function assertNoPII(payload: unknown): void;
|
|
22
|
+
/**
|
|
23
|
+
* Non-throwing variant. Returns null if clean, or the offending error
|
|
24
|
+
* object if PII was detected. Useful in places where you want to log
|
|
25
|
+
* and drop the sample rather than crash the host app.
|
|
26
|
+
*/
|
|
27
|
+
declare function checkNoPII(payload: unknown): PIIDetectedError | null;
|
|
28
|
+
|
|
29
|
+
export { PIIDetectedError, PII_DENYLIST, assertNoPII, checkNoPII };
|
package/dist/no-pii.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
interface SafeTransmitOptions {
|
|
2
|
+
/** Optional fetch implementation (use the default global fetch
|
|
3
|
+
* unless your runtime injects a polyfill or test double). */
|
|
4
|
+
fetchImpl?: typeof fetch;
|
|
5
|
+
/** Extra HTTP headers (do not include authentication secrets here
|
|
6
|
+
* — Boosten's guard does not authorize them; pass via your own
|
|
7
|
+
* signed token in `headers` deliberately). */
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
/** Optional AbortSignal for cancellation. */
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Send a Boosten payload to a server endpoint, AFTER verifying it
|
|
14
|
+
* contains no PII-shaped fields. Throws `PIIDetectedError` (from
|
|
15
|
+
* `./no-pii`) if the payload would leak personal data; the network
|
|
16
|
+
* call is then NEVER made.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* import { safeTransmit } from "boosten-runtime";
|
|
20
|
+
* await safeTransmit("https://your-endpoint/boosten/ingest", sample);
|
|
21
|
+
*/
|
|
22
|
+
declare function safeTransmit(endpoint: string, payload: unknown, opts?: SafeTransmitOptions): Promise<Response>;
|
|
23
|
+
|
|
24
|
+
export { type SafeTransmitOptions, safeTransmit };
|
package/dist/transmit.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export { BoostenChecklistEntry } from 'boosten-checklist';
|
|
2
|
+
|
|
3
|
+
type BoostenPhase = "mounted" | "afterCommit" | "afterPaint" | "afterIM" | "interactive";
|
|
4
|
+
type BootKind = "cold" | "warm" | "hot" | "first" | "unknown";
|
|
5
|
+
interface PerfEvent {
|
|
6
|
+
/** Unique screen route name — e.g. "team/[id]". */
|
|
7
|
+
screen: string;
|
|
8
|
+
phase: BoostenPhase;
|
|
9
|
+
/** Monotonic ms since boot (use performance.now() or equivalent). */
|
|
10
|
+
t: number;
|
|
11
|
+
/** Optional cause hint — e.g. "press:openProfile". */
|
|
12
|
+
cause?: string;
|
|
13
|
+
/** Optional payload (cell counts, image counts, etc.). */
|
|
14
|
+
meta?: Record<string, number | string | boolean>;
|
|
15
|
+
}
|
|
16
|
+
interface ScreenScoreBreakdown {
|
|
17
|
+
ttffMs: number | null;
|
|
18
|
+
ttiMs: number | null;
|
|
19
|
+
fidMs: number | null;
|
|
20
|
+
ttffScore: number;
|
|
21
|
+
ttiScore: number;
|
|
22
|
+
fidScore: number;
|
|
23
|
+
composite: number;
|
|
24
|
+
rating: "good" | "needs-work" | "poor";
|
|
25
|
+
}
|
|
26
|
+
interface PerfSample {
|
|
27
|
+
/** Anonymous device class — never user-identifying. */
|
|
28
|
+
appVersion: string;
|
|
29
|
+
screen: string;
|
|
30
|
+
ts: number;
|
|
31
|
+
bootKind: BootKind;
|
|
32
|
+
score: number;
|
|
33
|
+
perception?: number;
|
|
34
|
+
ttiMs?: number;
|
|
35
|
+
ttffMs?: number;
|
|
36
|
+
fidMs?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type { BoostenPhase, BootKind, PerfEvent, PerfSample, ScreenScoreBreakdown };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "./chunk-6F4PWJZI.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "boosten-runtime",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "React Native performance runtime: tracker hook, frame sampler, boot ladder, novel detectors, score formula. Zero-config, in-memory by default.",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./types": {
|
|
13
|
+
"types": "./dist/types.d.ts",
|
|
14
|
+
"default": "./dist/types.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": ">=18",
|
|
27
|
+
"react-native": ">=0.74"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"@react-native-async-storage/async-storage": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"boosten-checklist": "^0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"tsup": "^8.5.0"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"react-native",
|
|
42
|
+
"performance",
|
|
43
|
+
"monitoring",
|
|
44
|
+
"boosten",
|
|
45
|
+
"tti",
|
|
46
|
+
"ttff"
|
|
47
|
+
],
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/boosten/boosten.git",
|
|
52
|
+
"directory": "lib/boosten-runtime"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup src/index.ts src/types.ts src/no-pii.ts src/transmit.ts --format esm --dts --clean --out-dir dist --tsconfig tsconfig.build.json",
|
|
56
|
+
"test": "node --test src/no-pii.test.ts src/transmit.test.ts"
|
|
57
|
+
},
|
|
58
|
+
"main": "./dist/index.js",
|
|
59
|
+
"types": "./dist/index.d.ts",
|
|
60
|
+
"bugs": {
|
|
61
|
+
"url": "https://github.com/boosten/boosten/issues"
|
|
62
|
+
},
|
|
63
|
+
"homepage": "https://github.com/boosten/boosten#readme"
|
|
64
|
+
}
|