@typetorch/framework 0.5.7 → 0.6.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/README.md +35 -3
- package/out/analytics/SCHEMA.md +27 -2
- package/out/analytics/join.d.ts +134 -0
- package/out/analytics/join.luau +598 -0
- package/out/analytics/rows.d.ts +14 -0
- package/out/analytics/rows.luau +29 -0
- package/out/analytics/schema.d.ts +10 -1
- package/out/analytics/server.d.ts +10 -0
- package/out/analytics/server.luau +266 -31
- package/out/analytics/settings.d.ts +0 -7
- package/out/analytics/settings.luau +22 -0
- package/out/devtools/admin-ui.d.ts +6 -1
- package/out/devtools/admin-ui.luau +3 -0
- package/out/devtools/palette-commands.d.ts +4 -2
- package/out/devtools/palette-commands.luau +223 -24
- package/out/devtools/palette-rules.d.ts +27 -0
- package/out/devtools/palette-rules.luau +118 -0
- package/out/devtools/sha256.d.ts +1 -8
- package/out/devtools/sha256.luau +15 -89
- package/out/index.d.ts +1 -0
- package/out/kernel.d.ts +7 -0
- package/out/notifications/api.d.ts +59 -0
- package/out/notifications/api.luau +17 -0
- package/out/notifications/client.d.ts +22 -0
- package/out/notifications/client.luau +205 -0
- package/out/notifications/index.d.ts +8 -0
- package/out/notifications/init.luau +34 -0
- package/out/notifications/protocol.d.ts +163 -0
- package/out/notifications/protocol.luau +604 -0
- package/out/notifications/server.d.ts +74 -0
- package/out/notifications/server.luau +870 -0
- package/out/notifications/token.d.ts +57 -0
- package/out/notifications/token.luau +349 -0
- package/out/runtime/start.luau +5 -2
- package/out/typetorch.d.ts +9 -0
- package/out/typetorch.luau +40 -3
- package/out/util/sha256.d.ts +8 -0
- package/out/util/sha256.luau +91 -0
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -360,6 +360,30 @@ TypeTorch.runDetached(() => store.StartSessionAsync(`Player_${userId}`)).then((p
|
|
|
360
360
|
run at once per server (then it throws); one past 60 s is logged and flagged in Server > Status. Older kernels
|
|
361
361
|
throw "needs kernel 0.3.8; use the DataHost job queue (Player data guide)": the other option, which works on every
|
|
362
362
|
kernel. `features.runDetached` tells which one this server has.
|
|
363
|
+
- **`notifications()`** (plans/29): native Roblox experience notifications through the TypeTorch backend (`backend.url`
|
|
364
|
+
/ `backend.key`, the launch-token keys in `backend.notify` from `typetorch notify key rotate`; no kernel change).
|
|
365
|
+
The game says why a player should hear from it, the backend decides when and sends, the framework verifies the tap:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
const notify = TypeTorch.notifications();
|
|
369
|
+
// Server: intents (upsert by user + kind + key; offline players by UserId), batched and swap-safe.
|
|
370
|
+
notify.want(userIdOrPlayer, "gift_new", { key: letterId, until: expiresAt, params: { gift: "5 coins" }, data: `mail:${letterId}` });
|
|
371
|
+
notify.drop(userIdOrPlayer, "streak", "20034", "played"); // no key: every key of the kind
|
|
372
|
+
// The kinds the player switched off in your settings: REPLACES their set ([] = all on), at most 32 names. The backend
|
|
373
|
+
// skips those intents whoever issued them (pref_off) and keeps them, so switching a kind back on resumes it.
|
|
374
|
+
notify.setOff(player, ["daily", "quests"]);
|
|
375
|
+
trove.add(notify.onOpen((player, open) => openMail(player, open.data))); // open: { nid, kind, data, ageSeconds, first }
|
|
376
|
+
// Client: Roblox's opt-in prompt, once per session (show your own explainer first). Yields until it closes.
|
|
377
|
+
const result = TypeTorch.notifications().promptOptIn("streak_2"); // "shown" | "unavailable" | "error" | "busy" | "already"
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Kinds are declared in typetorch.json `notifications` and pushed with the CLI. Intents go to
|
|
381
|
+
`<backend.url>/v1/notify/intents` every `flushSeconds` (and at 200); presence (`online` on join and every 3 h, `offline` on leave)
|
|
382
|
+
rides along while `backend.notify` is set. A join with `tt1n.` launch data is verified on the server (HMAC, the
|
|
383
|
+
player's UserId, 14 days; the previous key for 14 days after a rotation): `onOpen` gets only verified data, also for
|
|
384
|
+
players who joined before the callback connected. Rows (`kind = notify`): `want`, `drop`, `optin_check`,
|
|
385
|
+
`optin_shown`, `optin_closed`, `open`, `open_bad`, `played` (src/analytics/SCHEMA.md); the join row gets
|
|
386
|
+
`from = "notification"`. Without backend settings (Studio) nothing is posted; rows are still written.
|
|
363
387
|
- **Edit mode** (UI Labs stories, no kernel): `running` is false, identity has defaults, `persist` keeps a local table,
|
|
364
388
|
events never fire, and the server-only reads throw.
|
|
365
389
|
|
|
@@ -520,7 +544,7 @@ const variant = analytics.experiment("onboarding", ["short", "long"]); // same a
|
|
|
520
544
|
when the player is alone) intersected with the players there, not a web call per player.
|
|
521
545
|
|
|
522
546
|
**Settings, framework 0.4.0 / kernel 0.4.0 (plans/21):** the record's `backend` = `{ url, key, analytics?: { flushSeconds,
|
|
523
|
-
recordShare } }` (`typetorch backend setup`) is the whole sink: DuckDB at `<url>/v1/ingest` with the one key (Basin
|
|
547
|
+
recordShare, joinLaunchPrefix, joinData } }` (`typetorch backend setup`) is the whole sink: DuckDB at `<url>/v1/ingest` with the one key (Basin
|
|
524
548
|
identities to `<url>/v1/identity`). The old `analytics` value below is read while `backend` is missing, and stays the
|
|
525
549
|
way to a Basin sink. The engine also tells the kernel each player's pid (`setAnalyticsId`: the kernel's error reports
|
|
526
550
|
count affected players by pid, never by name) and counts its own DataStore and HTTP requests for the budget view (dev
|
|
@@ -543,6 +567,12 @@ there are no settings unless the game passes `new AnalyticsEngine({ settings })`
|
|
|
543
567
|
Basin Pipelines Send permission, if the stream requires authentication). Without `recordings` nothing is recorded.
|
|
544
568
|
The streams' schemas must match SCHEMA.md exactly: Basin drops rows that don't, silently.
|
|
545
569
|
- `duckdb`: one `POST <events>` with `{"events":[...],"recordings":[...]}`, gzip, `Authorization: Bearer <token>`.
|
|
570
|
+
- Join row settings (also in `backend.analytics`):
|
|
571
|
+
|
|
572
|
+
| Setting | Default | Meaning |
|
|
573
|
+
|---|---|---|
|
|
574
|
+
| `joinData` | `"full"` | `"full"`: the join row's summary props plus `jd`, a privacy-safe copy of the whole `GetJoinData()` (at most 1 KB: launch data up to 200 bytes, place / game ids, the join context as text, the TeleportData's size, type and keys but never its content, Members / referrer / followed user as pids or `"other"`, unknown fields by type; plans/30). `"summary"`: no `jd`. `"off"`: no `jd` and only the join props from before plans/29 (`from`, `ctx`, `tp`, `party`, age, prem, country, friends, ret). Anything else warns and means `"full"`. Games that put personal data in launch data should use `"summary"` |
|
|
575
|
+
| `joinLaunchPrefix` | `false` | `true`: the summary `launch` keeps the first 32 characters of the game's own launch data (`jd.LaunchData` follows `joinData`, not this) |
|
|
546
576
|
- The token is write-only and never logged. Needs **Allow HTTP Requests** (Game Settings > Security). No key: the
|
|
547
577
|
engine collects but keeps only the newest 1,000 rows until settings appear. Removing the key stops sending, live.
|
|
548
578
|
|
|
@@ -589,8 +619,10 @@ bun run build # rbxtsc --type package -> out/
|
|
|
589
619
|
vouch, live-copy rules), `test-messaging` (messaging and the server list), `test-settings` (`liveConfig` and the
|
|
590
620
|
settings lines), `test-detached` (`runDetached`), `test-dev-access` (the dev-only tools rule and every gate through
|
|
591
621
|
it), `test-remote-debug` (the remote debug ops and their limits), `test-net-inspect` (packet capture),
|
|
592
|
-
`test-explorer-core` (Dex), `test-toolbox-sanitize` (the Creator Store sanitizer)
|
|
593
|
-
fingerprint)
|
|
622
|
+
`test-explorer-core` (Dex), `test-toolbox-sanitize` (the Creator Store sanitizer), `test-sha256` (SHA-256 / HMAC in util and the
|
|
623
|
+
pairing-code fingerprint), `test-notifications` (the launch token with the contract's shared vector, intent checks,
|
|
624
|
+
the queue: batches of 200, rev order, a swap's handover), `test-notify-server` (the notifications server against
|
|
625
|
+
mocked services) and `test-analytics-join` (the join row's metadata; no other player's UserId). `node scripts/check-self-calls.mjs out` flags compiled functions that call themselves on their first
|
|
594
626
|
lines (after `bun run build`).
|
|
595
627
|
- **Package contents:** only `out/` (no `.tsbuildinfo`), `README.md` and `LICENSE`; check with `bun pm pack --dry-run`.
|
|
596
628
|
- **Explorer class icons:** `assets/class-icons.png` (kept locally, not in the repo: it is Roblox's own texture) is
|
package/out/analytics/SCHEMA.md
CHANGED
|
@@ -15,7 +15,7 @@ Integers are written with every digit (never exponent notation).
|
|
|
15
15
|
|---|---|---|
|
|
16
16
|
| `v` | int32 | Row format version: `1` |
|
|
17
17
|
| `t` | int64 | Unix ms on the game server's clock. Client events are corrected by the server (`server now - age - ping/2`) |
|
|
18
|
-
| `kind` | string | `session`, `tech`, `zone`, `funnel`, `purchase`, `currency`, `state`, `experiment`, `custom`, `recording_meta`, `fleet` |
|
|
18
|
+
| `kind` | string | `session`, `tech`, `zone`, `funnel`, `purchase`, `currency`, `state`, `experiment`, `custom`, `recording_meta`, `fleet`, `notify` |
|
|
19
19
|
| `name` | string | Event name (1-64 chars), see below |
|
|
20
20
|
| `pid` | string | Random player id (32 hex chars; `t` + 31 hex = temporary, the player-id store couldn't be read). `""` for server-only events. Never the UserId |
|
|
21
21
|
| `sid` | string | Session id (32 hex), new per join, kept across hot swaps. `""` for server-only events |
|
|
@@ -57,8 +57,11 @@ First-ever-session detail, one row per packed chunk.
|
|
|
57
57
|
|
|
58
58
|
| kind | name | props |
|
|
59
59
|
|---|---|---|
|
|
60
|
-
| session | `join` | `from`: `direct` (Home, search, sorts: Roblox doesn't say), `teleport` (same game), `teleport_game` (another game), `referral` (invite/referral link), `share` (launch data), `follow` (followed someone); `ctx?` (GameJoinContext
|
|
60
|
+
| session | `join` | `from`: `direct` (Home, search, sorts: Roblox doesn't say), `teleport` (same game), `teleport_game` (another game), `referral` (invite/referral link), `notification` (a TypeTorch notification's launch data verified), `share` (other launch data), `follow` (followed someone); join metadata (plans/29): `src_place?`, `src_game?` (SourcePlaceId / SourceGameId), `ref?` / `follow?` (the referrer / followed user as their pid when this game knows them, else `"other"`: never a UserId; players in the server are free, others cost one DataStore read each, at most 2 per join and only with read budget to spare), `party?` (players teleported along) + `party_pids?` (the pids of those this game knows, at most 10), `ctx?`, `ctx_item?`, `ctx_asset?`, `ctx_outfit?`, `ctx_asset_type?` (GameJoinContext JoinSource / ItemType / AssetId / OutfitId / AssetType), `launch?` (`notification` + `nid`, `nk` (kind); `tt_bad` + `launch_bad` (`format`, `sig`, `user`, `old`); or the game's own launch data: `launch_len`, plus its first 32 chars in `launch` only with the settings' `joinLaunchPrefix`), `tp?` + `tp_bytes?` (TeleportData present, its JSON size; never the content), `age_days` (exact account age), `locale?`, `verified?` (HasVerifiedBadge), `membership` (MembershipType name); `age` (account age: `<1d`, `1-7d`, `7-30d`, `30-90d`, `90-365d`, `1-3y`, `3y+`); `prem` (Premium); `country?` (ISO code); `friends` (how many of the player's friends are in the server: one friend-list lookup per join, at most 10 pages, `0` when it fails); `ret` (days since the last visit, `-1` first visit or unknown); `late?` (the engine started over 60 s after the join); `jd?` (plans/30, with the settings' `joinData` `"full"`, the default: the raw join data, below). With `joinData` `"off"` only `from`, `ctx`, `tp`, `party`, `age`, `prem`, `country`, `friends`, `ret`, `late` are written. `t` is the join time |
|
|
61
61
|
| session | `device` (src client) | `dev?` (the device class, as the `dev` column; frameworks after 0.4.2, older ones leave it out), `input` (`kbm`, `touch`, `gamepad`, `vr`, `unknown`), `w`, `h` (viewport points), `touch`, `kb`, `mouse`, `pad`, `vr`. Once per session. The class (Roblox gives game scripts no OS): `vr` when VRService.VREnabled; `console` when GuiService:IsTenFootInterface(); touch without a keyboard: `tablet` when the viewport's short side is 600 points or more, else `phone` (the first hello waits up to 2 s for a real viewport); a keyboard or mouse: `desktop` (touch laptops, tablets with a keyboard); else `unknown` |
|
|
62
|
+
| notify | `want`, `drop` | Notifications (plans/29), written by the game server: `k` (kind), `key?` (first 12 hex of SHA-256 of the intent's key; never the key), `at?`, `until?`, `offline` (want: the user isn't in this server; the row then has no pid), `why?` (drop) |
|
|
63
|
+
| notify | `optin_check`, `optin_shown`, `optin_closed` | The client's opt-in prompt, checked and written by the server (at most 3 of each per session): `where`, `can` (check), `err?` (`timeout`, `swap` or the error's first 64 chars), `secs` (closed: how long it was up) |
|
|
64
|
+
| notify | `open`, `open_bad`, `played` | A join from a notification: `open` (`nid`, `k`, `age` (secs since sent), `first`), `open_bad` (`why`: `format`, `sig`, `user`, `old`), `played` (`nid`, `k`, `secs`: the opened session's length, at its end). Backend rows of the same kind (`planned`, `sent`, ...) come from `src = "backend"` |
|
|
62
65
|
| session | `leave` | `secs` (session length), `why`: `left`, `shutdown`, `gone` (left while no generation ran the engine), `swap60?` (left within 60 s of a hot swap) |
|
|
63
66
|
| tech | `server` | Every `techEvery` s: `fps` (physics), `hb` (Heartbeat rate), `mem` (MB, 10s), `players`, `ping50?`, `ping90?` (ms), `q`, `qr` (queued events, recordings), `dropped`, `sent`, `fails`, `health?`, `errors?` (kernel 0.3.2 health) |
|
|
64
67
|
| tech | `client` (src client) | Every `techEvery` s: `fps`, `mem` (MB, 10s), `ping?` (ms) |
|
|
@@ -159,9 +162,31 @@ repo's `bun run local` does it). Every new signed copy applies at once, so dials
|
|
|
159
162
|
| `experiments` | none | Per experiment: `active: false` (everyone gets the first variant, not stamped), `weights` (per variant, in the game's order), `variant` (force one), `players` (`{ [UserId or pid]: variant }`, at most 200: forces one player's variant over everything else, stamped `forced`; each client's hello gets only its own entry). The CLI writes them: `typetorch experiment` |
|
|
160
163
|
| `identity` | the settings' `fleet` url + `/v1/identity` | basin: where identity rows go (below); none and no `fleet` in the settings: not sent. duckdb: unused |
|
|
161
164
|
| `identityToken` | the settings' `fleet` token | basin: the token for `identity`. Never logged |
|
|
165
|
+
| `joinData` | `"full"` | `"full"`: the join row carries `jd` (below); `"summary"`: no `jd`; `"off"`: no `jd` and only the pre-plans/29 join props (no pid lookups). Anything else is an error line (warned) and means `"full"`. In `backend.analytics` (or the old `analytics` value) |
|
|
166
|
+
| `joinLaunchPrefix` | false | true: the join row's `launch` keeps the first 32 characters of the game's own launch data (off: only `launch_len`). Set it in `backend.analytics` (or the old `analytics` value) |
|
|
162
167
|
|
|
163
168
|
Unknown fields are ignored. Settings errors are warned once (never with the token).
|
|
164
169
|
|
|
170
|
+
## The raw join data (`jd`, plans/30)
|
|
171
|
+
|
|
172
|
+
The `session` / `join` row's `jd` prop mirrors `Player:GetJoinData()` field by field, privacy-safe, at most 1024 bytes of
|
|
173
|
+
JSON (written by the framework's own encoder, so an empty table stays `{}` where the shape says object):
|
|
174
|
+
|
|
175
|
+
| field | content |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `keys` | The top-level keys Roblox returned, sorted (each cut to 32 characters) |
|
|
178
|
+
| `LaunchData?`, `LaunchDataLen?` | The launch data cut to 200 bytes at a UTF-8 boundary, and its full byte length; present iff the key is |
|
|
179
|
+
| `SourcePlaceId?`, `SourceGameId?` | Numbers, present iff the keys are |
|
|
180
|
+
| `GameJoinContext?` | Present iff the key is: every field as text (EnumItems by name), `{}` when empty |
|
|
181
|
+
| `TeleportData?` | `{ bytes?, type, keys? }`: its JSON size, type and top-level keys (at most 20, 32 characters each, sorted); never the content |
|
|
182
|
+
| `Members?` | `{ count, pids? }`: each member's pid when this game knows them (players in the server, or one of the join's at most 2 budget-checked DataStore reads shared with the summary props), else `"other"`; the joining player by their own pid |
|
|
183
|
+
| `ReferredByPlayerId?`, `FollowUserId?` | pid or `"other"`; absent for 0 |
|
|
184
|
+
| `other?` | Any other top-level key: its type name only, never the value |
|
|
185
|
+
| `trunc?` | `true` when cut to fit: TeleportData keys first (then the list), Members pids (then the list), LaunchData (shorter), GameJoinContext values (64 each), `other` |
|
|
186
|
+
|
|
187
|
+
No other player's UserId is ever in `jd`. Example (Roblox dropped a notification's launch data):
|
|
188
|
+
`{"keys":["GameJoinContext","LaunchData"],"LaunchData":"","LaunchDataLen":0,"GameJoinContext":{}}`.
|
|
189
|
+
|
|
165
190
|
## Transport
|
|
166
191
|
|
|
167
192
|
- **basin:** `POST <events>` with a JSON array of event rows and `POST <recordings>` with a JSON array of recording
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `session` / `join` row's join metadata (plans/29 "Native join metadata"): every field of `Player:GetJoinData()`
|
|
3
|
+
* and the player facts, as props. The engine reads the data (enum values as their names, the TeleportData's JSON size,
|
|
4
|
+
* other players' pids looked up beforehand) and this builds the props.
|
|
5
|
+
*
|
|
6
|
+
* Privacy: no other player's UserId ever lands in a prop. Referrer, followed user and party members become pids (the
|
|
7
|
+
* game's pseudonymous ids) when this game knows them, else "other" (ref / follow) or nothing (party_pids).
|
|
8
|
+
*
|
|
9
|
+
* Pure: no imports and no services, so it runs under Lune (scripts/test-analytics-join.luau).
|
|
10
|
+
*/
|
|
11
|
+
/** What the engine reads from GetJoinData (enums already turned into names). */
|
|
12
|
+
export interface JoinDataLike {
|
|
13
|
+
SourcePlaceId?: number;
|
|
14
|
+
SourceGameId?: number;
|
|
15
|
+
ReferredByPlayerId?: number;
|
|
16
|
+
Members?: number[];
|
|
17
|
+
/** Whether TeleportData was present (its content never leaves the server). */
|
|
18
|
+
HasTeleportData?: boolean;
|
|
19
|
+
LaunchData?: string;
|
|
20
|
+
GameJoinContext?: {
|
|
21
|
+
JoinSource?: string;
|
|
22
|
+
ItemType?: string;
|
|
23
|
+
AssetId?: string;
|
|
24
|
+
OutfitId?: string;
|
|
25
|
+
AssetType?: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** The joining player and this server. */
|
|
29
|
+
export interface JoinFacts {
|
|
30
|
+
userId: number;
|
|
31
|
+
/** game.GameId (a teleport from another game is `teleport_game`). */
|
|
32
|
+
gameId: number;
|
|
33
|
+
followUserId: number;
|
|
34
|
+
/** Player.AccountAge (days). */
|
|
35
|
+
accountAge: number;
|
|
36
|
+
locale?: string;
|
|
37
|
+
verified?: boolean;
|
|
38
|
+
/** Enum.MembershipType name. */
|
|
39
|
+
membership?: string;
|
|
40
|
+
/** The TeleportData's JSON size in bytes (undefined: none or not encodable). */
|
|
41
|
+
tpBytes?: number;
|
|
42
|
+
}
|
|
43
|
+
/** The launch data's verdict (notifications/token.ts verifyToken): undefined when it isn't a TypeTorch token. */
|
|
44
|
+
export type LaunchVerdict = {
|
|
45
|
+
ok: true;
|
|
46
|
+
nid: string;
|
|
47
|
+
kind: string;
|
|
48
|
+
} | {
|
|
49
|
+
ok: false;
|
|
50
|
+
why: string;
|
|
51
|
+
} | undefined;
|
|
52
|
+
export interface JoinOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Keep the first LAUNCH_KEEP chars of the game's own launch data in `launch` (the analytics settings'
|
|
55
|
+
* `joinLaunchPrefix`, default off: launch data can carry anything a link maker put there). `launch_len` is always kept.
|
|
56
|
+
*/
|
|
57
|
+
launchPrefix?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The analytics settings' `joinData` (plans/30). "off": only what the join row carried before plans/29 (`from`,
|
|
60
|
+
* `ctx`, `tp`, `party`; the engine adds age, prem, country, friends, ret). "full" / "summary": every summary prop.
|
|
61
|
+
*/
|
|
62
|
+
mode?: JoinDataMode;
|
|
63
|
+
}
|
|
64
|
+
/** `joinData` (plans/30): "full" (default: the summary props + `jd`), "summary" (no `jd`), "off" (see JoinOptions.mode). */
|
|
65
|
+
export type JoinDataMode = "full" | "summary" | "off";
|
|
66
|
+
export declare const JOIN_DATA_MODES: ReadonlyArray<JoinDataMode>;
|
|
67
|
+
/** Party pids kept at most. */
|
|
68
|
+
export declare const PARTY_PIDS_MAX = 10;
|
|
69
|
+
/** The game's own launch data: this many characters are kept. */
|
|
70
|
+
export declare const LAUNCH_KEEP = 32;
|
|
71
|
+
/** UserIds whose pids the row wants (referrer, followed user, party members), without the player and duplicates. */
|
|
72
|
+
export declare function wantedUserIds(data: JoinDataLike | undefined, facts: JoinFacts): number[];
|
|
73
|
+
/**
|
|
74
|
+
* The join props. `pidOf(userId)` answers a known pid (a player in this server, or one this game's records know) or
|
|
75
|
+
* undefined.
|
|
76
|
+
*/
|
|
77
|
+
export declare function joinProps(data: JoinDataLike | undefined, facts: JoinFacts, pidOf: (userId: number) => string | undefined, launch: LaunchVerdict, options?: JoinOptions): Record<string, unknown>;
|
|
78
|
+
/** Bytes of the `jd` JSON at most (props hold 4 KB; the join row's other props stay well under the rest). */
|
|
79
|
+
export declare const JD_MAX_BYTES = 1024;
|
|
80
|
+
/** LaunchData bytes kept (Roblox's own limit for launch data). */
|
|
81
|
+
export declare const JD_LAUNCH_MAX = 200;
|
|
82
|
+
/** TeleportData: top-level keys listed at most, and characters per key. */
|
|
83
|
+
export declare const JD_TP_KEYS_MAX = 20;
|
|
84
|
+
export declare const JD_KEY_CHARS = 32;
|
|
85
|
+
/** GameJoinContext values are cut to this when `jd` is over the cap. */
|
|
86
|
+
export declare const JD_CONTEXT_CUT = 64;
|
|
87
|
+
/** `text` cut to at most `max` bytes without splitting a UTF-8 sequence. */
|
|
88
|
+
export declare function cutUtf8(text: string, max: number): string;
|
|
89
|
+
interface JdTeleport {
|
|
90
|
+
bytes?: number;
|
|
91
|
+
type: string;
|
|
92
|
+
keys?: string[];
|
|
93
|
+
}
|
|
94
|
+
interface JdMembers {
|
|
95
|
+
count: number;
|
|
96
|
+
pids?: string[];
|
|
97
|
+
}
|
|
98
|
+
/** The `jd` value (see plans/30-join-data-raw.md for the shape). */
|
|
99
|
+
export interface JoinDataJson {
|
|
100
|
+
keys: string[];
|
|
101
|
+
LaunchData?: string;
|
|
102
|
+
LaunchDataLen?: number;
|
|
103
|
+
SourcePlaceId?: number;
|
|
104
|
+
SourceGameId?: number;
|
|
105
|
+
GameJoinContext?: Record<string, string>;
|
|
106
|
+
TeleportData?: JdTeleport;
|
|
107
|
+
Members?: JdMembers;
|
|
108
|
+
ReferredByPlayerId?: string;
|
|
109
|
+
FollowUserId?: string;
|
|
110
|
+
other?: Record<string, string>;
|
|
111
|
+
trunc?: boolean;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* `Player:GetJoinData()` field by field, privacy-safe (plans/30): the launch data (200 bytes), place / game ids, the
|
|
115
|
+
* join context as text, the TeleportData's size, type and top-level keys (never its content), Members / referrer /
|
|
116
|
+
* followed user as pids from `pidOf` (the same lookups as the summary props) else "other", and unknown keys by type
|
|
117
|
+
* only. At most JD_MAX_BYTES of JSON as `encodeJoinData` writes it (`encode`: HttpService.JSONEncode in game, only to
|
|
118
|
+
* size the TeleportData; `quote`: a string as JSON), cut in the plan's order with `trunc`. Not a table (GetJoinData
|
|
119
|
+
* failed): undefined.
|
|
120
|
+
*/
|
|
121
|
+
export declare function joinDataJson(raw: unknown, followUserId: number, pidOf: (userId: number) => string | undefined, encode: (value: unknown) => string, quote: (text: string) => string): JoinDataJson | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* `jd` as JSON text, field order fixed. Written here rather than by HttpService.JSONEncode, which turns an empty table
|
|
124
|
+
* into `[]`: the plan's `GameJoinContext: {}` (present but empty, the evidence case) stays an object, and lists stay
|
|
125
|
+
* lists.
|
|
126
|
+
*/
|
|
127
|
+
export declare function encodeJoinData(jd: JoinDataJson, quote: (text: string) => string): string;
|
|
128
|
+
/**
|
|
129
|
+
* Cuts `jd` to JD_MAX_BYTES of JSON, in the plan's order: TeleportData.keys (then the field), Members.pids (then the
|
|
130
|
+
* field), LaunchData (shorter), GameJoinContext values (each to 64), `other` (entry by entry); `trunc: true` once cut.
|
|
131
|
+
* Still over (an absurd join context): only `keys` (shortened), the ids, LaunchDataLen and `trunc` stay.
|
|
132
|
+
*/
|
|
133
|
+
export declare function capJoinData(jd: JoinDataJson, quote: (text: string) => string): JoinDataJson;
|
|
134
|
+
export {};
|