mayu-live 0.0.0
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.
- checksums.yaml +7 -0
- data/COPYING +661 -0
- data/README.md +598 -0
- data/exe/mayu +33 -0
- data/lib/mayu/app_metrics.rb +93 -0
- data/lib/mayu/banner.rb +12 -0
- data/lib/mayu/client/README.md +17 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js +1 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.br +0 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map +1 -0
- data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map.br +0 -0
- data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js.map +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js +1 -0
- data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js.map +1 -0
- data/lib/mayu/client/dist/entries.json +3 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js +1 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.br +0 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.map +1 -0
- data/lib/mayu/client/dist/main-4b49dbc4.js.map.br +0 -0
- data/lib/mayu/client/package.json +39 -0
- data/lib/mayu/client/rollup.config.js +81 -0
- data/lib/mayu/client/src/DecompressionStream.ts +15 -0
- data/lib/mayu/client/src/DecompressionStreamPolyfill.ts +43 -0
- data/lib/mayu/client/src/MimeTypes.ts +4 -0
- data/lib/mayu/client/src/NodeTree.ts +445 -0
- data/lib/mayu/client/src/custom-elements/mayu-alert.html +137 -0
- data/lib/mayu/client/src/custom-elements/mayu-alert.ts +62 -0
- data/lib/mayu/client/src/custom-elements/mayu-disconnected.html +134 -0
- data/lib/mayu/client/src/custom-elements/mayu-disconnected.ts +51 -0
- data/lib/mayu/client/src/custom-elements/mayu-exception.html +79 -0
- data/lib/mayu/client/src/custom-elements/mayu-exception.ts +28 -0
- data/lib/mayu/client/src/custom-elements/mayu-log.html +70 -0
- data/lib/mayu/client/src/custom-elements/mayu-log.ts +42 -0
- data/lib/mayu/client/src/custom-elements/mayu-ping.html +36 -0
- data/lib/mayu/client/src/custom-elements/mayu-ping.ts +53 -0
- data/lib/mayu/client/src/custom-elements/mayu-progress-bar.html +44 -0
- data/lib/mayu/client/src/custom-elements/mayu-progress-bar.ts +40 -0
- data/lib/mayu/client/src/custom-elements/types.d.ts +4 -0
- data/lib/mayu/client/src/global.d.ts +26 -0
- data/lib/mayu/client/src/h.ts +27 -0
- data/lib/mayu/client/src/logger.ts +56 -0
- data/lib/mayu/client/src/main.ts +271 -0
- data/lib/mayu/client/src/serializeEvent.ts +90 -0
- data/lib/mayu/client/src/stream.ts +175 -0
- data/lib/mayu/client/src/types.ts +1 -0
- data/lib/mayu/client/src/utils.ts +71 -0
- data/lib/mayu/client/tsconfig.json +18 -0
- data/lib/mayu/colors.rb +34 -0
- data/lib/mayu/commands/base.rb +22 -0
- data/lib/mayu/commands/build.rb +82 -0
- data/lib/mayu/commands.rb +53 -0
- data/lib/mayu/component/base.rb +177 -0
- data/lib/mayu/component/handler_ref.rb +99 -0
- data/lib/mayu/component/helpers.rb +93 -0
- data/lib/mayu/component/interface.rb +18 -0
- data/lib/mayu/component/wrapper.rb +165 -0
- data/lib/mayu/component.rb +54 -0
- data/lib/mayu/configuration.rb +195 -0
- data/lib/mayu/disable_sorbet.rb +23 -0
- data/lib/mayu/environment.rb +151 -0
- data/lib/mayu/event_stream.rb +158 -0
- data/lib/mayu/fetch.rb +88 -0
- data/lib/mayu/html.rb +53 -0
- data/lib/mayu/html.yaml +767 -0
- data/lib/mayu/message_cipher.rb +172 -0
- data/lib/mayu/message_cipher.test.rb +16 -0
- data/lib/mayu/metrics/collector.rb +161 -0
- data/lib/mayu/metrics/exporter.rb +47 -0
- data/lib/mayu/metrics/reporter.rb +187 -0
- data/lib/mayu/metrics.rb +82 -0
- data/lib/mayu/ref_counter.rb +57 -0
- data/lib/mayu/resources/README.md +14 -0
- data/lib/mayu/resources/asset.rb +71 -0
- data/lib/mayu/resources/assets.rb +76 -0
- data/lib/mayu/resources/dependency_graph.rb +306 -0
- data/lib/mayu/resources/dot_exporter.rb +167 -0
- data/lib/mayu/resources/generators/base.rb +18 -0
- data/lib/mayu/resources/generators/copy_file.rb +26 -0
- data/lib/mayu/resources/generators/image.rb +106 -0
- data/lib/mayu/resources/generators/write_file.rb +39 -0
- data/lib/mayu/resources/hot_swap/file_watcher.rb +69 -0
- data/lib/mayu/resources/hot_swap.rb +46 -0
- data/lib/mayu/resources/mermaid_exporter.rb +210 -0
- data/lib/mayu/resources/registry.rb +190 -0
- data/lib/mayu/resources/resolver/base.rb +32 -0
- data/lib/mayu/resources/resolver/filesystem.rb +94 -0
- data/lib/mayu/resources/resolver/static.rb +27 -0
- data/lib/mayu/resources/resolver.rb +13 -0
- data/lib/mayu/resources/resource.rb +150 -0
- data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/attributes.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/attributes.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/composes.in.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/composes.out.css +10 -0
- data/lib/mayu/resources/transformers/__test__/css/element_selectors.in.css +3 -0
- data/lib/mayu/resources/transformers/__test__/css/element_selectors.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/css/has.in.css +7 -0
- data/lib/mayu/resources/transformers/__test__/css/has.out.css +10 -0
- data/lib/mayu/resources/transformers/__test__/css/media_queries.in.css +8 -0
- data/lib/mayu/resources/transformers/__test__/css/media_queries.out.css +12 -0
- data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.in.css +5 -0
- data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.out.css +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/README.md +10 -0
- data/lib/mayu/resources/transformers/__test__/haml/case.haml +8 -0
- data/lib/mayu/resources/transformers/__test__/haml/case.rb +15 -0
- data/lib/mayu/resources/transformers/__test__/haml/class_names.haml +13 -0
- data/lib/mayu/resources/transformers/__test__/haml/class_names.rb +26 -0
- data/lib/mayu/resources/transformers/__test__/haml/comments.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/comments.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/css.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/css.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/dashes.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/dashes.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return.haml +4 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return2.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/early_return2.rb +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/handlers.haml +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/handlers.rb +12 -0
- data/lib/mayu/resources/transformers/__test__/haml/if_else.haml +6 -0
- data/lib/mayu/resources/transformers/__test__/haml/if_else.rb +12 -0
- data/lib/mayu/resources/transformers/__test__/haml/interpolation.haml +8 -0
- data/lib/mayu/resources/transformers/__test__/haml/interpolation.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.haml +1 -0
- data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/props.haml +4 -0
- data/lib/mayu/resources/transformers/__test__/haml/props.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.rb +9 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.rb +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing.haml +5 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing.rb +14 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing2.haml +10 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing2.rb +11 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing3.haml +3 -0
- data/lib/mayu/resources/transformers/__test__/haml/spacing3.rb +10 -0
- data/lib/mayu/resources/transformers/css/rouge_lexer.rb +841 -0
- data/lib/mayu/resources/transformers/css.rb +100 -0
- data/lib/mayu/resources/transformers/css.test.rb +87 -0
- data/lib/mayu/resources/transformers/haml.rb +984 -0
- data/lib/mayu/resources/transformers/haml.test.rb +114 -0
- data/lib/mayu/resources/types/README.md +36 -0
- data/lib/mayu/resources/types/base.rb +35 -0
- data/lib/mayu/resources/types/component.rb +198 -0
- data/lib/mayu/resources/types/image.rb +169 -0
- data/lib/mayu/resources/types/javascript.rb +50 -0
- data/lib/mayu/resources/types/nil.rb +23 -0
- data/lib/mayu/resources/types/stylesheet.rb +119 -0
- data/lib/mayu/resources/types/svg.rb +69 -0
- data/lib/mayu/resources/types.rb +37 -0
- data/lib/mayu/routes.rb +170 -0
- data/lib/mayu/routing/builder.rb +108 -0
- data/lib/mayu/routing/matcher.rb +58 -0
- data/lib/mayu/routing/routes.rb +85 -0
- data/lib/mayu/routing.rb +17 -0
- data/lib/mayu/server/app.rb +494 -0
- data/lib/mayu/server/controller.rb +152 -0
- data/lib/mayu/server/errors.rb +110 -0
- data/lib/mayu/server/file_server.rb +140 -0
- data/lib/mayu/server.rb +63 -0
- data/lib/mayu/session.rb +358 -0
- data/lib/mayu/state/README.md +6 -0
- data/lib/mayu/state/action_creator.rb +191 -0
- data/lib/mayu/state/action_wrapper.rb +30 -0
- data/lib/mayu/state/loader.rb +220 -0
- data/lib/mayu/state/store.rb +82 -0
- data/lib/mayu/state.rb +8 -0
- data/lib/mayu/state.test.rb +97 -0
- data/lib/mayu/utils.rb +114 -0
- data/lib/mayu/vdom/children.rb +117 -0
- data/lib/mayu/vdom/component_marshaler.rb +53 -0
- data/lib/mayu/vdom/css_attributes.rb +131 -0
- data/lib/mayu/vdom/descriptor.rb +151 -0
- data/lib/mayu/vdom/descriptor.test.rb +26 -0
- data/lib/mayu/vdom/dom.rb +239 -0
- data/lib/mayu/vdom/h.rb +22 -0
- data/lib/mayu/vdom/id_generator.rb +55 -0
- data/lib/mayu/vdom/interfaces.rb +186 -0
- data/lib/mayu/vdom/marshalling.rb +78 -0
- data/lib/mayu/vdom/reconciliation.rb +205 -0
- data/lib/mayu/vdom/reconciliation.test.rb +56 -0
- data/lib/mayu/vdom/special_elements.rb +108 -0
- data/lib/mayu/vdom/update_context.rb +180 -0
- data/lib/mayu/vdom/vdom.perf.test.rb +146 -0
- data/lib/mayu/vdom/vnode.rb +266 -0
- data/lib/mayu/vdom/vtree.rb +672 -0
- data/lib/mayu/vdom/vtree.test.rb +68 -0
- data/lib/mayu/vdom.rb +8 -0
- data/lib/mayu/vdom.test.rb +73 -0
- data/lib/mayu/version.rb +6 -0
- data/lib/mayu.rb +8 -0
- data/mayu-live.gemspec +70 -0
- metadata +612 -0
@@ -0,0 +1,271 @@
|
|
1
|
+
import { sessionStream } from "./stream";
|
2
|
+
import NodeTree from "./NodeTree";
|
3
|
+
import h from "./h";
|
4
|
+
import type MayuPingElement from "./custom-elements/mayu-ping";
|
5
|
+
import type MayuLogElement from "./custom-elements/mayu-log";
|
6
|
+
import type MayuExceptionElement from "./custom-elements/mayu-exception";
|
7
|
+
import type MayuAlertElement from "./custom-elements/mayu-alert";
|
8
|
+
|
9
|
+
import serializeEvent from "./serializeEvent";
|
10
|
+
|
11
|
+
import logger from "./logger";
|
12
|
+
|
13
|
+
const onDOMContentLoaded = new Promise<void>((resolve) => {
|
14
|
+
if (document.readyState !== "loading") {
|
15
|
+
return resolve();
|
16
|
+
}
|
17
|
+
|
18
|
+
window.addEventListener("DOMContentLoaded", () => resolve());
|
19
|
+
});
|
20
|
+
|
21
|
+
function shouldPreventDefault(e: Event) {
|
22
|
+
if (typeof TouchEvent !== "undefined") {
|
23
|
+
if (e instanceof TouchEvent) {
|
24
|
+
return false;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
return true;
|
28
|
+
}
|
29
|
+
|
30
|
+
async function showException({
|
31
|
+
type,
|
32
|
+
message,
|
33
|
+
backtrace,
|
34
|
+
}: {
|
35
|
+
type: string;
|
36
|
+
message: string;
|
37
|
+
backtrace: string[];
|
38
|
+
}) {
|
39
|
+
await import("./custom-elements/mayu-exception");
|
40
|
+
|
41
|
+
const cleanedBacktrace = backtrace
|
42
|
+
.filter((line) => !/\/vendor\/bundle\//.test(line))
|
43
|
+
.join("\n");
|
44
|
+
|
45
|
+
const el = h("mayu-exception", [
|
46
|
+
h("span", [`${type}: ${message}`], { slot: "title" }),
|
47
|
+
h("span", [cleanedBacktrace], { slot: "backtrace" }),
|
48
|
+
]);
|
49
|
+
|
50
|
+
document.body.appendChild(el);
|
51
|
+
}
|
52
|
+
|
53
|
+
async function showAlert(message: string) {
|
54
|
+
await import("./custom-elements/mayu-alert");
|
55
|
+
const elem = document.createElement("mayu-alert") as MayuAlertElement;
|
56
|
+
elem.setAttribute("message", message);
|
57
|
+
document.body.appendChild(elem);
|
58
|
+
}
|
59
|
+
|
60
|
+
class MayuGlobal {
|
61
|
+
#sessionId: string;
|
62
|
+
|
63
|
+
constructor(sessionId: string) {
|
64
|
+
this.#sessionId = sessionId;
|
65
|
+
|
66
|
+
onDOMContentLoaded.then(() => {
|
67
|
+
window.addEventListener("popstate", () => {
|
68
|
+
return navigateTo(this.#sessionId, location.pathname);
|
69
|
+
});
|
70
|
+
});
|
71
|
+
}
|
72
|
+
|
73
|
+
async handle(e: Event, handlerId: string) {
|
74
|
+
if (shouldPreventDefault(e)) {
|
75
|
+
e.preventDefault();
|
76
|
+
}
|
77
|
+
|
78
|
+
const payload = serializeEvent(e);
|
79
|
+
console.log(payload);
|
80
|
+
// progressBar.setAttribute("progress", "0");
|
81
|
+
|
82
|
+
await mayuCallback(this.#sessionId, handlerId, payload);
|
83
|
+
|
84
|
+
let didRun = false;
|
85
|
+
const timeout = setTimeout(() => {
|
86
|
+
// progressBar.setAttribute("progress", "25");
|
87
|
+
didRun = true;
|
88
|
+
}, 1);
|
89
|
+
|
90
|
+
clearTimeout(timeout);
|
91
|
+
|
92
|
+
// progressBar.setAttribute("progress", "100");
|
93
|
+
}
|
94
|
+
|
95
|
+
async navigate(e: MouseEvent) {
|
96
|
+
if (e.metaKey || e.ctrlKey) return;
|
97
|
+
|
98
|
+
e.preventDefault();
|
99
|
+
const anchor = (e.target as HTMLElement).closest("a");
|
100
|
+
|
101
|
+
if (!anchor) {
|
102
|
+
logger.error("Could not find anchor element for", e.target);
|
103
|
+
return;
|
104
|
+
}
|
105
|
+
|
106
|
+
const url = new URL((anchor as HTMLAnchorElement).href);
|
107
|
+
// progressBar.setAttribute("progress", "0");
|
108
|
+
return navigateTo(this.#sessionId, url.pathname + url.search);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
function mayuCallback(sessionId: string, handlerId: string, payload: any) {
|
113
|
+
return fetch(`/__mayu/session/${sessionId}/callback/${handlerId}`, {
|
114
|
+
method: "POST",
|
115
|
+
headers: {
|
116
|
+
"content-type": "application/json",
|
117
|
+
},
|
118
|
+
body: JSON.stringify(payload),
|
119
|
+
});
|
120
|
+
}
|
121
|
+
|
122
|
+
async function navigateTo(sessionId: string, url: string) {
|
123
|
+
return fetch(`/__mayu/session/${sessionId}/navigate`, {
|
124
|
+
method: "POST",
|
125
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
126
|
+
body: url,
|
127
|
+
});
|
128
|
+
}
|
129
|
+
|
130
|
+
function getSessionIdFromUrl(url: string) {
|
131
|
+
const index = url.lastIndexOf("#");
|
132
|
+
if (index === -1) {
|
133
|
+
throw new Error(`No # found in script url: ${url}`);
|
134
|
+
}
|
135
|
+
return url.slice(index + 1);
|
136
|
+
}
|
137
|
+
|
138
|
+
function loadCustomElements() {
|
139
|
+
import("./custom-elements/mayu-ping");
|
140
|
+
import("./custom-elements/mayu-disconnected");
|
141
|
+
import("./custom-elements/mayu-progress-bar");
|
142
|
+
import("./custom-elements/mayu-exception");
|
143
|
+
import("./custom-elements/mayu-alert");
|
144
|
+
}
|
145
|
+
|
146
|
+
async function main(url: string) {
|
147
|
+
const sessionId = getSessionIdFromUrl(url);
|
148
|
+
const mayu = new MayuGlobal(sessionId);
|
149
|
+
window.Mayu = mayu;
|
150
|
+
|
151
|
+
let nodeTree: NodeTree | undefined;
|
152
|
+
|
153
|
+
const disconnectedElement = document.createElement("mayu-disconnected");
|
154
|
+
|
155
|
+
const pingElement = document.createElement("mayu-ping") as MayuPingElement;
|
156
|
+
pingElement.setAttribute("region", "Connecting...");
|
157
|
+
pingElement.setAttribute("status", "connecting");
|
158
|
+
document.body.appendChild(pingElement);
|
159
|
+
|
160
|
+
for await (const [event, payload] of sessionStream(sessionId)) {
|
161
|
+
switch (event) {
|
162
|
+
case "system.connected":
|
163
|
+
loadCustomElements();
|
164
|
+
|
165
|
+
pingElement.setAttribute("region", "Connected!");
|
166
|
+
pingElement.setAttribute("status", "connected");
|
167
|
+
logger.success("Connected!");
|
168
|
+
|
169
|
+
document.body
|
170
|
+
.querySelectorAll("mayu-disconnected")
|
171
|
+
.forEach((el) => el.remove());
|
172
|
+
break;
|
173
|
+
case "system.disconnected":
|
174
|
+
if (payload.transferring) {
|
175
|
+
pingElement.setAttribute("region", "Transferring…");
|
176
|
+
pingElement.setAttribute("status", "transferring");
|
177
|
+
break;
|
178
|
+
}
|
179
|
+
|
180
|
+
pingElement.setAttribute("region", "Disconnected");
|
181
|
+
pingElement.setAttribute("status", "disconnected");
|
182
|
+
|
183
|
+
logger.error("Disconnected");
|
184
|
+
|
185
|
+
disconnectedElement.setAttribute("reason", payload.reason);
|
186
|
+
|
187
|
+
if (disconnectedElement.parentElement !== document.body) {
|
188
|
+
document.body.appendChild(disconnectedElement);
|
189
|
+
}
|
190
|
+
break;
|
191
|
+
case "session.init":
|
192
|
+
await onDOMContentLoaded;
|
193
|
+
nodeTree = new NodeTree(payload.ids);
|
194
|
+
break;
|
195
|
+
case "session.patch":
|
196
|
+
nodeTree?.apply(payload);
|
197
|
+
break;
|
198
|
+
case "session.navigate":
|
199
|
+
const path = payload.path;
|
200
|
+
|
201
|
+
if (path !== location.pathname) {
|
202
|
+
logger.info("Navigating to", path);
|
203
|
+
history.pushState({}, "", path);
|
204
|
+
// progressBar.setAttribute("progress", "100");
|
205
|
+
}
|
206
|
+
break;
|
207
|
+
case "session.action":
|
208
|
+
handleAction(payload.type, payload.payload);
|
209
|
+
break;
|
210
|
+
case "session.keep_alive":
|
211
|
+
break;
|
212
|
+
case "session.transfer":
|
213
|
+
pingElement.setAttribute("region", "Transferring");
|
214
|
+
pingElement.setAttribute("status", "transferring");
|
215
|
+
break;
|
216
|
+
case "session.exception":
|
217
|
+
showException(payload);
|
218
|
+
break;
|
219
|
+
case "ping":
|
220
|
+
const values = Object.values(payload.values) as number[];
|
221
|
+
const mean = values.reduce((a, b) => a + b, 0.0) / values.length;
|
222
|
+
pingElement.setAttribute("ping", `${mean.toFixed(2)} ms`);
|
223
|
+
pingElement.setAttribute(
|
224
|
+
"region",
|
225
|
+
`${payload.instance} @ ${payload.region}`
|
226
|
+
);
|
227
|
+
pingElement.setAttribute("status", "ping");
|
228
|
+
break;
|
229
|
+
default:
|
230
|
+
logger.warn("Unhandled event:", event, payload);
|
231
|
+
break;
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
function handleAction(type: string, payload: any) {
|
236
|
+
switch (type) {
|
237
|
+
case "scroll_into_view": {
|
238
|
+
scrollIntoView(payload.selector, payload.options || {});
|
239
|
+
break;
|
240
|
+
}
|
241
|
+
case "alert": {
|
242
|
+
showAlert(payload);
|
243
|
+
break;
|
244
|
+
}
|
245
|
+
default: {
|
246
|
+
logger.error("Unhandled action:", type, payload);
|
247
|
+
break;
|
248
|
+
}
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
function scrollIntoView(selector: string, options: Record<string, string>) {
|
253
|
+
const elem = document.querySelector(selector);
|
254
|
+
|
255
|
+
if (elem) {
|
256
|
+
elem.scrollIntoView({
|
257
|
+
block: "start",
|
258
|
+
inline: "nearest",
|
259
|
+
behavior: "smooth",
|
260
|
+
...options,
|
261
|
+
});
|
262
|
+
} else {
|
263
|
+
console.error(
|
264
|
+
"Could not find element to scrollIntoView, selector:",
|
265
|
+
selector
|
266
|
+
);
|
267
|
+
}
|
268
|
+
}
|
269
|
+
}
|
270
|
+
|
271
|
+
export default main(import.meta.url);
|
@@ -0,0 +1,90 @@
|
|
1
|
+
function serializeElement(obj: any) {
|
2
|
+
if (obj instanceof HTMLFormElement) {
|
3
|
+
const formData = Object.fromEntries(new FormData(obj).entries());
|
4
|
+
|
5
|
+
return {
|
6
|
+
tagName: obj.tagName,
|
7
|
+
id: obj.id,
|
8
|
+
method: obj.method,
|
9
|
+
target: obj.target,
|
10
|
+
name: obj.name,
|
11
|
+
formData,
|
12
|
+
};
|
13
|
+
}
|
14
|
+
|
15
|
+
if (obj instanceof HTMLSelectElement) {
|
16
|
+
return {
|
17
|
+
tagName: obj.tagName,
|
18
|
+
id: obj.id,
|
19
|
+
type: obj.type,
|
20
|
+
name: obj.name,
|
21
|
+
value: obj.value,
|
22
|
+
};
|
23
|
+
}
|
24
|
+
|
25
|
+
if (obj instanceof HTMLDetailsElement) {
|
26
|
+
return {
|
27
|
+
tagName: obj.tagName,
|
28
|
+
id: obj.id,
|
29
|
+
open: obj.open,
|
30
|
+
};
|
31
|
+
}
|
32
|
+
|
33
|
+
if (obj instanceof HTMLInputElement) {
|
34
|
+
return {
|
35
|
+
tagName: obj.tagName,
|
36
|
+
id: obj.id,
|
37
|
+
type: obj.type,
|
38
|
+
name: obj.name,
|
39
|
+
value: obj.value,
|
40
|
+
checked: obj.checked,
|
41
|
+
};
|
42
|
+
}
|
43
|
+
|
44
|
+
if (obj instanceof HTMLButtonElement) {
|
45
|
+
return {
|
46
|
+
tagName: obj.tagName,
|
47
|
+
id: obj.id,
|
48
|
+
type: obj.type,
|
49
|
+
name: obj.name,
|
50
|
+
value: obj.value,
|
51
|
+
};
|
52
|
+
}
|
53
|
+
|
54
|
+
if (obj instanceof HTMLElement) {
|
55
|
+
return {
|
56
|
+
tagName: obj.tagName,
|
57
|
+
id: obj.id,
|
58
|
+
};
|
59
|
+
}
|
60
|
+
|
61
|
+
return {};
|
62
|
+
}
|
63
|
+
|
64
|
+
function serializeEvent(e: Event) {
|
65
|
+
const payload: Record<string, any> = {};
|
66
|
+
|
67
|
+
payload.type = e.constructor.name;
|
68
|
+
|
69
|
+
if (e.currentTarget) {
|
70
|
+
payload.currentTarget = serializeElement(e.currentTarget);
|
71
|
+
}
|
72
|
+
|
73
|
+
if (e.target) {
|
74
|
+
payload.target = serializeElement(e.target);
|
75
|
+
}
|
76
|
+
|
77
|
+
if (e instanceof MouseEvent) {
|
78
|
+
payload.buttons = e.buttons;
|
79
|
+
}
|
80
|
+
|
81
|
+
if (e instanceof SubmitEvent) {
|
82
|
+
if (e.submitter instanceof HTMLElement) {
|
83
|
+
payload.submitter = serializeElement(e.submitter);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
return payload;
|
88
|
+
}
|
89
|
+
|
90
|
+
export default serializeEvent;
|
@@ -0,0 +1,175 @@
|
|
1
|
+
import { decodeMultiStream, ExtensionCodec } from "@msgpack/msgpack";
|
2
|
+
import { stringifyJSON, retry, FatalError, sleep } from "./utils";
|
3
|
+
import { MimeTypes } from "./MimeTypes";
|
4
|
+
import logger from "./logger";
|
5
|
+
import DecompressionStream from "./DecompressionStream";
|
6
|
+
|
7
|
+
function createExtensionCodec() {
|
8
|
+
const extensionCodec = new ExtensionCodec();
|
9
|
+
|
10
|
+
extensionCodec.register({
|
11
|
+
type: 0x01,
|
12
|
+
encode() {
|
13
|
+
throw new Error("Not implemented");
|
14
|
+
},
|
15
|
+
decode(buffer: Uint8Array) {
|
16
|
+
return new Blob([buffer], { type: "application/vnd.mayu.session" });
|
17
|
+
},
|
18
|
+
});
|
19
|
+
|
20
|
+
return extensionCodec;
|
21
|
+
}
|
22
|
+
|
23
|
+
async function startStream(sessionId: string, encryptedState?: Blob) {
|
24
|
+
const res = await resume(sessionId, encryptedState);
|
25
|
+
|
26
|
+
if (!res.ok) {
|
27
|
+
const text = await res.text();
|
28
|
+
|
29
|
+
if (res.status == 503) {
|
30
|
+
// Server is shutting down, so retry..
|
31
|
+
throw new Error(`${res.status}: ${text}`);
|
32
|
+
}
|
33
|
+
|
34
|
+
throw new FatalError(`${res.status}: ${text}`);
|
35
|
+
}
|
36
|
+
|
37
|
+
if (!res.body) {
|
38
|
+
throw new FatalError("body is null");
|
39
|
+
}
|
40
|
+
|
41
|
+
const decompressionStream = new DecompressionStream("deflate-raw");
|
42
|
+
|
43
|
+
return res.body.pipeThrough(decompressionStream);
|
44
|
+
}
|
45
|
+
|
46
|
+
type ServerMessage = [id: string, event: string, payload: any];
|
47
|
+
type SessionStreamMessage = [string, any];
|
48
|
+
|
49
|
+
function resume(sessionId: string, encryptedState?: Blob) {
|
50
|
+
if (!encryptedState) {
|
51
|
+
return retry(() =>
|
52
|
+
fetch(`/__mayu/session/${sessionId}/init`, {
|
53
|
+
method: "POST",
|
54
|
+
})
|
55
|
+
);
|
56
|
+
}
|
57
|
+
|
58
|
+
return retry(() =>
|
59
|
+
fetch(`/__mayu/session/${sessionId}/resume`, {
|
60
|
+
method: "POST",
|
61
|
+
headers: { "content-type": MimeTypes.MAYU_SESSION },
|
62
|
+
body: encryptedState,
|
63
|
+
})
|
64
|
+
);
|
65
|
+
}
|
66
|
+
|
67
|
+
function errorMessage(e: any) {
|
68
|
+
if (e instanceof Error) {
|
69
|
+
return e.message;
|
70
|
+
}
|
71
|
+
|
72
|
+
if (typeof e === "string") {
|
73
|
+
return e;
|
74
|
+
}
|
75
|
+
|
76
|
+
return String(e);
|
77
|
+
}
|
78
|
+
|
79
|
+
export async function* sessionStream(
|
80
|
+
sessionId: string
|
81
|
+
): AsyncGenerator<SessionStreamMessage> {
|
82
|
+
let isRunning = true;
|
83
|
+
let encryptedState: Blob | undefined;
|
84
|
+
let isConnected = false;
|
85
|
+
const extensionCodec = createExtensionCodec();
|
86
|
+
let reason: string | undefined;
|
87
|
+
|
88
|
+
while (isRunning) {
|
89
|
+
try {
|
90
|
+
const stream = await retry(() => startStream(sessionId, encryptedState));
|
91
|
+
|
92
|
+
try {
|
93
|
+
for await (const message of decodeMultiStream(stream, {
|
94
|
+
extensionCodec,
|
95
|
+
})) {
|
96
|
+
const [id, event, payload] = message as ServerMessage;
|
97
|
+
|
98
|
+
if (!isConnected) {
|
99
|
+
isConnected = true;
|
100
|
+
yield ["system.connected", {}];
|
101
|
+
}
|
102
|
+
|
103
|
+
if (encryptedState) {
|
104
|
+
logger.info("Clearing encryptedState");
|
105
|
+
encryptedState = undefined;
|
106
|
+
}
|
107
|
+
|
108
|
+
try {
|
109
|
+
switch (event) {
|
110
|
+
case "session.transfer":
|
111
|
+
yield ["session.transfer", {}];
|
112
|
+
encryptedState = payload;
|
113
|
+
logger.info("Setting encryptedState", payload);
|
114
|
+
break;
|
115
|
+
case "pong":
|
116
|
+
yield [
|
117
|
+
"ping",
|
118
|
+
{
|
119
|
+
values: {
|
120
|
+
client: new Date().getTime() - Number(payload.pong),
|
121
|
+
server: payload.server,
|
122
|
+
},
|
123
|
+
region: payload.region,
|
124
|
+
instance: payload.instance,
|
125
|
+
},
|
126
|
+
];
|
127
|
+
break;
|
128
|
+
case "ping":
|
129
|
+
postCallback(sessionId, "ping", {
|
130
|
+
pong: payload,
|
131
|
+
ping: new Date().getTime(),
|
132
|
+
});
|
133
|
+
break;
|
134
|
+
default:
|
135
|
+
yield [event, payload];
|
136
|
+
}
|
137
|
+
} catch (e) {
|
138
|
+
reason = errorMessage(e);
|
139
|
+
logger.error(e);
|
140
|
+
}
|
141
|
+
}
|
142
|
+
} catch (e) {
|
143
|
+
reason = errorMessage(e);
|
144
|
+
logger.error(e);
|
145
|
+
}
|
146
|
+
|
147
|
+
isConnected = false;
|
148
|
+
|
149
|
+
if (isRunning) {
|
150
|
+
reason ||= "Stream ended unexpectedly";
|
151
|
+
}
|
152
|
+
|
153
|
+
yield ["system.disconnected", { transferring: !!encryptedState, reason }];
|
154
|
+
} catch (e) {
|
155
|
+
logger.error(e);
|
156
|
+
|
157
|
+
if (e instanceof FatalError) {
|
158
|
+
isRunning = false;
|
159
|
+
isConnected = false;
|
160
|
+
yield ["system.disconnected", { reason: e.message }];
|
161
|
+
return;
|
162
|
+
}
|
163
|
+
|
164
|
+
await sleep(1000);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
async function postCallback(sessionId: string, callbackId: string, data: any) {
|
170
|
+
return fetch(`/__mayu/session/${sessionId}/${callbackId}`, {
|
171
|
+
method: "POST",
|
172
|
+
headers: { "content-type": "application/json" },
|
173
|
+
body: stringifyJSON(data),
|
174
|
+
});
|
175
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export type MayuNodeData = { id: number };
|
@@ -0,0 +1,71 @@
|
|
1
|
+
export function stringifyJSON(payload: any, space?: number) {
|
2
|
+
return JSON.stringify(
|
3
|
+
payload,
|
4
|
+
(_key: string, value: any) => {
|
5
|
+
if (typeof value === "bigint") {
|
6
|
+
return Number(value);
|
7
|
+
} else if (value instanceof Blob) {
|
8
|
+
return `Blob{type: ${value.type}, size: ${value.size}}`;
|
9
|
+
} else {
|
10
|
+
return value;
|
11
|
+
}
|
12
|
+
},
|
13
|
+
space
|
14
|
+
);
|
15
|
+
}
|
16
|
+
|
17
|
+
export async function sleep(ms = 1000) {
|
18
|
+
return new Promise<void>((resolve) => {
|
19
|
+
setTimeout(resolve, ms);
|
20
|
+
});
|
21
|
+
}
|
22
|
+
|
23
|
+
export class FatalError extends Error {}
|
24
|
+
|
25
|
+
export async function retry<T>(fn: () => Promise<T>): Promise<T> {
|
26
|
+
const maxAttempts = 10;
|
27
|
+
let attempts = 0;
|
28
|
+
|
29
|
+
while (true) {
|
30
|
+
try {
|
31
|
+
return await fn();
|
32
|
+
} catch (e) {
|
33
|
+
if (e instanceof FatalError) {
|
34
|
+
throw e;
|
35
|
+
}
|
36
|
+
|
37
|
+
if (attempts >= maxAttempts) {
|
38
|
+
console.error("Reached the maximum number of attempts!");
|
39
|
+
throw e;
|
40
|
+
}
|
41
|
+
|
42
|
+
const waitTime = attempts + Math.random();
|
43
|
+
|
44
|
+
console.error(
|
45
|
+
`Got error (attempts: ${attempts}, wait: ${waitTime.toFixed(2)})`,
|
46
|
+
e
|
47
|
+
);
|
48
|
+
|
49
|
+
const logTimes = Math.ceil(waitTime);
|
50
|
+
const sleepTime = waitTime / logTimes;
|
51
|
+
|
52
|
+
for (let i = 0; i < logTimes; i++) {
|
53
|
+
console.warn(
|
54
|
+
`Retrying in ${(waitTime - i * sleepTime).toFixed(2)} seconds`
|
55
|
+
);
|
56
|
+
await sleep(sleepTime * 1000);
|
57
|
+
}
|
58
|
+
|
59
|
+
attempts++;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
export function* splitChunk(chunk: Uint8Array) {
|
65
|
+
let offset = 0;
|
66
|
+
|
67
|
+
while (offset < chunk.byteLength) {
|
68
|
+
yield chunk.slice(offset, offset + 1024);
|
69
|
+
offset += 1024;
|
70
|
+
}
|
71
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
"target": "ESNext",
|
4
|
+
"useDefineForClassFields": true,
|
5
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
6
|
+
"allowJs": false,
|
7
|
+
"skipLibCheck": true,
|
8
|
+
"esModuleInterop": false,
|
9
|
+
"allowSyntheticDefaultImports": true,
|
10
|
+
"strict": true,
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
12
|
+
"module": "ESNext",
|
13
|
+
"moduleResolution": "Node",
|
14
|
+
"resolveJsonModule": true,
|
15
|
+
"isolatedModules": true,
|
16
|
+
"noEmit": true
|
17
|
+
}
|
18
|
+
}
|
data/lib/mayu/colors.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Mayu
|
5
|
+
module Colors
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(str: String, t: Float).returns(String) }
|
9
|
+
def self.rainbow(str, t = Time.now.to_f)
|
10
|
+
str
|
11
|
+
.each_line
|
12
|
+
.map do |line|
|
13
|
+
line
|
14
|
+
.chars
|
15
|
+
.map
|
16
|
+
.with_index do |ch, i|
|
17
|
+
next ch if ch.strip.empty?
|
18
|
+
|
19
|
+
r, g, b =
|
20
|
+
3
|
21
|
+
.times
|
22
|
+
.map { _1 / 3.0 * Math::PI }
|
23
|
+
.map { _1 + i / 10.0 }
|
24
|
+
.map { Math.sin(_1 - t)**2 }
|
25
|
+
.map { (_1 * 255).to_i }
|
26
|
+
|
27
|
+
format("\e[38;2;%d;%d;%dm%s", r, g, b, ch)
|
28
|
+
end
|
29
|
+
.join
|
30
|
+
end
|
31
|
+
.join + "\e[0m"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Mayu
|
5
|
+
module Commands
|
6
|
+
class Base
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { returns(Configuration) }
|
10
|
+
attr_reader :configuration
|
11
|
+
|
12
|
+
sig { params(configuration: Configuration).void }
|
13
|
+
def initialize(configuration)
|
14
|
+
@configuration = configuration
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { params(argv: T::Array[String]).void }
|
18
|
+
def call(argv)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|