@diffenderfer-games/hub 0.0.0-stage → 2.0.0-next.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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +191 -3
- package/dist/auto.d.ts +36 -0
- package/dist/auto.d.ts.map +1 -0
- package/dist/auto.js +27 -0
- package/dist/auto.js.map +1 -0
- package/dist/daily.d.ts +2 -0
- package/dist/daily.js +1 -0
- package/dist/doctor/cli.mjs +268 -0
- package/dist/doctor/cli.mjs.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +6 -0
- package/dist/input.d.ts +2 -0
- package/dist/input.js +3 -0
- package/dist/mp.d.ts +4 -0
- package/dist/mp.js +1 -0
- package/dist/race.d.ts +4 -0
- package/dist/race.js +1 -0
- package/dist/rooms.d.ts +4 -0
- package/dist/rooms.js +1 -0
- package/dist/shared/bridge.js +209 -0
- package/dist/shared/bridge.js.map +1 -0
- package/dist/shared/client-version.gen.js +7 -0
- package/dist/shared/client-version.gen.js.map +1 -0
- package/dist/shared/daily.d.ts +123 -0
- package/dist/shared/daily.d.ts.map +1 -0
- package/dist/shared/daily.js +191 -0
- package/dist/shared/daily.js.map +1 -0
- package/dist/shared/dom.js +48 -0
- package/dist/shared/dom.js.map +1 -0
- package/dist/shared/globals.js +102 -0
- package/dist/shared/globals.js.map +1 -0
- package/dist/shared/index.d.ts +706 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/input.d.ts +730 -0
- package/dist/shared/input.d.ts.map +1 -0
- package/dist/shared/input.js +2718 -0
- package/dist/shared/input.js.map +1 -0
- package/dist/shared/latest-state.js +17 -0
- package/dist/shared/latest-state.js.map +1 -0
- package/dist/shared/mp.d.ts +9 -0
- package/dist/shared/mp.d.ts.map +1 -0
- package/dist/shared/mp.js +30 -0
- package/dist/shared/mp.js.map +1 -0
- package/dist/shared/multiplayer-types.d.ts +47 -0
- package/dist/shared/multiplayer-types.d.ts.map +1 -0
- package/dist/shared/page-stack.js +207 -0
- package/dist/shared/page-stack.js.map +1 -0
- package/dist/shared/race-types.d.ts +126 -0
- package/dist/shared/race-types.d.ts.map +1 -0
- package/dist/shared/race.d.ts +9 -0
- package/dist/shared/race.d.ts.map +1 -0
- package/dist/shared/race.js +53 -0
- package/dist/shared/race.js.map +1 -0
- package/dist/shared/room-types.d.ts +125 -0
- package/dist/shared/room-types.d.ts.map +1 -0
- package/dist/shared/rooms.d.ts +9 -0
- package/dist/shared/rooms.d.ts.map +1 -0
- package/dist/shared/rooms.js +62 -0
- package/dist/shared/rooms.js.map +1 -0
- package/dist/shared/runtime-opener.js +84 -0
- package/dist/shared/runtime-opener.js.map +1 -0
- package/dist/shared/social.d.ts +148 -0
- package/dist/shared/social.d.ts.map +1 -0
- package/dist/shared/social.js +73 -0
- package/dist/shared/social.js.map +1 -0
- package/dist/shared/src.js +2164 -0
- package/dist/shared/src.js.map +1 -0
- package/dist/shared/wire-types.d.ts +51 -0
- package/dist/shared/wire-types.d.ts.map +1 -0
- package/dist/social.d.ts +6 -0
- package/dist/social.js +3 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +0 -0
- package/package.json +89 -6
- package/src/auto.ts +39 -0
- package/src/core/connection.ts +299 -0
- package/src/core/errors.ts +147 -0
- package/src/core/game-data.ts +38 -0
- package/src/core/hub-economy.ts +179 -0
- package/src/core/hub-progress.ts +205 -0
- package/src/core/hub.ts +131 -0
- package/src/core/menu-control.ts +61 -0
- package/src/core/offline/events.ts +50 -0
- package/src/core/offline/favorites.ts +21 -0
- package/src/core/offline/local-store.ts +261 -0
- package/src/core/offline/queue.ts +171 -0
- package/src/core/page.ts +74 -0
- package/src/core/rest/endpoint-types.ts +22 -0
- package/src/core/rest/rest-client.ts +152 -0
- package/src/core/rest/routes.gen.ts +90 -0
- package/src/core/storage/json-guards.ts +74 -0
- package/src/core/storage/key-value.ts +66 -0
- package/src/core/ticket.ts +43 -0
- package/src/core/types.ts +134 -0
- package/src/core/version/client-version.gen.ts +3 -0
- package/src/core/version/hub-version.ts +62 -0
- package/src/core/version/semver.ts +97 -0
- package/src/daily/daily-system.ts +161 -0
- package/src/daily/page-daily.ts +36 -0
- package/src/daily/types.ts +67 -0
- package/src/daily.ts +10 -0
- package/src/doctor/checks.ts +78 -0
- package/src/doctor/cli.ts +98 -0
- package/src/doctor/hub-version-fetch.ts +43 -0
- package/src/doctor/tsconfig.json +12 -0
- package/src/globals.ts +115 -0
- package/src/index.ts +50 -0
- package/src/input/device-state.ts +146 -0
- package/src/input/edges.ts +55 -0
- package/src/input/gamepad.ts +61 -0
- package/src/input/input-events.ts +32 -0
- package/src/input/input-registry.ts +164 -0
- package/src/input/input-system.ts +393 -0
- package/src/input/legacy-controls.ts +64 -0
- package/src/input/nav-state.ts +191 -0
- package/src/input/navigator.ts +171 -0
- package/src/input/on-screen-controls.ts +101 -0
- package/src/input/page-control.ts +43 -0
- package/src/input/page-input.ts +193 -0
- package/src/input/player-remaps.ts +77 -0
- package/src/input/source-values.ts +116 -0
- package/src/input/spatial.ts +47 -0
- package/src/input/types.ts +331 -0
- package/src/input/zero-config.ts +177 -0
- package/src/input.ts +22 -0
- package/src/mp.ts +10 -0
- package/src/overlay/controls-panel.ts +163 -0
- package/src/overlay/dom.ts +46 -0
- package/src/overlay/layout-clusters.ts +162 -0
- package/src/overlay/layout-geometry.ts +136 -0
- package/src/overlay/layout.ts +203 -0
- package/src/overlay/listener-scope.ts +23 -0
- package/src/overlay/on-screen-keyboard.ts +137 -0
- package/src/overlay/overlay.ts +208 -0
- package/src/overlay/styles.ts +54 -0
- package/src/overlay/virtual-controls.ts +262 -0
- package/src/race.ts +10 -0
- package/src/realtime/bridge.ts +121 -0
- package/src/realtime/cross-bundle.ts +29 -0
- package/src/realtime/fallback-notice.ts +48 -0
- package/src/realtime/hub-link.ts +65 -0
- package/src/realtime/latest-state.ts +33 -0
- package/src/realtime/multiplayer-forwarder.ts +28 -0
- package/src/realtime/multiplayer-types.ts +52 -0
- package/src/realtime/page-runtime.ts +60 -0
- package/src/realtime/race-forwarder.ts +59 -0
- package/src/realtime/race-types.ts +132 -0
- package/src/realtime/room-types.ts +125 -0
- package/src/realtime/rooms-forwarder.ts +79 -0
- package/src/realtime/runtime-api.ts +49 -0
- package/src/realtime/runtime-opener.ts +61 -0
- package/src/realtime/social-forwarder.ts +72 -0
- package/src/realtime/social-types.ts +78 -0
- package/src/realtime/types.ts +53 -0
- package/src/realtime/wire-types.ts +72 -0
- package/src/rooms.ts +10 -0
- package/src/social.ts +18 -0
- package/src/types.ts +13 -0
- package/src/uistack/page-stack.ts +45 -0
- package/src/uistack/play-suspension.ts +82 -0
- package/src/uistack/types.ts +76 -0
- package/src/uistack/ui-stack.ts +144 -0
package/dist/input.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as isPageInputControl, c as layoutControls, i as parseInputOverrides, l as boxesTouch, n as getInput, o as InputSystem, r as isInputSystem, s as Overlay, t as INPUT_SINGLETON_API, u as controlSize } from "./shared/input.js";
|
|
2
|
+
import { n as isUiStack, r as UI_STACK_VERSION, t as getUiStack } from "./shared/page-stack.js";
|
|
3
|
+
export { INPUT_SINGLETON_API, InputSystem, Overlay, UI_STACK_VERSION, boxesTouch, controlSize, getInput, getUiStack, isInputSystem, isPageInputControl, isUiStack, layoutControls, parseInputOverrides };
|
package/dist/mp.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { t as InviteCreated } from "./shared/wire-types.js";
|
|
2
|
+
import { a as JoinInfoEvent, i as InviteResult, n as HubMpApi, r as InviteOptions, t as BusyOptions } from "./shared/multiplayer-types.js";
|
|
3
|
+
import { a as Party, i as LaunchTicket, n as JoinInfo, o as multiplayerForwarder, r as Launch, t as Invite } from "./shared/mp.js";
|
|
4
|
+
export { type BusyOptions, type HubMpApi, type Invite, type InviteCreated, type InviteOptions, type InviteResult, type JoinInfo, type JoinInfoEvent, type Launch, type LaunchTicket, type Party, multiplayerForwarder as mp };
|
package/dist/mp.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as mp } from "./shared/mp.js";
|
package/dist/race.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { a as RaceResultPlayer, i as RaceRecordPlayer, o as RaceStatusEvent, r as RaceHistory } from "./shared/wire-types.js";
|
|
2
|
+
import { a as RaceHistoryQuery, c as RaceRuntimeApi, i as RaceEvents, l as RaceStart, n as RaceCreateOptions, o as RaceHistoryViewOptions, r as RaceDefinition, s as RaceHudPlace, t as HubRaceApi } from "./shared/race-types.js";
|
|
3
|
+
import { a as RaceStatValue, c as Racer, d as RoomInfo, f as raceForwarder, i as RaceResult, l as RacerResult, n as RaceParams, o as RaceState, r as RaceRecord, s as RaceStats, t as RaceOutcome, u as RacerState } from "./shared/race.js";
|
|
4
|
+
export { type HubRaceApi, type RaceCreateOptions, type RaceDefinition, type RaceEvents, type RaceHistory, type RaceHistoryQuery, type RaceHistoryViewOptions, type RaceHudPlace, type RaceOutcome, type RaceParams, type RaceRecord, type RaceRecordPlayer, type RaceResult, type RaceResultPlayer, type RaceRuntimeApi, type RaceStart, type RaceStatValue, type RaceState, type RaceStats, type RaceStatusEvent, type Racer, type RacerResult, type RacerState, type RoomInfo, raceForwarder as race };
|
package/dist/race.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as race } from "./shared/race.js";
|
package/dist/rooms.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { c as RoomChatLine, d as RoomStateChange, l as RoomMessage, u as RoomSeat } from "./shared/wire-types.js";
|
|
2
|
+
import { a as RoomEvents, c as RoomSendOptions, i as RoomCreateOptions, l as RoomState, n as HubRoomsApi, o as RoomListOptions, r as JoinOptions, s as RoomSelf, t as HubRoom, u as RoomStateOptions } from "./shared/room-types.js";
|
|
3
|
+
import { n as roomsForwarder, t as RoomInfo } from "./shared/rooms.js";
|
|
4
|
+
export { type HubRoom, type HubRoomsApi, type JoinOptions, type RoomChatLine, type RoomCreateOptions, type RoomEvents, type RoomInfo, type RoomListOptions, type RoomMessage, type RoomSeat, type RoomSelf, type RoomSendOptions, type RoomState, type RoomStateChange, type RoomStateOptions, roomsForwarder as rooms };
|
package/dist/rooms.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as rooms } from "./shared/rooms.js";
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { a as isObject, i as hasMethods, o as readField, r as _defineProperty, t as LEGACY_GLOBAL_NAMES } from "./globals.js";
|
|
2
|
+
//#region src/realtime/hub-link.ts
|
|
3
|
+
/**
|
|
4
|
+
* What the forwarders need from the core client: its error classes (so a
|
|
5
|
+
* runtime rejection is an `instanceof HubError` in the game) and the direct
|
|
6
|
+
* own-server ticket request. The core client links itself at load with
|
|
7
|
+
* {@link linkRealtimeToHub}; until then plain errors with the same fields are
|
|
8
|
+
* used, so a forwarder imported on its own still works.
|
|
9
|
+
*/
|
|
10
|
+
/** Copy the error-body fields of another bundle's error onto a plain `Error`. */
|
|
11
|
+
function copyAsPlainError(error) {
|
|
12
|
+
if (error instanceof Error) return error;
|
|
13
|
+
const message = readField(error, "message");
|
|
14
|
+
const copy = new Error(typeof message === "string" ? message : String(error));
|
|
15
|
+
for (const key of ["status", "retryAfterMs"]) {
|
|
16
|
+
const field = readField(error, key);
|
|
17
|
+
if (typeof field === "number") copy[key] = field;
|
|
18
|
+
}
|
|
19
|
+
for (const key of [
|
|
20
|
+
"code",
|
|
21
|
+
"reason",
|
|
22
|
+
"hint"
|
|
23
|
+
]) {
|
|
24
|
+
const field = readField(error, key);
|
|
25
|
+
if (typeof field === "string") copy[key] = field;
|
|
26
|
+
}
|
|
27
|
+
return copy;
|
|
28
|
+
}
|
|
29
|
+
let currentLink = {
|
|
30
|
+
toHubError: copyAsPlainError,
|
|
31
|
+
createOfflineError(message) {
|
|
32
|
+
const offline = new Error(message);
|
|
33
|
+
offline.code = "offline";
|
|
34
|
+
return offline;
|
|
35
|
+
},
|
|
36
|
+
getGameServerTicket: () => Promise.reject(/* @__PURE__ */ new Error("Hub: import @diffenderfer-games/hub to request game-server tickets."))
|
|
37
|
+
};
|
|
38
|
+
/** Called once by the core client so forwarder errors use its classes and tickets use its REST client. */
|
|
39
|
+
function linkRealtimeToHub(link) {
|
|
40
|
+
currentLink = link;
|
|
41
|
+
}
|
|
42
|
+
/** The link in effect. */
|
|
43
|
+
function hubLink() {
|
|
44
|
+
return currentLink;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/realtime/runtime-api.ts
|
|
48
|
+
/**
|
|
49
|
+
* The realtime feature version the runtime reports in {@link RealtimeApi.version}.
|
|
50
|
+
* 2 added races; a client feature-detects newer additions with it.
|
|
51
|
+
*/
|
|
52
|
+
const REALTIME_FEATURE_VERSION = 2;
|
|
53
|
+
/**
|
|
54
|
+
* The cross-bundle shape number of the `rt` page singleton (plan §4.4). The
|
|
55
|
+
* runtime registers with it through `claimSingleton`; bump it only when the
|
|
56
|
+
* shape of {@link RealtimeApi} changes incompatibly.
|
|
57
|
+
*/
|
|
58
|
+
const REALTIME_SINGLETON_API = 1;
|
|
59
|
+
/** The window event the runtime fires once it has installed itself. */
|
|
60
|
+
const REALTIME_READY_EVENT = "hub:rt-ready";
|
|
61
|
+
/** The window event the menu fires when the runtime failed to load (its reason is on `window.__HUB_RT_ERROR__`). */
|
|
62
|
+
const REALTIME_FAILED_EVENT = "hub:rt-failed";
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/realtime/page-runtime.ts
|
|
65
|
+
/**
|
|
66
|
+
* Finding the social runtime's realtime API on the page. The runtime owns the
|
|
67
|
+
* `rt` singleton (plan §4.4): it registers on `window.__DG__.rt`, and older
|
|
68
|
+
* runtimes only set the v1 name `window.__HUB_RT__`.
|
|
69
|
+
*/
|
|
70
|
+
/** Whether `candidate` looks like a {@link RealtimeApi} (any bundle's). */
|
|
71
|
+
function isRealtimeApi(candidate) {
|
|
72
|
+
return typeof readField(candidate, "version") === "number" && hasMethods(readField(candidate, "presence"), ["set", "clear"]) && hasMethods(readField(candidate, "social"), [
|
|
73
|
+
"openPlayer",
|
|
74
|
+
"friends",
|
|
75
|
+
"counts",
|
|
76
|
+
"chatMode",
|
|
77
|
+
"on"
|
|
78
|
+
]) && hasMethods(readField(candidate, "mp"), [
|
|
79
|
+
"onLaunch",
|
|
80
|
+
"onJoinInfo",
|
|
81
|
+
"invite",
|
|
82
|
+
"setBusy",
|
|
83
|
+
"party"
|
|
84
|
+
]) && hasMethods(readField(candidate, "rooms"), [
|
|
85
|
+
"create",
|
|
86
|
+
"join",
|
|
87
|
+
"list"
|
|
88
|
+
]) && hasMethods(readField(candidate, "notify"), ["setQuiet"]);
|
|
89
|
+
}
|
|
90
|
+
let hasWarnedAboutApi = false;
|
|
91
|
+
/** Warn once when the runtime registered a different shape number than this client knows. */
|
|
92
|
+
function warnOnApiMismatch(registeredApi) {
|
|
93
|
+
if (registeredApi === 1 || hasWarnedAboutApi) return;
|
|
94
|
+
hasWarnedAboutApi = true;
|
|
95
|
+
console.warn(`[hub] this page's social runtime speaks realtime api ${String(registeredApi)}, this client api ${String(1)}. Fix: npm update @diffenderfer-games/hub`);
|
|
96
|
+
}
|
|
97
|
+
/** The page's realtime API if the runtime has loaded, else `null`. */
|
|
98
|
+
function findRealtimeApi() {
|
|
99
|
+
if (typeof window === "undefined") return null;
|
|
100
|
+
const registered = window.__DG__?.rt;
|
|
101
|
+
if (registered !== void 0 && isRealtimeApi(registered.instance)) {
|
|
102
|
+
warnOnApiMismatch(registered.api);
|
|
103
|
+
return registered.instance;
|
|
104
|
+
}
|
|
105
|
+
const legacy = window[LEGACY_GLOBAL_NAMES.rt];
|
|
106
|
+
return isRealtimeApi(legacy) ? legacy : null;
|
|
107
|
+
}
|
|
108
|
+
/** Whether the hub injected this page (`window.__HUB__`); without it the runtime can never arrive. */
|
|
109
|
+
function isHubInjectedPage() {
|
|
110
|
+
return typeof window !== "undefined" && isObject(readField(window, "__HUB__"));
|
|
111
|
+
}
|
|
112
|
+
/** Why the social runtime failed to load on this page (set by the menu), or `''`. */
|
|
113
|
+
function readRuntimeFailure() {
|
|
114
|
+
if (typeof window === "undefined") return "";
|
|
115
|
+
const failure = readField(window, "__HUB_RT_ERROR__");
|
|
116
|
+
return typeof failure === "string" ? failure.slice(0, 120) : "";
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/realtime/bridge.ts
|
|
120
|
+
/**
|
|
121
|
+
* The bridge from the forwarders to the social runtime, which the menu loads
|
|
122
|
+
* after the page (and which may never load). The forwarders stay thin so a
|
|
123
|
+
* game's bundled copy never goes stale:
|
|
124
|
+
*
|
|
125
|
+
* - listeners are kept here and attached whenever the runtime appears;
|
|
126
|
+
* - "latest state" calls are remembered and replayed when it appears;
|
|
127
|
+
* - calls that return data wait up to {@link RUNTIME_WAIT_MS}, then reject
|
|
128
|
+
* with `OfflineError`;
|
|
129
|
+
* - with no `window.__HUB__` (a page the hub didn't inject) waits end at once.
|
|
130
|
+
*/
|
|
131
|
+
/** How long data and UI calls wait for the social runtime before giving up. */
|
|
132
|
+
const RUNTIME_WAIT_MS = 15e3;
|
|
133
|
+
/** Resolves the runtime's realtime API, waiting for it to load when necessary. */
|
|
134
|
+
var RealtimeBridge = class {
|
|
135
|
+
constructor() {
|
|
136
|
+
_defineProperty(this, "pendingCallbacks", []);
|
|
137
|
+
if (typeof window !== "undefined") window.addEventListener(REALTIME_READY_EVENT, () => {
|
|
138
|
+
this.runPendingCallbacks();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/** The runtime's API if it has loaded. */
|
|
142
|
+
current() {
|
|
143
|
+
return findRealtimeApi();
|
|
144
|
+
}
|
|
145
|
+
/** Run `callback` as soon as the runtime exists (now, or whenever it loads). */
|
|
146
|
+
onReady(callback) {
|
|
147
|
+
const realtime = this.current();
|
|
148
|
+
if (realtime === null) this.pendingCallbacks.push(callback);
|
|
149
|
+
else callback(realtime);
|
|
150
|
+
}
|
|
151
|
+
/** The runtime, waiting up to {@link RUNTIME_WAIT_MS}; rejects with `OfflineError`. */
|
|
152
|
+
whenReady() {
|
|
153
|
+
const realtime = this.current();
|
|
154
|
+
if (realtime !== null) return Promise.resolve(realtime);
|
|
155
|
+
if (!isHubInjectedPage()) return Promise.reject(hubLink().createOfflineError("Online features are not available on this page."));
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
const resolveWhenReady = (loaded) => {
|
|
158
|
+
clearTimeout(timer);
|
|
159
|
+
resolve(loaded);
|
|
160
|
+
};
|
|
161
|
+
const timer = setTimeout(() => {
|
|
162
|
+
this.pendingCallbacks = this.pendingCallbacks.filter((callback) => callback !== resolveWhenReady);
|
|
163
|
+
reject(hubLink().createOfflineError("Online features did not load."));
|
|
164
|
+
}, RUNTIME_WAIT_MS);
|
|
165
|
+
this.pendingCallbacks.push(resolveWhenReady);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/** Attach a listener now or on load; the returned function detaches it. */
|
|
169
|
+
subscribe(attach) {
|
|
170
|
+
let detach = null;
|
|
171
|
+
let isCancelled = false;
|
|
172
|
+
this.onReady((realtime) => {
|
|
173
|
+
if (!isCancelled) detach = attach(realtime);
|
|
174
|
+
});
|
|
175
|
+
return () => {
|
|
176
|
+
isCancelled = true;
|
|
177
|
+
detach?.();
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** Call the runtime (waiting for it) and re-create its errors as the core client's classes. */
|
|
181
|
+
async call(request) {
|
|
182
|
+
return rethrowAsHubError(request(await this.whenReady()));
|
|
183
|
+
}
|
|
184
|
+
runPendingCallbacks() {
|
|
185
|
+
const realtime = this.current();
|
|
186
|
+
if (realtime === null) return;
|
|
187
|
+
const callbacks = this.pendingCallbacks;
|
|
188
|
+
this.pendingCallbacks = [];
|
|
189
|
+
for (const callback of callbacks) try {
|
|
190
|
+
callback(realtime);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error("[hub] social runtime callback failed", error);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
/** The same promise, with rejections re-created as the core client's error classes. */
|
|
197
|
+
async function rethrowAsHubError(pending) {
|
|
198
|
+
try {
|
|
199
|
+
return await pending;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw hubLink().toHubError(error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** The page's one bridge (per client bundle). */
|
|
205
|
+
const pageBridge = new RealtimeBridge();
|
|
206
|
+
//#endregion
|
|
207
|
+
export { readRuntimeFailure as a, REALTIME_READY_EVENT as c, linkRealtimeToHub as d, isRealtimeApi as i, REALTIME_SINGLETON_API as l, rethrowAsHubError as n, REALTIME_FAILED_EVENT as o, findRealtimeApi as r, REALTIME_FEATURE_VERSION as s, pageBridge as t, hubLink as u };
|
|
208
|
+
|
|
209
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","names":[],"sources":["../../src/realtime/hub-link.ts","../../src/realtime/runtime-api.ts","../../src/realtime/page-runtime.ts","../../src/realtime/bridge.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/core/version/client-version.gen.ts
|
|
2
|
+
/** This client's package version, sent as `x-hub-client` on REST calls and `client` in `hello`. */
|
|
3
|
+
const HUB_CLIENT_VERSION = "2.0.0-next.1";
|
|
4
|
+
//#endregion
|
|
5
|
+
export { HUB_CLIENT_VERSION as t };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=client-version.gen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-version.gen.js","names":[],"sources":["../../src/core/version/client-version.gen.ts"],"mappings":";;AAEA,MAAa,qBAAqB"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { f as SchemaOutput } from "./wire-types.js";
|
|
2
|
+
import { DailyCompletion, DailyCompletions, Json, dailyEndpoints } from "@diffenderfer-games/hub-contract";
|
|
3
|
+
//#region src/daily/types.d.ts
|
|
4
|
+
/** The response type of a daily endpoint. */
|
|
5
|
+
type DailyResponse<Name extends keyof typeof dailyEndpoints> = SchemaOutput<(typeof dailyEndpoints)[Name]['cfg']['response']>;
|
|
6
|
+
/** This game's daily window and the player's completions (`GET /games/:slug/daily`). */
|
|
7
|
+
type DailyStatus = DailyResponse<'getGameDailyChallenges'>;
|
|
8
|
+
/** Every daily game and the global standings (`GET /daily`). */
|
|
9
|
+
type DailyAll = DailyResponse<'listDailyChallengesForAllGames'>;
|
|
10
|
+
/** One game's row in {@link DailyAll}. */
|
|
11
|
+
type DailyGameStatus = DailyAll['games'][number];
|
|
12
|
+
/** The answer to starting a day: its seed and the signed token to present on completion. */
|
|
13
|
+
type DailyStartResponse = DailyResponse<'startDailyChallenge'>;
|
|
14
|
+
/** The answer to completing a day: the player's new completion count and rank. */
|
|
15
|
+
type DailyCompleteResponse = DailyResponse<'completeDailyChallenge'>;
|
|
16
|
+
/** The challenge a game's `play` receives: set up and start exactly this day (derive the level from `seed`). */
|
|
17
|
+
interface DailyChallenge {
|
|
18
|
+
/** The day, `YYYY-MM-DD`. */
|
|
19
|
+
day: string;
|
|
20
|
+
/** Deterministic per game and day. */
|
|
21
|
+
seed: number;
|
|
22
|
+
/** The signed token `complete` presents. */
|
|
23
|
+
token: string;
|
|
24
|
+
/** The player already completed this day (a replay). */
|
|
25
|
+
alreadyCompleted?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** What `hub.daily.define` takes. */
|
|
28
|
+
interface DailyDefinition {
|
|
29
|
+
/** Start this challenge now. */
|
|
30
|
+
play: (challenge: DailyChallenge) => void;
|
|
31
|
+
}
|
|
32
|
+
/** Metadata a game records with a completion (`{ score, difficulty, timeMs, … }`). */
|
|
33
|
+
type DailyCompletionMeta = Record<string, Json>;
|
|
34
|
+
/** What the daily system needs from the hub client: the REST calls and its window events. */
|
|
35
|
+
interface DailyHost {
|
|
36
|
+
/** This page's game slug. */
|
|
37
|
+
readonly slug: string | null;
|
|
38
|
+
/** `GET /games/:slug/daily`. */
|
|
39
|
+
status(): Promise<DailyStatus>;
|
|
40
|
+
/** `GET /daily`. */
|
|
41
|
+
all(): Promise<DailyAll>;
|
|
42
|
+
/** `POST /games/:slug/daily/:day/start`. */
|
|
43
|
+
start(day: string): Promise<DailyStartResponse>;
|
|
44
|
+
/** `POST /games/:slug/daily/:day/complete`. */
|
|
45
|
+
complete(day: string, token: string, meta: DailyCompletionMeta): Promise<DailyCompleteResponse>;
|
|
46
|
+
/** Fire a `hub:*` window event (the menu listens). */
|
|
47
|
+
emit(name: string, detail: DailyEventDetail): void;
|
|
48
|
+
}
|
|
49
|
+
/** The detail of the `hub:daily-*` window events. */
|
|
50
|
+
type DailyEventDetail = {
|
|
51
|
+
day: string;
|
|
52
|
+
seed: number;
|
|
53
|
+
alreadyCompleted: boolean;
|
|
54
|
+
} | {
|
|
55
|
+
day: string;
|
|
56
|
+
count: number;
|
|
57
|
+
rank: number | null;
|
|
58
|
+
meta: DailyCompletionMeta;
|
|
59
|
+
} | {
|
|
60
|
+
day: string;
|
|
61
|
+
error: string;
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/daily/daily-system.d.ts
|
|
65
|
+
/** The window event the injected menu fires to start a day on the game's instance. */
|
|
66
|
+
declare const DAILY_START_EVENT = "hub:daily-start";
|
|
67
|
+
/** Daily challenges (`hub.daily`). */
|
|
68
|
+
declare class DailySystem {
|
|
69
|
+
/**
|
|
70
|
+
* The challenge in progress (set when a day starts, cleared on complete).
|
|
71
|
+
* A game's win handler checks it: `if (hub.daily.active) hub.daily.complete(…)`.
|
|
72
|
+
*/
|
|
73
|
+
active: DailyChallenge | null;
|
|
74
|
+
private readonly host;
|
|
75
|
+
private play;
|
|
76
|
+
private pendingDay;
|
|
77
|
+
private isRelaying;
|
|
78
|
+
constructor(host: DailyHost);
|
|
79
|
+
/** Whether a game registered a daily-challenge handler on this page. */
|
|
80
|
+
get isDefined(): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Register the game's challenge runner. Call once at load. If the page was
|
|
83
|
+
* deep-linked with `?daily=DATE` (or a start was requested before this),
|
|
84
|
+
* that day starts now.
|
|
85
|
+
*/
|
|
86
|
+
define(definition: DailyDefinition): void;
|
|
87
|
+
/**
|
|
88
|
+
* Fetch the signed token for `day`, make it active and call the game's
|
|
89
|
+
* `play`. Before `define`, the day is kept and played then, so the menu's
|
|
90
|
+
* own instance never fetches a token; it is also relayed to any other
|
|
91
|
+
* daily system on the page (a client copy that couldn't share this one).
|
|
92
|
+
*/
|
|
93
|
+
startAndPlay(day: string): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Record completion of the active challenge with optional metadata.
|
|
96
|
+
* Resolves with the new completion count and rank, or `null` when no
|
|
97
|
+
* challenge is active or the hub refused.
|
|
98
|
+
*/
|
|
99
|
+
complete(meta?: DailyCompletionMeta): Promise<DailyCompleteResponse | null>;
|
|
100
|
+
/** Ask the page's other daily systems to start `day` (this one ignores its own event). */
|
|
101
|
+
private relayStart;
|
|
102
|
+
/** Abandon the active challenge without recording it (e.g. the player quit). */
|
|
103
|
+
cancel(): void;
|
|
104
|
+
/** This game's window and the player's completions. */
|
|
105
|
+
status(): Promise<DailyStatus>;
|
|
106
|
+
/** Every daily-enabled game and the global standings (for the home hub). */
|
|
107
|
+
all(): Promise<DailyAll>;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/daily/page-daily.d.ts
|
|
111
|
+
/** The cross-bundle shape number of the `daily` singleton. Bump only on an incompatible change. */
|
|
112
|
+
declare const DAILY_SINGLETON_API = 1;
|
|
113
|
+
/** Whether `candidate` is some bundle's daily system (v1 copies included). */
|
|
114
|
+
declare function isDailySystem(candidate: unknown): candidate is DailySystem;
|
|
115
|
+
/**
|
|
116
|
+
* The page-wide daily system, created with `host` if no bundle has made one
|
|
117
|
+
* yet. On a shape mismatch the registered one is kept and this bundle gets a
|
|
118
|
+
* private instance. The answer is kept for this bundle.
|
|
119
|
+
*/
|
|
120
|
+
declare function getDaily(host: DailyHost): DailySystem;
|
|
121
|
+
//#endregion
|
|
122
|
+
export { DailyStatus as _, isDailySystem as a, DailyAll as c, DailyCompletionMeta as d, DailyDefinition as f, DailyStartResponse as g, DailyHost as h, getDaily as i, DailyChallenge as l, DailyGameStatus as m, DailyCompletions as n, DAILY_START_EVENT as o, DailyEventDetail as p, DAILY_SINGLETON_API as r, DailySystem as s, DailyCompletion as t, DailyCompleteResponse as u };
|
|
123
|
+
//# sourceMappingURL=daily.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daily.d.ts","names":[],"sources":["../../src/daily/types.ts","../../src/daily/daily-system.ts","../../src/daily/page-daily.ts"],"mappings":";;;;KAQK,cAAc,0BAA0B,kBAAkB,qBAAqB,gBAAgB;;KAGxF,cAAc;;KAGd,WAAW;;KAGX,kBAAkB;;KAGlB,qBAAqB;;KAGrB,wBAAwB;;UAGnB;;EAEf;;EAEA;;EAEA;;EAEA;;;UAIe;;EAEf,OAAO,WAAW;;;KAIR,sBAAsB,eAAe;;UAGhC;;WAEN;;EAET,UAAU,QAAQ;;EAElB,OAAO,QAAQ;;EAEf,MAAM,cAAc,QAAQ;;EAE5B,SAAS,aAAa,eAAe,MAAM,sBAAsB,QAAQ;;EAEzE,KAAK,cAAc,QAAQ;;;KAIjB;EACN;EAAa;EAAc;;EAC3B;EAAa;EAAe;EAAqB,MAAM;;EACvD;EAAa;;;;;cClDN;;cA0BA;;;;;EAKX,QAAQ;mBACS;UACT;UACA;UACA;EAEI,YAAA,MAAM;;MAiBd;;;;;;EASJ,OAAO,YAAY;;;;;;;EAeb,aAAa,cAAc;;;;;;EAuB3B,SAAS,OAAM,sBAA2B,QAAQ;;UAiBhD;;EAaR;;EAKA,UAAU,QAAQ;;EAKlB,OAAO,QAAQ;;;;;cClJJ;;iBAIG,cAAc,qBAAqB,aAAa;;;;;;iBAWhD,SAAS,MAAM,YAAY"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { i as hasMethods, n as claimSingleton, o as readField, r as _defineProperty } from "./globals.js";
|
|
2
|
+
import "./client-version.gen.js";
|
|
3
|
+
//#region src/daily/daily-system.ts
|
|
4
|
+
/**
|
|
5
|
+
* Daily challenges, client side. A daily-enabled game calls
|
|
6
|
+
* `hub.daily.define({ play })` once at load; `play` receives a
|
|
7
|
+
* {@link DailyChallenge} and starts exactly that challenge. When the player
|
|
8
|
+
* finishes, the game calls `hub.daily.complete({ score, … })`.
|
|
9
|
+
*
|
|
10
|
+
* The system is a page-wide singleton shared by the game's client and the
|
|
11
|
+
* injected menu, so the menu can start a day on the instance the game
|
|
12
|
+
* registered `play` on. Deep links (`/<slug>/?daily=YYYY-MM-DD`) auto-start.
|
|
13
|
+
*/
|
|
14
|
+
const DAY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
15
|
+
/** The window event the injected menu fires to start a day on the game's instance. */
|
|
16
|
+
const DAILY_START_EVENT = "hub:daily-start";
|
|
17
|
+
/** A day key, or `null` when `candidate` isn't one. */
|
|
18
|
+
function asDayKey(candidate) {
|
|
19
|
+
return typeof candidate === "string" && DAY_PATTERN.test(candidate) ? candidate : null;
|
|
20
|
+
}
|
|
21
|
+
/** The day the page was deep-linked to, if any. */
|
|
22
|
+
function readDeepLinkDay() {
|
|
23
|
+
const captured = typeof window === "undefined" ? null : asDayKey(readField(window, "__hubDailyDeep"));
|
|
24
|
+
if (captured !== null || typeof location === "undefined") return captured;
|
|
25
|
+
return asDayKey(new URL(location.href).searchParams.get("daily"));
|
|
26
|
+
}
|
|
27
|
+
/** The message of a thrown value. */
|
|
28
|
+
function messageOf(error) {
|
|
29
|
+
return error instanceof Error ? error.message : String(error);
|
|
30
|
+
}
|
|
31
|
+
/** Daily challenges (`hub.daily`). */
|
|
32
|
+
var DailySystem = class {
|
|
33
|
+
constructor(host) {
|
|
34
|
+
_defineProperty(
|
|
35
|
+
this,
|
|
36
|
+
/**
|
|
37
|
+
* The challenge in progress (set when a day starts, cleared on complete).
|
|
38
|
+
* A game's win handler checks it: `if (hub.daily.active) hub.daily.complete(…)`.
|
|
39
|
+
*/
|
|
40
|
+
"active",
|
|
41
|
+
null
|
|
42
|
+
);
|
|
43
|
+
_defineProperty(this, "host", void 0);
|
|
44
|
+
_defineProperty(this, "play", null);
|
|
45
|
+
_defineProperty(this, "pendingDay", null);
|
|
46
|
+
_defineProperty(this, "isRelaying", false);
|
|
47
|
+
this.host = host;
|
|
48
|
+
if (typeof window !== "undefined") window.addEventListener(DAILY_START_EVENT, (event) => {
|
|
49
|
+
if (this.isRelaying) return;
|
|
50
|
+
const day = event instanceof CustomEvent ? readField(event.detail, "day") : void 0;
|
|
51
|
+
if (typeof day === "string") this.startAndPlay(day);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Whether a game registered a daily-challenge handler on this page. */
|
|
55
|
+
get isDefined() {
|
|
56
|
+
return this.play !== null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Register the game's challenge runner. Call once at load. If the page was
|
|
60
|
+
* deep-linked with `?daily=DATE` (or a start was requested before this),
|
|
61
|
+
* that day starts now.
|
|
62
|
+
*/
|
|
63
|
+
define(definition) {
|
|
64
|
+
this.play = definition.play;
|
|
65
|
+
const day = readDeepLinkDay() ?? this.pendingDay;
|
|
66
|
+
this.pendingDay = null;
|
|
67
|
+
if (day !== null) this.startAndPlay(day);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Fetch the signed token for `day`, make it active and call the game's
|
|
71
|
+
* `play`. Before `define`, the day is kept and played then, so the menu's
|
|
72
|
+
* own instance never fetches a token; it is also relayed to any other
|
|
73
|
+
* daily system on the page (a client copy that couldn't share this one).
|
|
74
|
+
*/
|
|
75
|
+
async startAndPlay(day) {
|
|
76
|
+
const play = this.play;
|
|
77
|
+
if (play === null) {
|
|
78
|
+
this.pendingDay = day;
|
|
79
|
+
this.relayStart(day);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const started = await this.host.start(day);
|
|
84
|
+
const challenge = {
|
|
85
|
+
day: started.day,
|
|
86
|
+
seed: started.seed,
|
|
87
|
+
token: started.token,
|
|
88
|
+
alreadyCompleted: started.alreadyCompleted
|
|
89
|
+
};
|
|
90
|
+
this.active = challenge;
|
|
91
|
+
this.host.emit("hub:daily-started", {
|
|
92
|
+
day: started.day,
|
|
93
|
+
seed: started.seed,
|
|
94
|
+
alreadyCompleted: started.alreadyCompleted
|
|
95
|
+
});
|
|
96
|
+
play({ ...challenge });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.host.emit("hub:daily-error", {
|
|
99
|
+
day,
|
|
100
|
+
error: messageOf(error)
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Record completion of the active challenge with optional metadata.
|
|
106
|
+
* Resolves with the new completion count and rank, or `null` when no
|
|
107
|
+
* challenge is active or the hub refused.
|
|
108
|
+
*/
|
|
109
|
+
async complete(meta = {}) {
|
|
110
|
+
const challenge = this.active;
|
|
111
|
+
if (challenge === null) return null;
|
|
112
|
+
try {
|
|
113
|
+
const completed = await this.host.complete(challenge.day, challenge.token, meta);
|
|
114
|
+
this.active = null;
|
|
115
|
+
this.host.emit("hub:daily-complete", {
|
|
116
|
+
day: challenge.day,
|
|
117
|
+
count: completed.count,
|
|
118
|
+
rank: completed.rank,
|
|
119
|
+
meta
|
|
120
|
+
});
|
|
121
|
+
return completed;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
this.host.emit("hub:daily-error", {
|
|
124
|
+
day: challenge.day,
|
|
125
|
+
error: messageOf(error)
|
|
126
|
+
});
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Ask the page's other daily systems to start `day` (this one ignores its own event). */
|
|
131
|
+
relayStart(day) {
|
|
132
|
+
if (typeof window === "undefined") return;
|
|
133
|
+
this.isRelaying = true;
|
|
134
|
+
try {
|
|
135
|
+
window.dispatchEvent(new CustomEvent(DAILY_START_EVENT, { detail: { day } }));
|
|
136
|
+
} finally {
|
|
137
|
+
this.isRelaying = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** Abandon the active challenge without recording it (e.g. the player quit). */
|
|
141
|
+
cancel() {
|
|
142
|
+
this.active = null;
|
|
143
|
+
}
|
|
144
|
+
/** This game's window and the player's completions. */
|
|
145
|
+
status() {
|
|
146
|
+
return this.host.status();
|
|
147
|
+
}
|
|
148
|
+
/** Every daily-enabled game and the global standings (for the home hub). */
|
|
149
|
+
all() {
|
|
150
|
+
return this.host.all();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/daily/page-daily.ts
|
|
155
|
+
/**
|
|
156
|
+
* The page's one daily-challenge system (plan §4.4): the first bundle to
|
|
157
|
+
* create it wins, so the menu starts days on the instance the game defined.
|
|
158
|
+
*/
|
|
159
|
+
/** The cross-bundle shape number of the `daily` singleton. Bump only on an incompatible change. */
|
|
160
|
+
const DAILY_SINGLETON_API = 1;
|
|
161
|
+
/** Whether `candidate` is some bundle's daily system (v1 copies included). */
|
|
162
|
+
function isDailySystem(candidate) {
|
|
163
|
+
return hasMethods(candidate, [
|
|
164
|
+
"define",
|
|
165
|
+
"startAndPlay",
|
|
166
|
+
"complete",
|
|
167
|
+
"cancel",
|
|
168
|
+
"status",
|
|
169
|
+
"all"
|
|
170
|
+
]);
|
|
171
|
+
}
|
|
172
|
+
let claimedDaily = null;
|
|
173
|
+
/**
|
|
174
|
+
* The page-wide daily system, created with `host` if no bundle has made one
|
|
175
|
+
* yet. On a shape mismatch the registered one is kept and this bundle gets a
|
|
176
|
+
* private instance. The answer is kept for this bundle.
|
|
177
|
+
*/
|
|
178
|
+
function getDaily(host) {
|
|
179
|
+
claimedDaily ?? (claimedDaily = claimSingleton({
|
|
180
|
+
name: "daily",
|
|
181
|
+
version: "2.0.0-next.1",
|
|
182
|
+
api: 1,
|
|
183
|
+
create: () => new DailySystem(host),
|
|
184
|
+
isInstance: isDailySystem
|
|
185
|
+
}));
|
|
186
|
+
return claimedDaily;
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
export { DailySystem as a, DAILY_START_EVENT as i, getDaily as n, isDailySystem as r, DAILY_SINGLETON_API as t };
|
|
190
|
+
|
|
191
|
+
//# sourceMappingURL=daily.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daily.js","names":[],"sources":["../../src/daily/daily-system.ts","../../src/daily/page-daily.ts"],"mappings":";;;;;;;;;;;;;AAaA,MAAM,cAAc;;AAGpB,MAAa,oBAAoB;;AAIjC,SAAS,SAAS,WAAmC;CACnD,OAAO,OAAO,cAAc,YAAY,YAAY,KAAK,SAAS,IAAI,YAAY;AACpF;;AAGA,SAAS,kBAAiC;CAGxC,MAAM,WAAW,OAAO,WAAW,cAAc,OAAO,SAAS,UAAU,QAAQ,gBAAgB,CAAC;CACpG,IAAI,aAAa,QAAQ,OAAO,aAAa,aAC3C,OAAO;CAET,OAAO,SAAS,IAAI,IAAI,SAAS,IAAI,CAAC,CAAC,aAAa,IAAI,OAAO,CAAC;AAClE;;AAIA,SAAS,UAAU,OAAwB;CACzC,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;;AAGA,IAAa,cAAb,MAAyB;CAWvB,YAAY,MAAiB;;;;;;;GAN7B;GAAgC;;EACf,gBAAA,MAAA,QAAA,KAAA,CAAA;EACT,gBAAA,MAAA,QAAuC,IAAA;EACvC,gBAAA,MAAA,cAA4B,IAAA;EAC5B,gBAAA,MAAA,cAAa,KAAA;EAGnB,KAAK,OAAO;EACZ,IAAI,OAAO,WAAW,aAEpB,OAAO,iBAAiB,oBAAoB,UAAU;GACpD,IAAI,KAAK,YACP;GAEF,MAAM,MAAM,iBAAiB,cAAc,UAAU,MAAM,QAAQ,KAAK,IAAI,KAAA;GAC5E,IAAI,OAAO,QAAQ,UACjB,KAAU,aAAa,GAAG;EAE9B,CAAC;CAEL;;CAGA,IAAI,YAAqB;EACvB,OAAO,KAAK,SAAS;CACvB;;;;;;CAOA,OAAO,YAAmC;EACxC,KAAK,OAAO,WAAW;EACvB,MAAM,MAAM,gBAAgB,KAAK,KAAK;EACtC,KAAK,aAAa;EAClB,IAAI,QAAQ,MACV,KAAU,aAAa,GAAG;CAE9B;;;;;;;CAQA,MAAM,aAAa,KAA4B;EAC7C,MAAM,OAAO,KAAK;EAClB,IAAI,SAAS,MAAM;GACjB,KAAK,aAAa;GAClB,KAAK,WAAW,GAAG;GACnB;EACF;EACA,IAAI;GACF,MAAM,UAAU,MAAM,KAAK,KAAK,MAAM,GAAG;GACzC,MAAM,YAA4B;IAAE,KAAK,QAAQ;IAAK,MAAM,QAAQ;IAAM,OAAO,QAAQ;IAAO,kBAAkB,QAAQ;GAAiB;GAC3I,KAAK,SAAS;GACd,KAAK,KAAK,KAAK,qBAAqB;IAAE,KAAK,QAAQ;IAAK,MAAM,QAAQ;IAAM,kBAAkB,QAAQ;GAAiB,CAAC;GACxH,KAAK,EAAE,GAAG,UAAU,CAAC;EACvB,SAAS,OAAO;GACd,KAAK,KAAK,KAAK,mBAAmB;IAAE;IAAK,OAAO,UAAU,KAAK;GAAE,CAAC;EACpE;CACF;;;;;;CAOA,MAAM,SAAS,OAA4B,CAAC,GAA0C;EACpF,MAAM,YAAY,KAAK;EACvB,IAAI,cAAc,MAChB,OAAO;EAET,IAAI;GACF,MAAM,YAAY,MAAM,KAAK,KAAK,SAAS,UAAU,KAAK,UAAU,OAAO,IAAI;GAC/E,KAAK,SAAS;GACd,KAAK,KAAK,KAAK,sBAAsB;IAAE,KAAK,UAAU;IAAK,OAAO,UAAU;IAAO,MAAM,UAAU;IAAM;GAAK,CAAC;GAC/G,OAAO;EACT,SAAS,OAAO;GACd,KAAK,KAAK,KAAK,mBAAmB;IAAE,KAAK,UAAU;IAAK,OAAO,UAAU,KAAK;GAAE,CAAC;GACjF,OAAO;EACT;CACF;;CAGA,WAAmB,KAAmB;EACpC,IAAI,OAAO,WAAW,aACpB;EAEF,KAAK,aAAa;EAClB,IAAI;GACF,OAAO,cAAc,IAAI,YAAY,mBAAmB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;EAC9E,UAAU;GACR,KAAK,aAAa;EACpB;CACF;;CAGA,SAAe;EACb,KAAK,SAAS;CAChB;;CAGA,SAA+B;EAC7B,OAAO,KAAK,KAAK,OAAO;CAC1B;;CAGA,MAAyB;EACvB,OAAO,KAAK,KAAK,IAAI;CACvB;AACF;;;;;;;;ACrJA,MAAa,sBAAsB;;AAInC,SAAgB,cAAc,WAA8C;CAC1E,OAAO,WAAW,WAAW;EAAC;EAAU;EAAgB;EAAY;EAAU;EAAU;CAAK,CAAC;AAChG;AAEA,IAAI,eAAmC;;;;;;AAOvC,SAAgB,SAAS,MAA8B;CACrD,iBAAA,eAAiB,eAAe;EAC9B,MAAM;EACN,SAAA;EACA,KAAA;EACA,cAAc,IAAI,YAAY,IAAI;EAClC,YAAY;CACd,CAAC;CACD,OAAO;AACT"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/overlay/dom.ts
|
|
2
|
+
/**
|
|
3
|
+
* Typed DOM helpers for the overlay: element lookups that check the element
|
|
4
|
+
* class instead of asserting it, and small escapes.
|
|
5
|
+
*/
|
|
6
|
+
/** The first element under `root` matching `selector` that is an instance of `elementClass`; throws if missing. */
|
|
7
|
+
function requireElement(root, selector, elementClass) {
|
|
8
|
+
const found = root.querySelector(selector);
|
|
9
|
+
if (!(found instanceof elementClass)) throw new Error(`[hub] overlay element ${selector} is missing`);
|
|
10
|
+
return found;
|
|
11
|
+
}
|
|
12
|
+
/** Every element under `root` matching `selector` that is an instance of `elementClass`. */
|
|
13
|
+
function findElements(root, selector, elementClass) {
|
|
14
|
+
return [...root.querySelectorAll(selector)].filter((found) => found instanceof elementClass);
|
|
15
|
+
}
|
|
16
|
+
/** `document.body`, which is `null` while the page is still parsing its head (whatever lib.dom says). */
|
|
17
|
+
function pageBody() {
|
|
18
|
+
return document.body;
|
|
19
|
+
}
|
|
20
|
+
const HTML_ESCAPES = {
|
|
21
|
+
"&": "&",
|
|
22
|
+
"<": "<",
|
|
23
|
+
">": ">",
|
|
24
|
+
"\"": """,
|
|
25
|
+
"'": "'"
|
|
26
|
+
};
|
|
27
|
+
/** Escape text for HTML. */
|
|
28
|
+
function escapeHtml(text) {
|
|
29
|
+
return text.replace(/[&<>"']/g, (character) => HTML_ESCAPES[character] ?? character);
|
|
30
|
+
}
|
|
31
|
+
/** Clear an element's position styles. */
|
|
32
|
+
function clearPosition(element) {
|
|
33
|
+
element.style.left = "";
|
|
34
|
+
element.style.right = "";
|
|
35
|
+
element.style.top = "";
|
|
36
|
+
element.style.bottom = "";
|
|
37
|
+
element.style.transform = "";
|
|
38
|
+
}
|
|
39
|
+
/** Position an element's top-left corner in viewport px. */
|
|
40
|
+
function placeAt(element, left, top) {
|
|
41
|
+
clearPosition(element);
|
|
42
|
+
element.style.left = `${String(left)}px`;
|
|
43
|
+
element.style.top = `${String(top)}px`;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { requireElement as a, placeAt as i, findElements as n, pageBody as r, escapeHtml as t };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=dom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom.js","names":[],"sources":["../../src/overlay/dom.ts"],"mappings":";;;;;;AAMA,SAAgB,eAA4C,MAAkB,UAAkB,cAAkD;CAChJ,MAAM,QAAQ,KAAK,cAAc,QAAQ;CACzC,IAAI,EAAE,iBAAiB,eACrB,MAAM,IAAI,MAAM,yBAAyB,SAAS,YAAY;CAEhE,OAAO;AACT;;AAGA,SAAgB,aAA0C,MAAkB,UAAkB,cAAoD;CAChJ,OAAO,CAAC,GAAG,KAAK,iBAAiB,QAAQ,CAAC,CAAC,CAAC,QAAQ,UAAgC,iBAAiB,YAAY;AACnH;;AAGA,SAAgB,WAA+B;CAC7C,OAAO,SAAS;AAClB;AAEA,MAAM,eAAuC;CAAE,KAAK;CAAS,KAAK;CAAQ,KAAK;CAAQ,MAAK;CAAU,KAAK;AAAQ;;AAGnH,SAAgB,WAAW,MAAsB;CAC/C,OAAO,KAAK,QAAQ,aAAa,cAAc,aAAa,cAAc,SAAS;AACrF;;AAGA,SAAgB,cAAc,SAA4B;CACxD,QAAQ,MAAM,OAAO;CACrB,QAAQ,MAAM,QAAQ;CACtB,QAAQ,MAAM,MAAM;CACpB,QAAQ,MAAM,SAAS;CACvB,QAAQ,MAAM,YAAY;AAC5B;;AAGA,SAAgB,QAAQ,SAAsB,MAAc,KAAmB;CAC7E,cAAc,OAAO;CACrB,QAAQ,MAAM,OAAO,GAAG,OAAO,IAAI,EAAE;CACrC,QAAQ,MAAM,MAAM,GAAG,OAAO,GAAG,EAAE;AACrC"}
|