@adobe/uix-core 0.6.3
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/LICENSE +201 -0
- package/README.md +1 -0
- package/dist/debug-emitter.d.ts +25 -0
- package/dist/debug-emitter.d.ts.map +1 -0
- package/dist/debuglog.d.ts +70 -0
- package/dist/debuglog.d.ts.map +1 -0
- package/dist/emitter.d.ts +62 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/esm/index.js +259 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +266 -0
- package/dist/index.js.map +1 -0
- package/dist/namespace-proxy.d.ts +32 -0
- package/dist/namespace-proxy.d.ts.map +1 -0
- package/dist/timeout-promise.d.ts +12 -0
- package/dist/timeout-promise.d.ts.map +1 -0
- package/dist/types.d.ts +177 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +36 -0
- package/src/debug-emitter.ts +83 -0
- package/src/debuglog.ts +284 -0
- package/src/emitter.ts +90 -0
- package/src/index.ts +23 -0
- package/src/namespace-proxy.ts +82 -0
- package/src/timeout-promise.ts +36 -0
- package/src/types.ts +224 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { _customConsole, DebugLogger, Theme } from "./debuglog.js";
|
|
14
|
+
import { Emits, Unsubscriber } from "./types.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Adds methods for logging events
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export interface EmitterDebugLogger extends DebugLogger {
|
|
21
|
+
/**
|
|
22
|
+
* Listen to an event and pass the logger to the handler
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
listen(
|
|
26
|
+
type: string,
|
|
27
|
+
listener: (logger: EmitterDebugLogger, ev: CustomEvent) => unknown
|
|
28
|
+
): this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Debugger for EventTarget objects like Hosts, Ports and Guests, which
|
|
33
|
+
* patches dispatchEvent to log events
|
|
34
|
+
* Adapter to attach console logging listeners to all events on an emitter.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function debugEmitter(
|
|
38
|
+
emitter: Emits,
|
|
39
|
+
opts: {
|
|
40
|
+
theme: Theme;
|
|
41
|
+
type?: string;
|
|
42
|
+
id?: string;
|
|
43
|
+
}
|
|
44
|
+
): EmitterDebugLogger {
|
|
45
|
+
const logger = _customConsole(
|
|
46
|
+
opts.theme,
|
|
47
|
+
opts.type ||
|
|
48
|
+
(Object.getPrototypeOf(emitter) as typeof emitter).constructor.name,
|
|
49
|
+
opts.id || emitter.id
|
|
50
|
+
) as EmitterDebugLogger;
|
|
51
|
+
const oldDispatch = emitter.dispatchEvent;
|
|
52
|
+
emitter.dispatchEvent = (event) => {
|
|
53
|
+
logger.pushState({ type: "event", name: event.type });
|
|
54
|
+
const retVal = oldDispatch.call(emitter, event) as boolean;
|
|
55
|
+
logger.popState();
|
|
56
|
+
return retVal;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const subscriptions: Unsubscriber[] = [];
|
|
60
|
+
|
|
61
|
+
const oldDetach = logger.detach;
|
|
62
|
+
logger.detach = () => {
|
|
63
|
+
oldDetach.call(logger);
|
|
64
|
+
subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Listens and passes a logger to callbacks
|
|
69
|
+
*/
|
|
70
|
+
function listen(
|
|
71
|
+
type: string,
|
|
72
|
+
listener: (logger: EmitterDebugLogger, ev: CustomEvent) => unknown
|
|
73
|
+
) {
|
|
74
|
+
subscriptions.push(
|
|
75
|
+
emitter.addEventListener(type, (event) => listener(logger, event))
|
|
76
|
+
);
|
|
77
|
+
return logger;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
logger.listen = listen;
|
|
81
|
+
|
|
82
|
+
return logger;
|
|
83
|
+
}
|
package/src/debuglog.ts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fancy looking console decorator.
|
|
15
|
+
* @hidden
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** @internal */
|
|
20
|
+
const isDarkMode = () =>
|
|
21
|
+
typeof window.matchMedia === "function" &&
|
|
22
|
+
window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
23
|
+
|
|
24
|
+
/** @internal */
|
|
25
|
+
type Layout = {
|
|
26
|
+
padX: number;
|
|
27
|
+
padY: number;
|
|
28
|
+
rounded: number;
|
|
29
|
+
fontSize: number;
|
|
30
|
+
emphasis: Style;
|
|
31
|
+
};
|
|
32
|
+
/** @internal */
|
|
33
|
+
type HexColor = `#${string}` | "transparent";
|
|
34
|
+
/** @internal */
|
|
35
|
+
type Color = {
|
|
36
|
+
text: HexColor;
|
|
37
|
+
bg: HexColor;
|
|
38
|
+
hilight: HexColor;
|
|
39
|
+
shadow: HexColor;
|
|
40
|
+
};
|
|
41
|
+
/** @internal */
|
|
42
|
+
type ThemeSpec = Color & Layout;
|
|
43
|
+
|
|
44
|
+
/** @internal */
|
|
45
|
+
const Layouts: Record<string, Layout> = {
|
|
46
|
+
medium: {
|
|
47
|
+
padX: 5,
|
|
48
|
+
padY: 3,
|
|
49
|
+
rounded: 4,
|
|
50
|
+
fontSize: 100,
|
|
51
|
+
emphasis: "font-weight: bold;",
|
|
52
|
+
},
|
|
53
|
+
small: {
|
|
54
|
+
padX: 3,
|
|
55
|
+
padY: 1,
|
|
56
|
+
rounded: 2,
|
|
57
|
+
fontSize: 95,
|
|
58
|
+
emphasis: "font-style: italic;",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** @internal */
|
|
63
|
+
const Colors: Record<string, Color> = {
|
|
64
|
+
yellow: {
|
|
65
|
+
text: "#333333",
|
|
66
|
+
bg: "#EBD932",
|
|
67
|
+
hilight: "#F7E434",
|
|
68
|
+
shadow: "#D1C12C",
|
|
69
|
+
},
|
|
70
|
+
green: {
|
|
71
|
+
text: "#333333",
|
|
72
|
+
bg: "#96EB5E",
|
|
73
|
+
hilight: "#9EF763",
|
|
74
|
+
shadow: "#85D154",
|
|
75
|
+
},
|
|
76
|
+
blue: {
|
|
77
|
+
text: "#333333",
|
|
78
|
+
bg: "#8DD0EB",
|
|
79
|
+
hilight: "#88F0F7",
|
|
80
|
+
shadow: "#74AED4",
|
|
81
|
+
},
|
|
82
|
+
gray: isDarkMode()
|
|
83
|
+
? {
|
|
84
|
+
text: "#eeeeee",
|
|
85
|
+
bg: "transparent",
|
|
86
|
+
hilight: "#cecece",
|
|
87
|
+
shadow: "#cecece",
|
|
88
|
+
}
|
|
89
|
+
: {
|
|
90
|
+
text: "#333333",
|
|
91
|
+
bg: "#eeeeee",
|
|
92
|
+
hilight: "#f6f6f6",
|
|
93
|
+
shadow: "#cecece",
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** @internal */
|
|
98
|
+
type ThemeTag = `${keyof typeof Colors} ${keyof typeof Layouts}`;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
export type Theme = ThemeSpec | ThemeTag;
|
|
104
|
+
|
|
105
|
+
/** @internal */
|
|
106
|
+
type LogDecorator = (...args: unknown[]) => unknown[];
|
|
107
|
+
|
|
108
|
+
/** @internal */
|
|
109
|
+
type Style = `${string};`;
|
|
110
|
+
|
|
111
|
+
function memoizeUnary<T, U>(fn: (arg: T) => U): typeof fn {
|
|
112
|
+
const cache: Map<T, U> = new Map();
|
|
113
|
+
return (arg) => {
|
|
114
|
+
if (!cache.has(arg)) {
|
|
115
|
+
const result = fn(arg);
|
|
116
|
+
cache.set(arg, result);
|
|
117
|
+
if (cache.size > 100) {
|
|
118
|
+
cache.delete(cache.keys().next().value as T);
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
return cache.get(arg);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const toTheme = memoizeUnary((theme: Theme): ThemeSpec => {
|
|
127
|
+
if (typeof theme === "string") {
|
|
128
|
+
const [color, size] = theme.split(" ");
|
|
129
|
+
return {
|
|
130
|
+
...Colors[color],
|
|
131
|
+
...Layouts[size],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return theme;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const block: Style = `display: inline-block; border: 1px solid;`;
|
|
138
|
+
|
|
139
|
+
const flatten = (side: "left" | "right"): Style =>
|
|
140
|
+
`padding-${side}: 0px; border-${side}-width: 0px; border-top-${side}-radius: 0px; border-bottom-${side}-radius: 0px;`;
|
|
141
|
+
|
|
142
|
+
const toColor = ({ bg, hilight, shadow, text }: Color): Style =>
|
|
143
|
+
`color: ${text}; background: ${bg}; border-color: ${hilight} ${shadow} ${shadow} ${hilight};`;
|
|
144
|
+
|
|
145
|
+
const toLayout = ({ fontSize, padY, padX, rounded }: Layout) =>
|
|
146
|
+
`font-size: ${fontSize}%; padding: ${padY}px ${padX}px; border-radius: ${rounded}px;`;
|
|
147
|
+
|
|
148
|
+
const toBubbleStyle = memoizeUnary((theme: ThemeSpec): string[] => {
|
|
149
|
+
const base = `${block}${toColor(theme)}${toLayout(theme)}`;
|
|
150
|
+
return [
|
|
151
|
+
`${base}${flatten("right")}`,
|
|
152
|
+
`${base}${flatten("left")}${theme.emphasis}`,
|
|
153
|
+
] as Style[];
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
function toBubblePrepender(
|
|
157
|
+
bubbleLeft: string,
|
|
158
|
+
bubbleRight: string,
|
|
159
|
+
theme: ThemeSpec
|
|
160
|
+
): LogDecorator {
|
|
161
|
+
const prefix = `%c${bubbleLeft}%c ${bubbleRight}`;
|
|
162
|
+
const [left, right] = toBubbleStyle(theme);
|
|
163
|
+
return (args: unknown[]) => {
|
|
164
|
+
const bubbleArgs = [prefix, left, right];
|
|
165
|
+
if (typeof args[0] === "string") {
|
|
166
|
+
bubbleArgs[0] = `${prefix}%c ${args.shift() as string}`;
|
|
167
|
+
bubbleArgs.push(""); // reset style
|
|
168
|
+
}
|
|
169
|
+
return [...bubbleArgs, ...args];
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** @internal */
|
|
174
|
+
const stateTypes = {
|
|
175
|
+
event: "️⚡️",
|
|
176
|
+
} as const;
|
|
177
|
+
|
|
178
|
+
const stateDelim = " ⤻ ";
|
|
179
|
+
|
|
180
|
+
/** @internal */
|
|
181
|
+
type DebugState = { type: keyof typeof stateTypes; name: string };
|
|
182
|
+
|
|
183
|
+
// Serialize to memoize.
|
|
184
|
+
const getStateFormatter = memoizeUnary((stateJson: string) => {
|
|
185
|
+
const stateStack = JSON.parse(stateJson) as unknown as DebugState[];
|
|
186
|
+
const firstState = stateStack.shift();
|
|
187
|
+
const left = stateTypes[firstState.type];
|
|
188
|
+
const right = [
|
|
189
|
+
firstState.name,
|
|
190
|
+
...stateStack.map((state) => `${stateTypes[state.type]} ${state.name}`),
|
|
191
|
+
].join(stateDelim);
|
|
192
|
+
return toBubblePrepender(left, right, toTheme("gray small"));
|
|
193
|
+
});
|
|
194
|
+
const getStatePrepender = (stateStack: DebugState[]) =>
|
|
195
|
+
getStateFormatter(JSON.stringify(stateStack));
|
|
196
|
+
|
|
197
|
+
const overrideMethods = ["log", "error", "warn", "info", "debug"] as const;
|
|
198
|
+
|
|
199
|
+
const identity = <T>(x: T) => x;
|
|
200
|
+
|
|
201
|
+
const noop = (): (() => undefined) => undefined;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* A console, plus some methods to track event lifecycles.
|
|
205
|
+
* @internal
|
|
206
|
+
*/
|
|
207
|
+
export interface DebugLogger extends Console {
|
|
208
|
+
/**
|
|
209
|
+
* Stop all logging; methods do nothing
|
|
210
|
+
* @internal
|
|
211
|
+
*/
|
|
212
|
+
detach(): void;
|
|
213
|
+
/**
|
|
214
|
+
* Add an event bubble to the log during handler.
|
|
215
|
+
*/
|
|
216
|
+
pushState(state: DebugState): void;
|
|
217
|
+
/**
|
|
218
|
+
* Remove the bubble when event is done dispatching
|
|
219
|
+
*/
|
|
220
|
+
popState(): void;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Returns a console whose methods autoformat with bubbles.
|
|
225
|
+
* @internal
|
|
226
|
+
*/
|
|
227
|
+
export function _customConsole(
|
|
228
|
+
theme: Theme,
|
|
229
|
+
type: string,
|
|
230
|
+
name: string
|
|
231
|
+
): DebugLogger {
|
|
232
|
+
const prepender = toBubblePrepender(`X${type}`, name, toTheme(theme));
|
|
233
|
+
let statePrepender: LogDecorator = identity as LogDecorator;
|
|
234
|
+
const stateStack: DebugState[] = [];
|
|
235
|
+
const loggerProto: PropertyDescriptorMap = {
|
|
236
|
+
detach: {
|
|
237
|
+
writable: true,
|
|
238
|
+
configurable: true,
|
|
239
|
+
value(this: DebugLogger) {
|
|
240
|
+
overrideMethods.forEach((method) => {
|
|
241
|
+
this[method] = noop;
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
pushState: {
|
|
246
|
+
value(state: DebugState) {
|
|
247
|
+
stateStack.push(state);
|
|
248
|
+
statePrepender = getStatePrepender(stateStack);
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
popState: {
|
|
252
|
+
value() {
|
|
253
|
+
stateStack.pop();
|
|
254
|
+
statePrepender =
|
|
255
|
+
stateStack.length === 0
|
|
256
|
+
? (identity as LogDecorator)
|
|
257
|
+
: getStatePrepender(stateStack);
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
const customConsole = Object.create(
|
|
262
|
+
console,
|
|
263
|
+
overrideMethods.reduce((out, level) => {
|
|
264
|
+
out[level] = {
|
|
265
|
+
writable: true,
|
|
266
|
+
configurable: true,
|
|
267
|
+
value(...args: unknown[]) {
|
|
268
|
+
console[level](...prepender(statePrepender(args)));
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
return out;
|
|
272
|
+
}, loggerProto)
|
|
273
|
+
) as DebugLogger;
|
|
274
|
+
return customConsole;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* @internal
|
|
279
|
+
*/
|
|
280
|
+
export const quietConsole = new Proxy(console, {
|
|
281
|
+
get() {
|
|
282
|
+
return noop;
|
|
283
|
+
},
|
|
284
|
+
});
|
package/src/emitter.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Emits, Unsubscriber, NamedEvent } from "./types.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Browser-native {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget | EventTarget}
|
|
17
|
+
* whose {@link Emitter.addEventListener} method returns an anonymous function
|
|
18
|
+
* which unsubscribes the original handler.
|
|
19
|
+
*
|
|
20
|
+
* Also provides typed events via generics. You can create or extend this class
|
|
21
|
+
* to define custom emitters with known event names and signatures.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import type { NamedEvent, Emitter } from '@adobe/uix-sdk'
|
|
26
|
+
*
|
|
27
|
+
* class FizzBuzzEmitter extends Emitter<
|
|
28
|
+
* NamedEvent<"fizz", { fizzCount: number }> |
|
|
29
|
+
* NamedEvent<"buzz", { buzzCount: number }> |
|
|
30
|
+
* NamedEvent<"fizzbuzz">
|
|
31
|
+
* > {
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
* The `FizzBuzzEmitter` class will now type check its events and event
|
|
35
|
+
* listeners, providing autosuggest in editors.
|
|
36
|
+
*
|
|
37
|
+
* @see [EventTarget - MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
|
|
38
|
+
*
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
export class Emitter<Events extends NamedEvent>
|
|
42
|
+
extends EventTarget
|
|
43
|
+
implements Emits<Events>
|
|
44
|
+
{
|
|
45
|
+
/**
|
|
46
|
+
* An arbitrary string to uniquely identify this emitter and its events.
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
id: string;
|
|
50
|
+
constructor(id: string) {
|
|
51
|
+
super();
|
|
52
|
+
this.id = id;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Convenience method to construct and dispatch custom events.
|
|
56
|
+
*
|
|
57
|
+
* @param type - Name of one of the allowed events this can emit
|
|
58
|
+
* @param detail - Object to expose in the {@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail | CustomEvent#detail}
|
|
59
|
+
* property.
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
protected emit<Event extends Events>(
|
|
63
|
+
type: Event["type"],
|
|
64
|
+
detail: Event["detail"]
|
|
65
|
+
): void {
|
|
66
|
+
const event = new CustomEvent<typeof detail>(type, { detail });
|
|
67
|
+
this.dispatchEvent(event);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Subscribe to an event and receive an unsubscribe callback.
|
|
71
|
+
* @see [EventTarget.addEventListener - MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
|
|
72
|
+
*
|
|
73
|
+
* Identical to `EventTarget.addEventListener`, but returns an "unsubscriber"
|
|
74
|
+
* function which detaches the listener when invoked. Solves an ergonomic
|
|
75
|
+
* problem with native EventTargets where it's impossible to detach listeners
|
|
76
|
+
* without having a reference to the original handler.
|
|
77
|
+
*
|
|
78
|
+
* @typeParam E - Name of one of the allowed events this can emit
|
|
79
|
+
* @param type - Event type
|
|
80
|
+
* @param listener - Event handler
|
|
81
|
+
* @returns Call to unsubscribe listener.
|
|
82
|
+
*/
|
|
83
|
+
addEventListener<
|
|
84
|
+
Type extends Events["type"],
|
|
85
|
+
Event extends Extract<Events, { type: Type }>
|
|
86
|
+
>(type: Type, listener: (ev: Event) => unknown): Unsubscriber {
|
|
87
|
+
super.addEventListener(type, listener);
|
|
88
|
+
return () => super.removeEventListener(type, listener);
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Core utilities, types and contracts for the Adobe UIX SDK.
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export * from "./debug-emitter";
|
|
19
|
+
export * from "./debuglog";
|
|
20
|
+
export * from "./emitter";
|
|
21
|
+
export * from "./namespace-proxy";
|
|
22
|
+
export * from "./timeout-promise";
|
|
23
|
+
export * from "./types";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
14
|
+
import { RemoteHostApis, RemoteMethodInvoker } from "./types.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build a fake object that turns "method calls" into RPC messages
|
|
18
|
+
* The resulting object will recursively make more fake proxies on demand until
|
|
19
|
+
* one of the looked-up properties is invoked as a function.
|
|
20
|
+
* Then it will call the passed `invoke` method with a {@link HostMethodAddress}
|
|
21
|
+
* that can send the method invocation as an RPC message to another realm.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```js
|
|
25
|
+
* const invoker = (methodAddress) => console.log(
|
|
26
|
+
* address.path,
|
|
27
|
+
* address.name,
|
|
28
|
+
* address.args
|
|
29
|
+
* );
|
|
30
|
+
* const ns = makeNamespaceProxy(invoker);
|
|
31
|
+
*
|
|
32
|
+
* // looking up any property on the object will work
|
|
33
|
+
*
|
|
34
|
+
* ns.example.builds.method.call.message("foo", 1);
|
|
35
|
+
*
|
|
36
|
+
* // Console will log:
|
|
37
|
+
* ['example','builds','method','call']
|
|
38
|
+
* 'message'
|
|
39
|
+
* ["foo", 1]
|
|
40
|
+
*```
|
|
41
|
+
* @internal
|
|
42
|
+
*
|
|
43
|
+
* @param invoke - Callback that receives address
|
|
44
|
+
*/
|
|
45
|
+
export function makeNamespaceProxy<ProxiedApi extends object>(
|
|
46
|
+
invoke: RemoteMethodInvoker<unknown>,
|
|
47
|
+
path: string[] = []
|
|
48
|
+
): RemoteHostApis<ProxiedApi> {
|
|
49
|
+
const handler: ProxyHandler<Record<string, any>> = {
|
|
50
|
+
get: (target, prop) => {
|
|
51
|
+
if (typeof prop === "string") {
|
|
52
|
+
if (!Reflect.has(target, prop)) {
|
|
53
|
+
const next = makeNamespaceProxy(invoke, path.concat(prop));
|
|
54
|
+
Reflect.set(target, prop, next);
|
|
55
|
+
}
|
|
56
|
+
return Reflect.get(target, prop) as unknown;
|
|
57
|
+
} else {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Cannot look up a symbol ${String(prop)} on a host connection proxy.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const target = {} as unknown as RemoteHostApis<ProxiedApi>;
|
|
65
|
+
// Only trap the apply if there's at least two levels of namespace.
|
|
66
|
+
// uix.host() is not a function, and neither is uix.host.bareMethod().
|
|
67
|
+
if (path.length < 2) {
|
|
68
|
+
return new Proxy<RemoteHostApis<ProxiedApi>>(target, handler);
|
|
69
|
+
}
|
|
70
|
+
const invoker = (...args: unknown[]) =>
|
|
71
|
+
invoke({
|
|
72
|
+
path: path.slice(0, -1),
|
|
73
|
+
name: path[path.length - 1],
|
|
74
|
+
args,
|
|
75
|
+
});
|
|
76
|
+
return new Proxy<typeof invoker>(invoker, {
|
|
77
|
+
...handler,
|
|
78
|
+
apply(target, _, args: unknown[]) {
|
|
79
|
+
return target(...args);
|
|
80
|
+
},
|
|
81
|
+
}) as unknown as typeof target;
|
|
82
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Add a timeout to a Promise. The returned Promise will resolve to the value of
|
|
15
|
+
* the original Promise, but if it doesn't resolve within the timeout interval,
|
|
16
|
+
* it will reject with a timeout error.
|
|
17
|
+
* @internal
|
|
18
|
+
*
|
|
19
|
+
* @param timeoutMs - Time to wait (ms) before rejecting
|
|
20
|
+
* @param promise - Original promise to set a timeout for
|
|
21
|
+
* @returns - Promise that rejects after X milliseconds have passed
|
|
22
|
+
*/
|
|
23
|
+
export function timeoutPromise<T>(timeoutMs: number, promise: Promise<T>) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const timeout = setTimeout(
|
|
26
|
+
() => reject(new Error(`Timed out after ${timeoutMs}ms`)),
|
|
27
|
+
timeoutMs
|
|
28
|
+
);
|
|
29
|
+
promise
|
|
30
|
+
.then((result) => {
|
|
31
|
+
clearTimeout(timeout);
|
|
32
|
+
resolve(result);
|
|
33
|
+
})
|
|
34
|
+
.catch(reject);
|
|
35
|
+
});
|
|
36
|
+
}
|