socket-function 0.9.0 → 0.9.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/.eslintrc.js +50 -50
- package/SocketFunction.ts +280 -280
- package/SocketFunctionTypes.ts +90 -90
- package/hot/HotReloadController.ts +105 -105
- package/mobx/UrlParam.ts +39 -39
- package/mobx/observer.tsx +49 -49
- package/mobx/promiseToObservable.tsx +41 -41
- package/package.json +30 -28
- package/require/CSSShim.ts +19 -19
- package/require/RequireController.ts +252 -252
- package/require/buffer.js +2368 -2368
- package/require/compileFlags.ts +44 -44
- package/require/require.html +13 -13
- package/require/require.js +462 -456
- package/spec.txt +115 -115
- package/src/CallFactory.ts +389 -389
- package/src/JSONLACKS/JSONLACKS.generated.js +17 -17
- package/src/JSONLACKS/JSONLACKS.pegjs +247 -247
- package/src/JSONLACKS/JSONLACKS.ts +429 -375
- package/src/args.ts +21 -21
- package/src/batching.ts +170 -129
- package/src/caching.ts +318 -314
- package/src/callHTTPHandler.ts +203 -203
- package/src/callManager.ts +134 -134
- package/src/certStore.ts +29 -29
- package/src/fixLargeNetworkCalls.ts +8 -8
- package/src/formatting/colors.ts +78 -78
- package/src/formatting/format.ts +160 -156
- package/src/formatting/logColors.ts +17 -17
- package/src/misc.ts +302 -171
- package/src/nodeCache.ts +92 -92
- package/src/nodeProxy.ts +54 -54
- package/src/profiling/getOwnTime.ts +142 -142
- package/src/profiling/measure.ts +273 -244
- package/src/profiling/stats.ts +212 -212
- package/src/profiling/tcpLagProxy.ts +63 -63
- package/src/storagePath.ts +10 -10
- package/src/tlsParsing.ts +96 -96
- package/src/types.ts +8 -8
- package/src/webSocketServer.ts +250 -250
- package/test/client.css +2 -2
- package/test/client.ts +46 -46
- package/test/server.ts +43 -43
- package/test/shared.ts +52 -52
- package/tsconfig.json +26 -26
package/SocketFunctionTypes.ts
CHANGED
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
/// <reference path="./require/RequireController.ts" />
|
|
2
|
-
|
|
3
|
-
module.allowclient = true;
|
|
4
|
-
|
|
5
|
-
import { SocketFunction } from "./SocketFunction";
|
|
6
|
-
import { getCallObj } from "./src/nodeProxy";
|
|
7
|
-
import { Args, MaybePromise } from "./src/types";
|
|
8
|
-
|
|
9
|
-
export const socket = Symbol("socket");
|
|
10
|
-
|
|
11
|
-
export type SocketExposedInterface = {
|
|
12
|
-
[functionName: string]: (...args: any[]) => Promise<unknown>;
|
|
13
|
-
};
|
|
14
|
-
export type SocketInternalInterface = {
|
|
15
|
-
[functionName: string]: {
|
|
16
|
-
[getCallObj]: (...args: any[]) => FullCallType;
|
|
17
|
-
(...args: any[]): Promise<unknown>;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
export type SocketExposedInterfaceClass = {
|
|
21
|
-
//new(): SocketExposedInterface;
|
|
22
|
-
new(): unknown;
|
|
23
|
-
prototype: unknown;
|
|
24
|
-
};
|
|
25
|
-
export interface SocketExposedShape<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
|
|
26
|
-
[functionName: string]: {
|
|
27
|
-
/** Indicates with the same input, we give the same output, forever,
|
|
28
|
-
* independent of code changes. This only works for data storage.
|
|
29
|
-
*/
|
|
30
|
-
dataImmutable?: boolean;
|
|
31
|
-
hooks?: SocketFunctionHook<ExposedType>[];
|
|
32
|
-
clientHooks?: SocketFunctionClientHook<ExposedType>[];
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface CallType {
|
|
37
|
-
classGuid: string;
|
|
38
|
-
functionName: string;
|
|
39
|
-
args: unknown[];
|
|
40
|
-
}
|
|
41
|
-
export interface FullCallType extends CallType {
|
|
42
|
-
nodeId: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface SocketFunctionHook<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
|
|
46
|
-
(config: HookContext<ExposedType>): MaybePromise<void>;
|
|
47
|
-
/** NOTE: This is useful when we need a clientside hook to set up state specifically for our serverside hook. */
|
|
48
|
-
clientHook?: SocketFunctionClientHook<ExposedType>;
|
|
49
|
-
}
|
|
50
|
-
export type HookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
|
|
51
|
-
call: FullCallType;
|
|
52
|
-
// If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
|
|
53
|
-
overrideResult?: unknown;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export type ClientHookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
|
|
57
|
-
call: FullCallType;
|
|
58
|
-
// If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
|
|
59
|
-
overrideResult?: unknown;
|
|
60
|
-
connectionId: { nodeId: string };
|
|
61
|
-
};
|
|
62
|
-
export interface SocketFunctionClientHook<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
|
|
63
|
-
(config: ClientHookContext<ExposedType>): MaybePromise<void>;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface SocketRegistered<ExposedType = any> {
|
|
67
|
-
nodes: {
|
|
68
|
-
// NOTE: Don't pass around nodeId to other nodes, instead pass around NetworkLocation (which they
|
|
69
|
-
// then turn into a nodeId, which they can then check permissions on themself).
|
|
70
|
-
[nodeId: string]: {
|
|
71
|
-
[functionName in keyof ExposedType]: ExposedType[functionName] & {
|
|
72
|
-
[getCallObj]: (...args: Args<ExposedType[functionName]>) => FullCallType;
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
};
|
|
76
|
-
_classGuid: string;
|
|
77
|
-
}
|
|
78
|
-
export type CallerContext = Readonly<CallerContextBase>;
|
|
79
|
-
export type CallerContextBase = {
|
|
80
|
-
// IMPORTANT! Do not pass nodeId to other nodes with the intention of having
|
|
81
|
-
// them call functions directly using nodeId. Instead pass location, and have them use connect.
|
|
82
|
-
// - nodeId will be unique per thread, so is only useful for temporary communcation. If you want
|
|
83
|
-
// a more permanent identity, you must derive it from certInfo yourself.
|
|
84
|
-
nodeId: string;
|
|
85
|
-
|
|
86
|
-
// The nodeId they contacted. This is useful to determine their intention (otherwise
|
|
87
|
-
// requests can be redirected to us and would accept them, even though they are being
|
|
88
|
-
// blatantly MITMed).
|
|
89
|
-
// IF they are the server, calling us back, then this will just be ""
|
|
90
|
-
localNodeId: string;
|
|
1
|
+
/// <reference path="./require/RequireController.ts" />
|
|
2
|
+
|
|
3
|
+
module.allowclient = true;
|
|
4
|
+
|
|
5
|
+
import { SocketFunction } from "./SocketFunction";
|
|
6
|
+
import { getCallObj } from "./src/nodeProxy";
|
|
7
|
+
import { Args, MaybePromise } from "./src/types";
|
|
8
|
+
|
|
9
|
+
export const socket = Symbol("socket");
|
|
10
|
+
|
|
11
|
+
export type SocketExposedInterface = {
|
|
12
|
+
[functionName: string]: (...args: any[]) => Promise<unknown>;
|
|
13
|
+
};
|
|
14
|
+
export type SocketInternalInterface = {
|
|
15
|
+
[functionName: string]: {
|
|
16
|
+
[getCallObj]: (...args: any[]) => FullCallType;
|
|
17
|
+
(...args: any[]): Promise<unknown>;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export type SocketExposedInterfaceClass = {
|
|
21
|
+
//new(): SocketExposedInterface;
|
|
22
|
+
new(): unknown;
|
|
23
|
+
prototype: unknown;
|
|
24
|
+
};
|
|
25
|
+
export interface SocketExposedShape<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
|
|
26
|
+
[functionName: string]: {
|
|
27
|
+
/** Indicates with the same input, we give the same output, forever,
|
|
28
|
+
* independent of code changes. This only works for data storage.
|
|
29
|
+
*/
|
|
30
|
+
dataImmutable?: boolean;
|
|
31
|
+
hooks?: SocketFunctionHook<ExposedType>[];
|
|
32
|
+
clientHooks?: SocketFunctionClientHook<ExposedType>[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CallType {
|
|
37
|
+
classGuid: string;
|
|
38
|
+
functionName: string;
|
|
39
|
+
args: unknown[];
|
|
40
|
+
}
|
|
41
|
+
export interface FullCallType extends CallType {
|
|
42
|
+
nodeId: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SocketFunctionHook<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
|
|
46
|
+
(config: HookContext<ExposedType>): MaybePromise<void>;
|
|
47
|
+
/** NOTE: This is useful when we need a clientside hook to set up state specifically for our serverside hook. */
|
|
48
|
+
clientHook?: SocketFunctionClientHook<ExposedType>;
|
|
49
|
+
}
|
|
50
|
+
export type HookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
|
|
51
|
+
call: FullCallType;
|
|
52
|
+
// If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
|
|
53
|
+
overrideResult?: unknown;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type ClientHookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
|
|
57
|
+
call: FullCallType;
|
|
58
|
+
// If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
|
|
59
|
+
overrideResult?: unknown;
|
|
60
|
+
connectionId: { nodeId: string };
|
|
61
|
+
};
|
|
62
|
+
export interface SocketFunctionClientHook<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
|
|
63
|
+
(config: ClientHookContext<ExposedType>): MaybePromise<void>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface SocketRegistered<ExposedType = any> {
|
|
67
|
+
nodes: {
|
|
68
|
+
// NOTE: Don't pass around nodeId to other nodes, instead pass around NetworkLocation (which they
|
|
69
|
+
// then turn into a nodeId, which they can then check permissions on themself).
|
|
70
|
+
[nodeId: string]: {
|
|
71
|
+
[functionName in keyof ExposedType]: ExposedType[functionName] & {
|
|
72
|
+
[getCallObj]: (...args: Args<ExposedType[functionName]>) => FullCallType;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
_classGuid: string;
|
|
77
|
+
}
|
|
78
|
+
export type CallerContext = Readonly<CallerContextBase>;
|
|
79
|
+
export type CallerContextBase = {
|
|
80
|
+
// IMPORTANT! Do not pass nodeId to other nodes with the intention of having
|
|
81
|
+
// them call functions directly using nodeId. Instead pass location, and have them use connect.
|
|
82
|
+
// - nodeId will be unique per thread, so is only useful for temporary communcation. If you want
|
|
83
|
+
// a more permanent identity, you must derive it from certInfo yourself.
|
|
84
|
+
nodeId: string;
|
|
85
|
+
|
|
86
|
+
// The nodeId they contacted. This is useful to determine their intention (otherwise
|
|
87
|
+
// requests can be redirected to us and would accept them, even though they are being
|
|
88
|
+
// blatantly MITMed).
|
|
89
|
+
// IF they are the server, calling us back, then this will just be ""
|
|
90
|
+
localNodeId: string;
|
|
91
91
|
};
|
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
/// <reference path="../../typenode/index.d.ts" />
|
|
2
|
-
/// <reference path="../require/RequireController.ts" />
|
|
3
|
-
module.allowclient = true;
|
|
4
|
-
|
|
5
|
-
import { SocketFunction } from "../SocketFunction";
|
|
6
|
-
import { cache, lazy } from "../src/caching";
|
|
7
|
-
import * as fs from "fs";
|
|
8
|
-
import debugbreak from "debugbreak";
|
|
9
|
-
import { isNode } from "../src/misc";
|
|
10
|
-
import { red } from "../src/formatting/logColors";
|
|
11
|
-
|
|
12
|
-
/** Enables some hot reload functionality.
|
|
13
|
-
* - Triggers a refresh clientside
|
|
14
|
-
* - Triggers a reload server, for modules marked with `module.hotreload`
|
|
15
|
-
*/
|
|
16
|
-
export function watchFilesAndTriggerHotReloading(noAutomaticBrowserWatch = false) {
|
|
17
|
-
|
|
18
|
-
SocketFunction.expose(HotReloadController);
|
|
19
|
-
if (!isNode()) {
|
|
20
|
-
if (!noAutomaticBrowserWatch) {
|
|
21
|
-
HotReloadController.nodes[SocketFunction.locationNode()]
|
|
22
|
-
.watchFiles()
|
|
23
|
-
.catch(e => console.error("watchFiles error", e))
|
|
24
|
-
;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
setInterval(() => {
|
|
28
|
-
for (let module of Object.values(require.cache)) {
|
|
29
|
-
if (!module) continue;
|
|
30
|
-
hotReloadModule(module);
|
|
31
|
-
}
|
|
32
|
-
}, 5000);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
declare global {
|
|
36
|
-
namespace NodeJS {
|
|
37
|
-
interface Module {
|
|
38
|
-
hotreload?: boolean;
|
|
39
|
-
noserverhotreload?: boolean;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const hotReloadModule = cache((module: NodeJS.Module) => {
|
|
45
|
-
if (!module.updateContents) return;
|
|
46
|
-
fs.watchFile(module.filename, { persistent: false, interval: 1000 }, (curr, prev) => {
|
|
47
|
-
if (curr.mtime.getTime() === prev.mtime.getTime()) return;
|
|
48
|
-
console.log(`Hot reloading due to change: ${module.filename}`);
|
|
49
|
-
module.updateContents?.();
|
|
50
|
-
if (isNode()) {
|
|
51
|
-
if (
|
|
52
|
-
module.hotreload
|
|
53
|
-
// A fairly big hack (as this could just be in a string, or something similar), but... it also VERY useful
|
|
54
|
-
|| module.moduleContents?.includes("\nmodule.hotreload = true;" + "\n")
|
|
55
|
-
|| module.moduleContents?.includes("\r\nmodule.hotreload = true;" + "\r\n")
|
|
56
|
-
) {
|
|
57
|
-
console.log(`Reloading ${module.id}`);
|
|
58
|
-
try {
|
|
59
|
-
module.loaded = false;
|
|
60
|
-
module.load(module.id);
|
|
61
|
-
} catch (e) {
|
|
62
|
-
console.error(red(`Error hot reloading ${module.id}`));
|
|
63
|
-
console.error(e);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
triggerClientSideReload();
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
let reloadTriggering = false;
|
|
71
|
-
let clientWatcherNodes = new Set<string>();
|
|
72
|
-
function triggerClientSideReload() {
|
|
73
|
-
if (reloadTriggering) return;
|
|
74
|
-
reloadTriggering = true;
|
|
75
|
-
setTimeout(async () => {
|
|
76
|
-
reloadTriggering = false;
|
|
77
|
-
for (let clientNodeId of clientWatcherNodes) {
|
|
78
|
-
console.log(`Notifying client of hot reload: ${clientNodeId}`);
|
|
79
|
-
HotReloadController.nodes[clientNodeId].fileUpdated().catch(() => {
|
|
80
|
-
console.log(`Removing erroring client: ${clientNodeId}`);
|
|
81
|
-
clientWatcherNodes.delete(clientNodeId);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}, 300);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
class HotReloadControllerBase {
|
|
88
|
-
// TODO: Also hot reload when we reconnect to the server, as it is likely setup will need to
|
|
89
|
-
// be rerun in that case as well (for example, we need to call watchFiles again!)
|
|
90
|
-
async watchFiles() {
|
|
91
|
-
let callerId = SocketFunction.getCaller().nodeId;
|
|
92
|
-
clientWatcherNodes.add(callerId);
|
|
93
|
-
}
|
|
94
|
-
async fileUpdated() {
|
|
95
|
-
document.location.reload();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export const HotReloadController = SocketFunction.register(
|
|
100
|
-
"HotReloadController-032b2250-3aac-4187-8c95-75412742b8f5",
|
|
101
|
-
new HotReloadControllerBase(),
|
|
102
|
-
() => ({
|
|
103
|
-
watchFiles: {},
|
|
104
|
-
fileUpdated: {}
|
|
105
|
-
})
|
|
1
|
+
/// <reference path="../../typenode/index.d.ts" />
|
|
2
|
+
/// <reference path="../require/RequireController.ts" />
|
|
3
|
+
module.allowclient = true;
|
|
4
|
+
|
|
5
|
+
import { SocketFunction } from "../SocketFunction";
|
|
6
|
+
import { cache, lazy } from "../src/caching";
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import debugbreak from "debugbreak";
|
|
9
|
+
import { isNode } from "../src/misc";
|
|
10
|
+
import { red } from "../src/formatting/logColors";
|
|
11
|
+
|
|
12
|
+
/** Enables some hot reload functionality.
|
|
13
|
+
* - Triggers a refresh clientside
|
|
14
|
+
* - Triggers a reload server, for modules marked with `module.hotreload`
|
|
15
|
+
*/
|
|
16
|
+
export function watchFilesAndTriggerHotReloading(noAutomaticBrowserWatch = false) {
|
|
17
|
+
|
|
18
|
+
SocketFunction.expose(HotReloadController);
|
|
19
|
+
if (!isNode()) {
|
|
20
|
+
if (!noAutomaticBrowserWatch) {
|
|
21
|
+
HotReloadController.nodes[SocketFunction.locationNode()]
|
|
22
|
+
.watchFiles()
|
|
23
|
+
.catch(e => console.error("watchFiles error", e))
|
|
24
|
+
;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
setInterval(() => {
|
|
28
|
+
for (let module of Object.values(require.cache)) {
|
|
29
|
+
if (!module) continue;
|
|
30
|
+
hotReloadModule(module);
|
|
31
|
+
}
|
|
32
|
+
}, 5000);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
declare global {
|
|
36
|
+
namespace NodeJS {
|
|
37
|
+
interface Module {
|
|
38
|
+
hotreload?: boolean;
|
|
39
|
+
noserverhotreload?: boolean;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hotReloadModule = cache((module: NodeJS.Module) => {
|
|
45
|
+
if (!module.updateContents) return;
|
|
46
|
+
fs.watchFile(module.filename, { persistent: false, interval: 1000 }, (curr, prev) => {
|
|
47
|
+
if (curr.mtime.getTime() === prev.mtime.getTime()) return;
|
|
48
|
+
console.log(`Hot reloading due to change: ${module.filename}`);
|
|
49
|
+
module.updateContents?.();
|
|
50
|
+
if (isNode()) {
|
|
51
|
+
if (
|
|
52
|
+
module.hotreload
|
|
53
|
+
// A fairly big hack (as this could just be in a string, or something similar), but... it also VERY useful
|
|
54
|
+
|| module.moduleContents?.includes("\nmodule.hotreload = true;" + "\n")
|
|
55
|
+
|| module.moduleContents?.includes("\r\nmodule.hotreload = true;" + "\r\n")
|
|
56
|
+
) {
|
|
57
|
+
console.log(`Reloading ${module.id}`);
|
|
58
|
+
try {
|
|
59
|
+
module.loaded = false;
|
|
60
|
+
module.load(module.id);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error(red(`Error hot reloading ${module.id}`));
|
|
63
|
+
console.error(e);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
triggerClientSideReload();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
let reloadTriggering = false;
|
|
71
|
+
let clientWatcherNodes = new Set<string>();
|
|
72
|
+
function triggerClientSideReload() {
|
|
73
|
+
if (reloadTriggering) return;
|
|
74
|
+
reloadTriggering = true;
|
|
75
|
+
setTimeout(async () => {
|
|
76
|
+
reloadTriggering = false;
|
|
77
|
+
for (let clientNodeId of clientWatcherNodes) {
|
|
78
|
+
console.log(`Notifying client of hot reload: ${clientNodeId}`);
|
|
79
|
+
HotReloadController.nodes[clientNodeId].fileUpdated().catch(() => {
|
|
80
|
+
console.log(`Removing erroring client: ${clientNodeId}`);
|
|
81
|
+
clientWatcherNodes.delete(clientNodeId);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}, 300);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class HotReloadControllerBase {
|
|
88
|
+
// TODO: Also hot reload when we reconnect to the server, as it is likely setup will need to
|
|
89
|
+
// be rerun in that case as well (for example, we need to call watchFiles again!)
|
|
90
|
+
async watchFiles() {
|
|
91
|
+
let callerId = SocketFunction.getCaller().nodeId;
|
|
92
|
+
clientWatcherNodes.add(callerId);
|
|
93
|
+
}
|
|
94
|
+
async fileUpdated() {
|
|
95
|
+
document.location.reload();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const HotReloadController = SocketFunction.register(
|
|
100
|
+
"HotReloadController-032b2250-3aac-4187-8c95-75412742b8f5",
|
|
101
|
+
new HotReloadControllerBase(),
|
|
102
|
+
() => ({
|
|
103
|
+
watchFiles: {},
|
|
104
|
+
fileUpdated: {}
|
|
105
|
+
})
|
|
106
106
|
);
|
package/mobx/UrlParam.ts
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import { observable } from "mobx";
|
|
2
|
-
import { isNode } from "../src/misc";
|
|
3
|
-
|
|
4
|
-
// TODO: Batch url state changes
|
|
5
|
-
// TODO: Create actual links, that a tag can use
|
|
6
|
-
// - Then after that, support not reloading the page, instead just setting the observables,
|
|
7
|
-
// for faster navigation.
|
|
8
|
-
|
|
9
|
-
export class UrlParam<T> {
|
|
10
|
-
constructor(private key: string, private defaultValue: T) { }
|
|
11
|
-
valueSeqNum = observable({ value: 1 });
|
|
12
|
-
public get(): T {
|
|
13
|
-
urlBackSeqNum.value;
|
|
14
|
-
this.valueSeqNum.value;
|
|
15
|
-
let value = new URL(location.href).searchParams.get(this.key);
|
|
16
|
-
if (value === null) {
|
|
17
|
-
return this.defaultValue;
|
|
18
|
-
}
|
|
19
|
-
return JSON.parse(value) as T;
|
|
20
|
-
}
|
|
21
|
-
public set(value: T) {
|
|
22
|
-
let url = new URL(location.href);
|
|
23
|
-
url.searchParams.set(this.key, JSON.stringify(value));
|
|
24
|
-
history.pushState({}, "", url.toString());
|
|
25
|
-
this.valueSeqNum.value++;
|
|
26
|
-
}
|
|
27
|
-
public reset() {
|
|
28
|
-
let url = new URL(location.href);
|
|
29
|
-
url.searchParams.delete(this.key);
|
|
30
|
-
history.pushState({}, "", url.toString());
|
|
31
|
-
this.valueSeqNum.value++;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let urlBackSeqNum = observable({ value: 1 });
|
|
36
|
-
if (!isNode()) {
|
|
37
|
-
window.addEventListener("popstate", () => {
|
|
38
|
-
urlBackSeqNum.value++;
|
|
39
|
-
});
|
|
1
|
+
import { observable } from "mobx";
|
|
2
|
+
import { isNode } from "../src/misc";
|
|
3
|
+
|
|
4
|
+
// TODO: Batch url state changes
|
|
5
|
+
// TODO: Create actual links, that a tag can use
|
|
6
|
+
// - Then after that, support not reloading the page, instead just setting the observables,
|
|
7
|
+
// for faster navigation.
|
|
8
|
+
|
|
9
|
+
export class UrlParam<T> {
|
|
10
|
+
constructor(private key: string, private defaultValue: T) { }
|
|
11
|
+
valueSeqNum = observable({ value: 1 });
|
|
12
|
+
public get(): T {
|
|
13
|
+
urlBackSeqNum.value;
|
|
14
|
+
this.valueSeqNum.value;
|
|
15
|
+
let value = new URL(location.href).searchParams.get(this.key);
|
|
16
|
+
if (value === null) {
|
|
17
|
+
return this.defaultValue;
|
|
18
|
+
}
|
|
19
|
+
return JSON.parse(value) as T;
|
|
20
|
+
}
|
|
21
|
+
public set(value: T) {
|
|
22
|
+
let url = new URL(location.href);
|
|
23
|
+
url.searchParams.set(this.key, JSON.stringify(value));
|
|
24
|
+
history.pushState({}, "", url.toString());
|
|
25
|
+
this.valueSeqNum.value++;
|
|
26
|
+
}
|
|
27
|
+
public reset() {
|
|
28
|
+
let url = new URL(location.href);
|
|
29
|
+
url.searchParams.delete(this.key);
|
|
30
|
+
history.pushState({}, "", url.toString());
|
|
31
|
+
this.valueSeqNum.value++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let urlBackSeqNum = observable({ value: 1 });
|
|
36
|
+
if (!isNode()) {
|
|
37
|
+
window.addEventListener("popstate", () => {
|
|
38
|
+
urlBackSeqNum.value++;
|
|
39
|
+
});
|
|
40
40
|
}
|
package/mobx/observer.tsx
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import type preact from "preact";
|
|
2
|
-
import { setFlag } from "../require/compileFlags";
|
|
3
|
-
|
|
4
|
-
import { observable, Reaction } from "mobx";
|
|
5
|
-
|
|
6
|
-
setFlag(require, "mobx", "allowclient", true);
|
|
7
|
-
setFlag(require, "preact", "allowclient", true);
|
|
8
|
-
|
|
9
|
-
let globalConstructOrder = 0;
|
|
10
|
-
|
|
11
|
-
export function observer<
|
|
12
|
-
T extends {
|
|
13
|
-
new(...args: any[]): {
|
|
14
|
-
render(): preact.ComponentChild;
|
|
15
|
-
forceUpdate(callback?: () => void): void;
|
|
16
|
-
componentWillUnmount?(): void;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
>(
|
|
20
|
-
Constructor: T
|
|
21
|
-
) {
|
|
22
|
-
let name = Constructor.name;
|
|
23
|
-
return class extends Constructor {
|
|
24
|
-
// NOTE: This is completely valid javascript. For some reason (https://github.com/microsoft/TypeScript/pull/12065#issuecomment-270205513)
|
|
25
|
-
// the typescript team decided, whatever, just make it an error, even though it isn't in es6 ("we should simplify ES6' semantics").
|
|
26
|
-
// So, instead of simplifying ES6 semantics, lets give ourself better info for debugging...
|
|
27
|
-
// @ts-ignore
|
|
28
|
-
static get name() { return Constructor.name; }
|
|
29
|
-
|
|
30
|
-
// It is always true, that a parent has a constructOrder < a child's constructOrder
|
|
31
|
-
constructOrder = globalConstructOrder++;
|
|
32
|
-
|
|
33
|
-
reaction = new Reaction(`render.${name}.${this.constructOrder}`, () => {
|
|
34
|
-
super.forceUpdate();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
componentWillUnmount() {
|
|
38
|
-
this.reaction.dispose();
|
|
39
|
-
super.componentWillUnmount?.();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
render() {
|
|
43
|
-
let output: preact.ComponentChild;
|
|
44
|
-
this.reaction.track(() => {
|
|
45
|
-
output = super.render();
|
|
46
|
-
});
|
|
47
|
-
return output;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
1
|
+
import type preact from "preact";
|
|
2
|
+
import { setFlag } from "../require/compileFlags";
|
|
3
|
+
|
|
4
|
+
import { observable, Reaction } from "mobx";
|
|
5
|
+
|
|
6
|
+
setFlag(require, "mobx", "allowclient", true);
|
|
7
|
+
setFlag(require, "preact", "allowclient", true);
|
|
8
|
+
|
|
9
|
+
let globalConstructOrder = 0;
|
|
10
|
+
|
|
11
|
+
export function observer<
|
|
12
|
+
T extends {
|
|
13
|
+
new(...args: any[]): {
|
|
14
|
+
render(): preact.ComponentChild;
|
|
15
|
+
forceUpdate(callback?: () => void): void;
|
|
16
|
+
componentWillUnmount?(): void;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
>(
|
|
20
|
+
Constructor: T
|
|
21
|
+
) {
|
|
22
|
+
let name = Constructor.name;
|
|
23
|
+
return class extends Constructor {
|
|
24
|
+
// NOTE: This is completely valid javascript. For some reason (https://github.com/microsoft/TypeScript/pull/12065#issuecomment-270205513)
|
|
25
|
+
// the typescript team decided, whatever, just make it an error, even though it isn't in es6 ("we should simplify ES6' semantics").
|
|
26
|
+
// So, instead of simplifying ES6 semantics, lets give ourself better info for debugging...
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
static get name() { return Constructor.name; }
|
|
29
|
+
|
|
30
|
+
// It is always true, that a parent has a constructOrder < a child's constructOrder
|
|
31
|
+
constructOrder = globalConstructOrder++;
|
|
32
|
+
|
|
33
|
+
reaction = new Reaction(`render.${name}.${this.constructOrder}`, () => {
|
|
34
|
+
super.forceUpdate();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
componentWillUnmount() {
|
|
38
|
+
this.reaction.dispose();
|
|
39
|
+
super.componentWillUnmount?.();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
render() {
|
|
43
|
+
let output: preact.ComponentChild;
|
|
44
|
+
this.reaction.track(() => {
|
|
45
|
+
output = super.render();
|
|
46
|
+
});
|
|
47
|
+
return output;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
50
|
}
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import { observable, Reaction, IObservable } from "mobx";
|
|
2
|
-
import { cacheLimited } from "../src/caching";
|
|
3
|
-
|
|
4
|
-
export interface InternalResult {
|
|
5
|
-
result: { value: unknown } | undefined;
|
|
6
|
-
}
|
|
7
|
-
export function promiseToObservable<T>(promise: Promise<T>, staleValue?: T): { value: T | undefined } {
|
|
8
|
-
let isDone = false;
|
|
9
|
-
let error: unknown;
|
|
10
|
-
let result: T | undefined;
|
|
11
|
-
|
|
12
|
-
let internalResult: InternalResult = { result: undefined };
|
|
13
|
-
|
|
14
|
-
let isDoneTrigger = observable({
|
|
15
|
-
value: false
|
|
16
|
-
});
|
|
17
|
-
promise.then(
|
|
18
|
-
r => {
|
|
19
|
-
internalResult.result = { value: r };
|
|
20
|
-
result = r;
|
|
21
|
-
isDoneTrigger.value = true;
|
|
22
|
-
isDone = true;
|
|
23
|
-
},
|
|
24
|
-
e => {
|
|
25
|
-
error = e;
|
|
26
|
-
isDoneTrigger.value = true;
|
|
27
|
-
isDone = true;
|
|
28
|
-
}
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
return Object.assign({
|
|
32
|
-
get value() {
|
|
33
|
-
if (isDone) {
|
|
34
|
-
if (error) throw error;
|
|
35
|
-
return result;
|
|
36
|
-
}
|
|
37
|
-
isDoneTrigger.value;
|
|
38
|
-
return staleValue;
|
|
39
|
-
}
|
|
40
|
-
}, { internalResult });
|
|
41
|
-
}
|
|
1
|
+
import { observable, Reaction, IObservable } from "mobx";
|
|
2
|
+
import { cacheLimited } from "../src/caching";
|
|
3
|
+
|
|
4
|
+
export interface InternalResult {
|
|
5
|
+
result: { value: unknown } | undefined;
|
|
6
|
+
}
|
|
7
|
+
export function promiseToObservable<T>(promise: Promise<T>, staleValue?: T): { value: T | undefined } {
|
|
8
|
+
let isDone = false;
|
|
9
|
+
let error: unknown;
|
|
10
|
+
let result: T | undefined;
|
|
11
|
+
|
|
12
|
+
let internalResult: InternalResult = { result: undefined };
|
|
13
|
+
|
|
14
|
+
let isDoneTrigger = observable({
|
|
15
|
+
value: false
|
|
16
|
+
});
|
|
17
|
+
promise.then(
|
|
18
|
+
r => {
|
|
19
|
+
internalResult.result = { value: r };
|
|
20
|
+
result = r;
|
|
21
|
+
isDoneTrigger.value = true;
|
|
22
|
+
isDone = true;
|
|
23
|
+
},
|
|
24
|
+
e => {
|
|
25
|
+
error = e;
|
|
26
|
+
isDoneTrigger.value = true;
|
|
27
|
+
isDone = true;
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return Object.assign({
|
|
32
|
+
get value() {
|
|
33
|
+
if (isDone) {
|
|
34
|
+
if (error) throw error;
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
isDoneTrigger.value;
|
|
38
|
+
return staleValue;
|
|
39
|
+
}
|
|
40
|
+
}, { internalResult });
|
|
41
|
+
}
|