@web-applets/sdk 0.1.5 → 0.2.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -130
- package/dist/applets/actions.d.ts +6 -0
- package/dist/applets/applet-factory.d.ts +6 -0
- package/dist/applets/applet-factory.js +23 -0
- package/dist/applets/applet-scope.d.ts +31 -0
- package/dist/applets/applet-scope.js +209 -0
- package/dist/applets/applet.d.ts +21 -0
- package/dist/applets/applet.js +161 -0
- package/dist/applets/errors.d.ts +6 -0
- package/dist/applets/errors.js +12 -0
- package/dist/applets/events.d.ts +15 -0
- package/dist/applets/events.js +11 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/debug.d.ts +5 -0
- package/dist/debug.js +6 -0
- package/dist/elements/applet-frame.d.ts +16 -0
- package/dist/elements/applet-frame.js +110 -0
- package/dist/index.d.ts +10 -9
- package/dist/index.js +8 -9
- package/dist/messages.d.ts +46 -0
- package/dist/messages.js +1 -0
- package/dist/polyfill.d.ts +11 -0
- package/dist/polyfill.js +6 -0
- package/dist/utils.d.ts +28 -1
- package/dist/utils.js +4 -26
- package/dist/web-applets.min.js +13 -0
- package/package.json +6 -2
- package/dist/components/applet-frame.d.ts +0 -19
- package/dist/components/applet-frame.js +0 -94
- package/dist/core/applet.d.ts +0 -21
- package/dist/core/applet.js +0 -117
- package/dist/core/context.d.ts +0 -32
- package/dist/core/context.js +0 -138
- package/dist/core/shared.d.ts +0 -124
- package/dist/core/shared.js +0 -177
- package/dist/types.d.ts +0 -9
- /package/dist/{types.js → applets/actions.js} +0 -0
package/README.md
CHANGED
@@ -2,149 +2,28 @@
|
|
2
2
|
|
3
3
|
> An open spec & SDK for creating web apps that agents can use.
|
4
4
|
|
5
|
-
👾 [Community Discord](https://discord.gg/
|
5
|
+
🌐 [Docs](https://unternet.co/docs) | 👾 [Community Discord](https://discord.gg/VsMuEKmqvt) | 💌 [Mailing List](https://buttondown.com/unternet)
|
6
6
|
|
7
|
-
[](https://builders.mozilla.org/)
|
8
8
|
|
9
9
|
Web Applets is a [Mozilla Builders](https://builders.mozilla.org/) project.
|
10
10
|
|
11
11
|
## What is it?
|
12
12
|
|
13
|
-
**Web Applets is an open specification for building software that both humans and AI can understand and use together.** Instead of forcing AI to operate traditional point-and-click apps built for humans, Web Applets creates a new kind of web software designed for human-AI collaboration.
|
13
|
+
**Web Applets is an open specification for building software that both humans and AI can understand and use together.** Instead of forcing AI to operate traditional point-and-click apps built for humans, Web Applets creates a new kind of web software designed for human-AI collaboration. You can read more about it on our website.
|
14
14
|
|
15
|
-
|
15
|
+
- [Motivation](https://unternet.co/docs/web-applets/introduction)
|
16
|
+
- [Getting started with building an applet](https://unternet.co/docs/web-applets/creating-an-applet)
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
[Unternet](https://unternet.co) is building a new, intelligent user agent that can do things for you across the web. As part of that effort, we needed a way to actuate an embedded web app. You can do this with a computer use model, but for many use cases it's not suitable to point and click around in a virtual browser. Why make a computer talk to another computer via a clumsy web interface when they can just talk directly?
|
20
|
-
|
21
|
-
Web Applets lets you define a simple, computer-readable API for a web app running in a browser, webview, or iframe. You can send it actions as JSON objects, which an LLM can easily create (see [OpenAI's structured JSON endpoint](https://openai.com/index/introducing-structured-outputs-in-the-api/)), and they can update their UI instantly in-place. Plus, you can expose the internal state of the applets to the model so you can do cool stuff like chat to a map.
|
22
|
-
|
23
|
-
We wanted anyone to be able to build these actions into their own third-party applets and distribute them. So, we extended the web & made it available to everyone!
|
24
|
-
|
25
|
-
## Getting started
|
26
|
-
|
27
|
-
Create a new web app using our CLI:
|
28
|
-
|
29
|
-
```bash
|
30
|
-
npx @web-applets/create
|
31
|
-
```
|
32
|
-
|
33
|
-
Inside the generated folder, you'll find a basic web app setup:
|
34
|
-
|
35
|
-
- `public/manifest.json`: A web app manifest, where you can define initial actions, add icons, etc.
|
36
|
-
- `index.html`: Much like a website, this holds the main page for your applet
|
37
|
-
- `src/main.ts`: Declares functions that respond to each action, and a render function that updates the view based on state
|
38
|
-
|
39
|
-
> Want to use React? Svelte? Vue? – No problem, just install the dependencies and create an app the way you normally would in a website. So long as you're receiving the action events, it will all just work.
|
40
|
-
|
41
|
-
Now let's build out a basic web applet that will say hello when we send it an action:
|
42
|
-
|
43
|
-
`index.html`:
|
44
|
-
|
45
|
-
```html
|
46
|
-
<!DOCTYPE html>
|
47
|
-
<html lang="en">
|
48
|
-
<script src="./main.js" type="module"></script>
|
49
|
-
<body>
|
50
|
-
Hello! <span id="name">whoever you are</span>.
|
51
|
-
</body>
|
52
|
-
</html>
|
53
|
-
```
|
54
|
-
|
55
|
-
Let's add some Web Applets functionality, so this can respond to a `set_name` action. You can do this by adding actions that a model can call, with each on accepting a parameters object that we can describe using JSONSchema.
|
56
|
-
|
57
|
-
`public/manifest.json`:
|
58
|
-
|
59
|
-
```js
|
60
|
-
{
|
61
|
-
// ...
|
62
|
-
"actions": [
|
63
|
-
{
|
64
|
-
"id": "set_name",
|
65
|
-
"description": "Sets the name of the user.",
|
66
|
-
"parameters": {
|
67
|
-
"type": "object",
|
68
|
-
"properties": {
|
69
|
-
"name": {
|
70
|
-
"type": "string"
|
71
|
-
}
|
72
|
-
},
|
73
|
-
"required": ["name"]
|
74
|
-
}
|
75
|
-
}
|
76
|
-
]
|
77
|
-
}
|
78
|
-
```
|
79
|
-
|
80
|
-
`main.js`:
|
81
|
-
|
82
|
-
```js
|
83
|
-
import { applets } from '@web-applets/sdk';
|
84
|
-
|
85
|
-
const context = applets.getContext();
|
86
|
-
|
87
|
-
// Define a 'set_name' action, and make it update the shared data object with the new name
|
88
|
-
context.setActionHandler('set_name', ({ name }) => {
|
89
|
-
context.data = { name };
|
90
|
-
});
|
91
|
-
|
92
|
-
// Whenever the data is updated, update the view
|
93
|
-
context.ondata = () => {
|
94
|
-
const nameElement = document.getElementById('name');
|
95
|
-
if (nameElement) {
|
96
|
-
nameElement.innerText = context.data.name;
|
97
|
-
}
|
98
|
-
};
|
99
|
-
```
|
100
|
-
|
101
|
-
To test out this applet, first start the dev server with `npm run dev`, and take note of the dev server URL. Then, fire up the Web Applets inspector by running `npx @web-applets/inspector`, and enter the dev URL into the URL bar up the top.
|
102
|
-
|
103
|
-

|
104
|
-
|
105
|
-
You can build this applet, by running `npm run build`, and host it on any static site host. This applet will now work in any environment where the SDK is installed.
|
106
|
-
|
107
|
-
## Integrating Web Applets into your client
|
108
|
-
|
109
|
-
In order to run, web applets need to be embedded in an environment that supports the Web Applets protocol. This might look like a browser (email me if you're interested!), or an electron app with `<webview>` tags, or sometthing as simple as a web-based AI chat client using iframes.
|
110
|
-
|
111
|
-
First, install & import the applets SDK in your client app:
|
112
|
-
|
113
|
-
```bash
|
114
|
-
npm install @web-applets/sdk
|
115
|
-
```
|
116
|
-
|
117
|
-
```js
|
118
|
-
import { applets } from '@web-applets/sdk';
|
119
|
-
```
|
120
|
-
|
121
|
-
Now you can import your applets from wherever they're being served from (note – you can also host them locally, or anywhere on the web):
|
122
|
-
|
123
|
-
```js
|
124
|
-
const applet = await applets.load('https://applets.unternet.co/maps');
|
125
|
-
applet.ondata = (e) => console.log(e.data);
|
126
|
-
applet.dispatchAction('set_name', { name: 'Web Applets' }); // console.log: { name: "Ada Lovelace" }
|
127
|
-
```
|
128
|
-
|
129
|
-
The above applet is actually running headless, but we can get it to display by attaching it to a container. For the loading step, instead run:
|
130
|
-
|
131
|
-
```js
|
132
|
-
const container = document.createElement('iframe');
|
133
|
-
document.body.appendChild(container);
|
134
|
-
const applet = await applets.load(`/helloworld.applet`, container);
|
135
|
-
```
|
136
|
-
|
137
|
-
To load pre-existing saved data into an applet, simply set the data property:
|
138
|
-
|
139
|
-
```js
|
140
|
-
applet.data = { name: 'Ada Lovelace' }; // console.log: { name: "Ada Lovelace" }
|
141
|
-
```
|
18
|
+

|
142
19
|
|
143
20
|
## Feedback & Community
|
144
21
|
|
145
22
|
This is a community project, and we're open to community members discussing the project direction, and submitting code!
|
146
23
|
|
147
|
-
|
24
|
+
- Join the [mailing list](https://buttondown.com/unternet).
|
25
|
+
- Follow [our blog](https://unternet.co/blog) for regular updates
|
26
|
+
- Join [our discord](https://discord.gg/VsMuEKmqvt)
|
148
27
|
|
149
28
|
## License
|
150
29
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { APPLET_CONNECT_TIMEOUT } from '../constants.js';
|
2
|
+
import { AppletConnectionError } from './errors.js';
|
3
|
+
import { Applet } from './applet.js';
|
4
|
+
import { AppletScope } from './applet-scope.js';
|
5
|
+
export class AppletFactory {
|
6
|
+
async connect(window) {
|
7
|
+
return new Promise((resolve, reject) => {
|
8
|
+
const applet = new Applet(window);
|
9
|
+
const timeout = setTimeout(() => {
|
10
|
+
reject(new AppletConnectionError(`Applet failed to connect before the timeout was reached (${APPLET_CONNECT_TIMEOUT}ms)`));
|
11
|
+
}, APPLET_CONNECT_TIMEOUT);
|
12
|
+
const listener = () => {
|
13
|
+
resolve(applet);
|
14
|
+
applet.removeEventListener('connect', listener);
|
15
|
+
clearTimeout(timeout);
|
16
|
+
};
|
17
|
+
applet.addEventListener('connect', listener);
|
18
|
+
});
|
19
|
+
}
|
20
|
+
register(manifest) {
|
21
|
+
return new AppletScope(manifest);
|
22
|
+
}
|
23
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { AppletActionDescriptor } from './actions.js';
|
2
|
+
import { AppletEvent } from './events.js';
|
3
|
+
import { AppletManifest } from '../utils.js';
|
4
|
+
export declare class AppletScope<DataType = any> extends EventTarget {
|
5
|
+
#private;
|
6
|
+
onconnect: (event: AppletEvent) => void;
|
7
|
+
onactions: (event: AppletEvent) => void;
|
8
|
+
ondata: (event: AppletEvent) => void;
|
9
|
+
constructor(manifest?: Object | undefined);
|
10
|
+
setActionHandler<T = any>(actionId: string, handler: Function): void;
|
11
|
+
defineAction(actionId: string, definition: AppletActionDescriptor & {
|
12
|
+
handler?: Function;
|
13
|
+
}): void;
|
14
|
+
set actions(actions: {
|
15
|
+
[id: string]: AppletActionDescriptor;
|
16
|
+
});
|
17
|
+
get actions(): {
|
18
|
+
[id: string]: AppletActionDescriptor;
|
19
|
+
};
|
20
|
+
get manifest(): AppletManifest;
|
21
|
+
get actionHandlers(): {
|
22
|
+
[id: string]: Function;
|
23
|
+
};
|
24
|
+
set actionHandlers(handlers: {
|
25
|
+
[id: string]: Function;
|
26
|
+
});
|
27
|
+
set data(data: DataType);
|
28
|
+
get data(): DataType;
|
29
|
+
get width(): number;
|
30
|
+
get height(): number;
|
31
|
+
}
|
@@ -0,0 +1,209 @@
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
6
|
+
};
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
11
|
+
};
|
12
|
+
var _AppletScope_instances, _AppletScope_actionHandlers, _AppletScope_manifest, _AppletScope_actions, _AppletScope_data, _AppletScope_dispatchEventAndHandler, _AppletScope_postMessage, _AppletScope_width, _AppletScope_height, _AppletScope_initialize, _AppletScope_handleMessage, _AppletScope_handleActionMessage, _AppletScope_createResizeObserver, _AppletScope_handleResize, _AppletScope_loadManifest;
|
13
|
+
import { debug } from '../debug.js';
|
14
|
+
import { AppletEvent } from './events.js';
|
15
|
+
import { dispatchEventAndHandler } from '../utils.js';
|
16
|
+
export class AppletScope extends EventTarget {
|
17
|
+
constructor(manifest) {
|
18
|
+
super();
|
19
|
+
_AppletScope_instances.add(this);
|
20
|
+
_AppletScope_actionHandlers.set(this, {});
|
21
|
+
_AppletScope_manifest.set(this, void 0);
|
22
|
+
_AppletScope_actions.set(this, void 0);
|
23
|
+
_AppletScope_data.set(this, void 0);
|
24
|
+
_AppletScope_dispatchEventAndHandler.set(this, void 0);
|
25
|
+
_AppletScope_postMessage.set(this, void 0);
|
26
|
+
_AppletScope_width.set(this, void 0);
|
27
|
+
_AppletScope_height.set(this, void 0);
|
28
|
+
debug.log('AppletScope', 'Constructor called');
|
29
|
+
__classPrivateFieldSet(this, _AppletScope_dispatchEventAndHandler, dispatchEventAndHandler.bind(this), "f");
|
30
|
+
if (manifest)
|
31
|
+
__classPrivateFieldSet(this, _AppletScope_manifest, manifest, "f");
|
32
|
+
// Listen for a connect event to set up message port
|
33
|
+
const appletConnectListener = (event) => {
|
34
|
+
if (event.source === window.parent &&
|
35
|
+
event.data.type === 'appletconnect' &&
|
36
|
+
event.ports &&
|
37
|
+
event.ports.length > 0) {
|
38
|
+
debug.log('AppletScope', 'Recieved message', event.data);
|
39
|
+
const port = event.ports[0];
|
40
|
+
__classPrivateFieldSet(this, _AppletScope_postMessage, port.postMessage.bind(port), "f");
|
41
|
+
port.onmessage = __classPrivateFieldGet(this, _AppletScope_instances, "m", _AppletScope_handleMessage).bind(this);
|
42
|
+
this.removeEventListener('message', appletConnectListener);
|
43
|
+
__classPrivateFieldGet(this, _AppletScope_instances, "m", _AppletScope_initialize).call(this);
|
44
|
+
}
|
45
|
+
};
|
46
|
+
window.addEventListener('message', appletConnectListener);
|
47
|
+
const connectMessage = {
|
48
|
+
type: 'appletconnect',
|
49
|
+
};
|
50
|
+
window.parent.postMessage(connectMessage, '*');
|
51
|
+
debug.log('AppletScope', 'Send message', connectMessage);
|
52
|
+
}
|
53
|
+
setActionHandler(actionId, handler) {
|
54
|
+
__classPrivateFieldGet(this, _AppletScope_actionHandlers, "f")[actionId] = handler;
|
55
|
+
}
|
56
|
+
defineAction(actionId, definition) {
|
57
|
+
const { handler, ...actionDefinition } = definition;
|
58
|
+
if (handler)
|
59
|
+
__classPrivateFieldGet(this, _AppletScope_actionHandlers, "f")[actionId] = handler;
|
60
|
+
this.actions = {
|
61
|
+
...this.actions,
|
62
|
+
[actionId]: actionDefinition,
|
63
|
+
};
|
64
|
+
}
|
65
|
+
set actions(actions) {
|
66
|
+
if (!actions)
|
67
|
+
return;
|
68
|
+
__classPrivateFieldSet(this, _AppletScope_actions, actions, "f");
|
69
|
+
const actionsMessage = {
|
70
|
+
type: 'actions',
|
71
|
+
actions: __classPrivateFieldGet(this, _AppletScope_actions, "f"),
|
72
|
+
};
|
73
|
+
debug.log('AppletScope', 'Send message', actionsMessage);
|
74
|
+
__classPrivateFieldGet(this, _AppletScope_postMessage, "f") && __classPrivateFieldGet(this, _AppletScope_postMessage, "f").call(this, actionsMessage);
|
75
|
+
// Set a timeout, so if data is set and a listener attached immediately after
|
76
|
+
// the listener will still fire
|
77
|
+
const dataEvent = new AppletEvent('actions', { actions });
|
78
|
+
setTimeout(() => __classPrivateFieldGet(this, _AppletScope_dispatchEventAndHandler, "f").call(this, dataEvent), 1);
|
79
|
+
}
|
80
|
+
get actions() {
|
81
|
+
return __classPrivateFieldGet(this, _AppletScope_actions, "f");
|
82
|
+
}
|
83
|
+
get manifest() {
|
84
|
+
return __classPrivateFieldGet(this, _AppletScope_manifest, "f");
|
85
|
+
}
|
86
|
+
get actionHandlers() {
|
87
|
+
return __classPrivateFieldGet(this, _AppletScope_actionHandlers, "f");
|
88
|
+
}
|
89
|
+
set actionHandlers(handlers) {
|
90
|
+
__classPrivateFieldSet(this, _AppletScope_actionHandlers, handlers, "f");
|
91
|
+
}
|
92
|
+
set data(data) {
|
93
|
+
__classPrivateFieldSet(this, _AppletScope_data, data, "f");
|
94
|
+
const dataMessage = {
|
95
|
+
type: 'data',
|
96
|
+
data,
|
97
|
+
};
|
98
|
+
debug.log('AppletScope', 'Send message', dataMessage);
|
99
|
+
__classPrivateFieldGet(this, _AppletScope_postMessage, "f") && __classPrivateFieldGet(this, _AppletScope_postMessage, "f").call(this, dataMessage);
|
100
|
+
// Set a timeout, so if data is set and a listener attached immediately after
|
101
|
+
// the listener will still fire
|
102
|
+
const dataEvent = new AppletEvent('data', { data });
|
103
|
+
setTimeout(() => __classPrivateFieldGet(this, _AppletScope_dispatchEventAndHandler, "f").call(this, dataEvent), 1);
|
104
|
+
}
|
105
|
+
get data() {
|
106
|
+
return __classPrivateFieldGet(this, _AppletScope_data, "f");
|
107
|
+
}
|
108
|
+
get width() {
|
109
|
+
return __classPrivateFieldGet(this, _AppletScope_width, "f");
|
110
|
+
}
|
111
|
+
get height() {
|
112
|
+
return __classPrivateFieldGet(this, _AppletScope_height, "f");
|
113
|
+
}
|
114
|
+
}
|
115
|
+
_AppletScope_actionHandlers = new WeakMap(), _AppletScope_manifest = new WeakMap(), _AppletScope_actions = new WeakMap(), _AppletScope_data = new WeakMap(), _AppletScope_dispatchEventAndHandler = new WeakMap(), _AppletScope_postMessage = new WeakMap(), _AppletScope_width = new WeakMap(), _AppletScope_height = new WeakMap(), _AppletScope_instances = new WeakSet(), _AppletScope_initialize = async function _AppletScope_initialize() {
|
116
|
+
const manifest = this.manifest ?? (await __classPrivateFieldGet(this, _AppletScope_instances, "m", _AppletScope_loadManifest).call(this));
|
117
|
+
__classPrivateFieldSet(this, _AppletScope_manifest, manifest || {}, "f");
|
118
|
+
__classPrivateFieldSet(this, _AppletScope_actions, __classPrivateFieldGet(this, _AppletScope_actions, "f") || manifest?.actions || {}, "f");
|
119
|
+
// Register the applet
|
120
|
+
const registerMessage = {
|
121
|
+
type: 'register',
|
122
|
+
manifest: __classPrivateFieldGet(this, _AppletScope_manifest, "f"),
|
123
|
+
actions: __classPrivateFieldGet(this, _AppletScope_actions, "f"),
|
124
|
+
data: __classPrivateFieldGet(this, _AppletScope_data, "f"),
|
125
|
+
};
|
126
|
+
__classPrivateFieldGet(this, _AppletScope_postMessage, "f").call(this, registerMessage);
|
127
|
+
debug.log('AppletScope', 'Send message', registerMessage);
|
128
|
+
const connectEvent = new AppletEvent('connect');
|
129
|
+
__classPrivateFieldGet(this, _AppletScope_dispatchEventAndHandler, "f").call(this, connectEvent);
|
130
|
+
__classPrivateFieldGet(this, _AppletScope_instances, "m", _AppletScope_createResizeObserver).call(this);
|
131
|
+
}, _AppletScope_handleMessage = function _AppletScope_handleMessage(messageEvent) {
|
132
|
+
const message = messageEvent.data;
|
133
|
+
debug.log('AppletScope', 'Recieved message', message);
|
134
|
+
switch (message.type) {
|
135
|
+
case 'data':
|
136
|
+
if ('data' in message)
|
137
|
+
this.data = message.data;
|
138
|
+
break;
|
139
|
+
case 'action':
|
140
|
+
if ('type' in message &&
|
141
|
+
message.type === 'action' &&
|
142
|
+
'id' in message &&
|
143
|
+
typeof message.id === 'string' &&
|
144
|
+
'actionId' in message &&
|
145
|
+
typeof message.actionId == 'string' &&
|
146
|
+
'arguments' in message) {
|
147
|
+
__classPrivateFieldGet(this, _AppletScope_instances, "m", _AppletScope_handleActionMessage).call(this, message);
|
148
|
+
}
|
149
|
+
break;
|
150
|
+
}
|
151
|
+
}, _AppletScope_handleActionMessage = async function _AppletScope_handleActionMessage(message) {
|
152
|
+
if (Object.keys(__classPrivateFieldGet(this, _AppletScope_actionHandlers, "f")).includes(message.actionId)) {
|
153
|
+
try {
|
154
|
+
await __classPrivateFieldGet(this, _AppletScope_actionHandlers, "f")[message.actionId](message.arguments);
|
155
|
+
const actionCompleteMessage = {
|
156
|
+
type: 'actioncomplete',
|
157
|
+
id: message.id,
|
158
|
+
};
|
159
|
+
__classPrivateFieldGet(this, _AppletScope_postMessage, "f").call(this, actionCompleteMessage);
|
160
|
+
}
|
161
|
+
catch (e) {
|
162
|
+
const actionErrorMessage = {
|
163
|
+
type: 'actionerror',
|
164
|
+
id: message.id,
|
165
|
+
message: e.message,
|
166
|
+
};
|
167
|
+
__classPrivateFieldGet(this, _AppletScope_postMessage, "f").call(this, actionErrorMessage);
|
168
|
+
console.error(e);
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}, _AppletScope_createResizeObserver = function _AppletScope_createResizeObserver() {
|
172
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
173
|
+
for (let entry of entries)
|
174
|
+
__classPrivateFieldGet(this, _AppletScope_instances, "m", _AppletScope_handleResize).call(this, {
|
175
|
+
width: entry.contentRect.width,
|
176
|
+
height: entry.contentRect.height,
|
177
|
+
});
|
178
|
+
});
|
179
|
+
resizeObserver.observe(document.querySelector('html'));
|
180
|
+
}, _AppletScope_handleResize = function _AppletScope_handleResize({ width, height }) {
|
181
|
+
__classPrivateFieldSet(this, _AppletScope_width, width, "f");
|
182
|
+
__classPrivateFieldSet(this, _AppletScope_height, height, "f");
|
183
|
+
const resizeMessage = {
|
184
|
+
type: 'resize',
|
185
|
+
width,
|
186
|
+
height,
|
187
|
+
};
|
188
|
+
debug.log('AppletScope', 'Send message', resizeMessage);
|
189
|
+
__classPrivateFieldGet(this, _AppletScope_postMessage, "f").call(this, resizeMessage);
|
190
|
+
}, _AppletScope_loadManifest = async function _AppletScope_loadManifest() {
|
191
|
+
const manifestLinkElem = document.querySelector('link[rel="manifest"]');
|
192
|
+
if (!manifestLinkElem)
|
193
|
+
return;
|
194
|
+
// TODO: Add timeout
|
195
|
+
try {
|
196
|
+
const manifestRequest = await fetch(manifestLinkElem.href);
|
197
|
+
const manifest = (await manifestRequest.json());
|
198
|
+
for (const key in manifest.actions) {
|
199
|
+
const action = manifest.actions[key];
|
200
|
+
if (action.params_schema && !Object.keys(action.params_schema).length) {
|
201
|
+
action.params_schema = undefined;
|
202
|
+
}
|
203
|
+
}
|
204
|
+
return manifest;
|
205
|
+
}
|
206
|
+
catch (e) {
|
207
|
+
return;
|
208
|
+
}
|
209
|
+
};
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { AppletManifest } from '../utils.js';
|
2
|
+
import { AppletEvent } from './events.js';
|
3
|
+
import { AppletActionDescriptor } from './actions.js';
|
4
|
+
export declare class Applet<DataType = any> extends EventTarget {
|
5
|
+
#private;
|
6
|
+
onconnect: (event: AppletEvent) => void;
|
7
|
+
onresize: (event: AppletEvent) => void;
|
8
|
+
onactions: (event: AppletEvent) => void;
|
9
|
+
ondata: (event: AppletEvent) => void;
|
10
|
+
constructor(targetWindow: Window);
|
11
|
+
sendAction(actionId: string, args: any): Promise<void>;
|
12
|
+
get data(): DataType;
|
13
|
+
set data(data: DataType);
|
14
|
+
get window(): Window;
|
15
|
+
get manifest(): AppletManifest;
|
16
|
+
get actions(): {
|
17
|
+
[id: string]: AppletActionDescriptor;
|
18
|
+
};
|
19
|
+
get width(): number;
|
20
|
+
get height(): number;
|
21
|
+
}
|
@@ -0,0 +1,161 @@
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
6
|
+
};
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
11
|
+
};
|
12
|
+
var _Applet_instances, _Applet_window, _Applet_actions, _Applet_manifest, _Applet_data, _Applet_dispatchEventAndHandler, _Applet_messagePort, _Applet_postMessage, _Applet_width, _Applet_height, _Applet_createMessageChannel, _Applet_handleMessage, _Applet_dispatchDataEvent, _Applet_dispatchActionsEvent;
|
13
|
+
import { RESPONSE_MESSAGE_TIMEOUT } from '../constants.js';
|
14
|
+
import { dispatchEventAndHandler } from '../utils.js';
|
15
|
+
import { AppletEvent } from './events.js';
|
16
|
+
import { debug } from '../debug.js';
|
17
|
+
import { AppletExecutionError } from './errors.js';
|
18
|
+
export class Applet extends EventTarget {
|
19
|
+
constructor(targetWindow) {
|
20
|
+
super();
|
21
|
+
_Applet_instances.add(this);
|
22
|
+
_Applet_window.set(this, void 0);
|
23
|
+
_Applet_actions.set(this, {});
|
24
|
+
_Applet_manifest.set(this, void 0);
|
25
|
+
_Applet_data.set(this, void 0);
|
26
|
+
_Applet_dispatchEventAndHandler.set(this, void 0);
|
27
|
+
_Applet_messagePort.set(this, void 0);
|
28
|
+
_Applet_postMessage.set(this, void 0);
|
29
|
+
_Applet_width.set(this, void 0);
|
30
|
+
_Applet_height.set(this, void 0);
|
31
|
+
debug.log('Applet', 'Constructor called');
|
32
|
+
__classPrivateFieldSet(this, _Applet_window, targetWindow, "f");
|
33
|
+
__classPrivateFieldSet(this, _Applet_dispatchEventAndHandler, dispatchEventAndHandler.bind(this), "f");
|
34
|
+
// Set up message port
|
35
|
+
__classPrivateFieldGet(this, _Applet_instances, "m", _Applet_createMessageChannel).call(this);
|
36
|
+
// In case the window hasn't loaded, wait for load then
|
37
|
+
const registerListener = (messageEvent) => {
|
38
|
+
if (messageEvent.source === __classPrivateFieldGet(this, _Applet_window, "f") &&
|
39
|
+
'type' in messageEvent.data &&
|
40
|
+
messageEvent.data.type === 'appletconnect') {
|
41
|
+
debug.log('Applet', 'Recieved message', messageEvent.data);
|
42
|
+
__classPrivateFieldGet(this, _Applet_instances, "m", _Applet_createMessageChannel).call(this);
|
43
|
+
this.removeEventListener('message', registerListener);
|
44
|
+
}
|
45
|
+
};
|
46
|
+
window.addEventListener('message', registerListener);
|
47
|
+
}
|
48
|
+
async sendAction(actionId, args) {
|
49
|
+
const actionMessage = {
|
50
|
+
id: crypto.randomUUID(),
|
51
|
+
type: 'action',
|
52
|
+
actionId,
|
53
|
+
arguments: args,
|
54
|
+
};
|
55
|
+
return new Promise((resolve, reject) => {
|
56
|
+
__classPrivateFieldGet(this, _Applet_postMessage, "f").call(this, actionMessage);
|
57
|
+
const timeout = setTimeout(() => {
|
58
|
+
reject(new AppletExecutionError(`Applet action handler failed to complete before timeout (${RESPONSE_MESSAGE_TIMEOUT}ms)`));
|
59
|
+
}, RESPONSE_MESSAGE_TIMEOUT);
|
60
|
+
const callback = (messageEvent) => {
|
61
|
+
const message = messageEvent.data;
|
62
|
+
if (['actioncomplete', 'actionerror'].includes(message.type) &&
|
63
|
+
'id' in message &&
|
64
|
+
message.id === actionMessage.id) {
|
65
|
+
__classPrivateFieldGet(this, _Applet_messagePort, "f").removeEventListener('message', callback);
|
66
|
+
clearTimeout(timeout);
|
67
|
+
if (message.type === 'actionerror') {
|
68
|
+
const actionErrorMessage = message;
|
69
|
+
reject(new AppletExecutionError(actionErrorMessage.message));
|
70
|
+
}
|
71
|
+
else {
|
72
|
+
resolve();
|
73
|
+
}
|
74
|
+
}
|
75
|
+
};
|
76
|
+
__classPrivateFieldGet(this, _Applet_messagePort, "f").addEventListener('message', callback);
|
77
|
+
});
|
78
|
+
}
|
79
|
+
get data() {
|
80
|
+
return __classPrivateFieldGet(this, _Applet_data, "f");
|
81
|
+
}
|
82
|
+
set data(data) {
|
83
|
+
__classPrivateFieldSet(this, _Applet_data, data, "f");
|
84
|
+
const dataMessage = {
|
85
|
+
type: 'data',
|
86
|
+
data,
|
87
|
+
};
|
88
|
+
__classPrivateFieldGet(this, _Applet_postMessage, "f").call(this, dataMessage);
|
89
|
+
}
|
90
|
+
get window() {
|
91
|
+
return __classPrivateFieldGet(this, _Applet_window, "f");
|
92
|
+
}
|
93
|
+
// TODO: Allow set for this.window, which re-attaches the applet
|
94
|
+
get manifest() {
|
95
|
+
return __classPrivateFieldGet(this, _Applet_manifest, "f");
|
96
|
+
}
|
97
|
+
get actions() {
|
98
|
+
return __classPrivateFieldGet(this, _Applet_actions, "f");
|
99
|
+
}
|
100
|
+
get width() {
|
101
|
+
return __classPrivateFieldGet(this, _Applet_width, "f");
|
102
|
+
}
|
103
|
+
get height() {
|
104
|
+
return __classPrivateFieldGet(this, _Applet_height, "f");
|
105
|
+
}
|
106
|
+
}
|
107
|
+
_Applet_window = new WeakMap(), _Applet_actions = new WeakMap(), _Applet_manifest = new WeakMap(), _Applet_data = new WeakMap(), _Applet_dispatchEventAndHandler = new WeakMap(), _Applet_messagePort = new WeakMap(), _Applet_postMessage = new WeakMap(), _Applet_width = new WeakMap(), _Applet_height = new WeakMap(), _Applet_instances = new WeakSet(), _Applet_createMessageChannel = function _Applet_createMessageChannel() {
|
108
|
+
if (__classPrivateFieldGet(this, _Applet_messagePort, "f"))
|
109
|
+
__classPrivateFieldGet(this, _Applet_messagePort, "f").close();
|
110
|
+
const messageChannel = new MessageChannel();
|
111
|
+
const connectMessage = {
|
112
|
+
type: 'appletconnect',
|
113
|
+
};
|
114
|
+
debug.log('Applet', 'Send message', connectMessage);
|
115
|
+
__classPrivateFieldSet(this, _Applet_messagePort, messageChannel.port1, "f");
|
116
|
+
__classPrivateFieldGet(this, _Applet_messagePort, "f").onmessage = __classPrivateFieldGet(this, _Applet_instances, "m", _Applet_handleMessage).bind(this);
|
117
|
+
__classPrivateFieldGet(this, _Applet_window, "f").postMessage(connectMessage, '*', [messageChannel.port2]);
|
118
|
+
__classPrivateFieldSet(this, _Applet_postMessage, __classPrivateFieldGet(this, _Applet_messagePort, "f").postMessage.bind(__classPrivateFieldGet(this, _Applet_messagePort, "f")), "f");
|
119
|
+
}, _Applet_handleMessage = function _Applet_handleMessage(messageEvent) {
|
120
|
+
const message = messageEvent.data;
|
121
|
+
debug.log('Applet', 'Recieved message', message);
|
122
|
+
switch (message.type) {
|
123
|
+
case 'register':
|
124
|
+
const registerMessage = message;
|
125
|
+
__classPrivateFieldSet(this, _Applet_manifest, registerMessage.manifest, "f");
|
126
|
+
const connectEvent = new AppletEvent('connect');
|
127
|
+
__classPrivateFieldGet(this, _Applet_dispatchEventAndHandler, "f").call(this, connectEvent);
|
128
|
+
__classPrivateFieldSet(this, _Applet_actions, registerMessage.actions, "f");
|
129
|
+
__classPrivateFieldGet(this, _Applet_instances, "m", _Applet_dispatchActionsEvent).call(this, registerMessage.actions);
|
130
|
+
__classPrivateFieldSet(this, _Applet_data, registerMessage.data, "f");
|
131
|
+
__classPrivateFieldGet(this, _Applet_instances, "m", _Applet_dispatchDataEvent).call(this, registerMessage.data);
|
132
|
+
break;
|
133
|
+
case 'data':
|
134
|
+
const dataMessage = message;
|
135
|
+
__classPrivateFieldSet(this, _Applet_data, dataMessage.data, "f");
|
136
|
+
__classPrivateFieldGet(this, _Applet_instances, "m", _Applet_dispatchDataEvent).call(this, dataMessage.data);
|
137
|
+
break;
|
138
|
+
case 'resize':
|
139
|
+
const resizeMessage = message;
|
140
|
+
__classPrivateFieldSet(this, _Applet_width, resizeMessage.width, "f");
|
141
|
+
__classPrivateFieldSet(this, _Applet_height, resizeMessage.height, "f");
|
142
|
+
const resizeEvent = new AppletEvent('resize');
|
143
|
+
__classPrivateFieldGet(this, _Applet_dispatchEventAndHandler, "f").call(this, resizeEvent);
|
144
|
+
break;
|
145
|
+
case 'actions':
|
146
|
+
const actionsMessage = message;
|
147
|
+
__classPrivateFieldSet(this, _Applet_actions, actionsMessage.actions, "f");
|
148
|
+
__classPrivateFieldGet(this, _Applet_instances, "m", _Applet_dispatchActionsEvent).call(this, actionsMessage.actions);
|
149
|
+
break;
|
150
|
+
}
|
151
|
+
}, _Applet_dispatchDataEvent = function _Applet_dispatchDataEvent(data) {
|
152
|
+
const actionsEvent = new AppletEvent('data', {
|
153
|
+
data,
|
154
|
+
});
|
155
|
+
__classPrivateFieldGet(this, _Applet_dispatchEventAndHandler, "f").call(this, actionsEvent);
|
156
|
+
}, _Applet_dispatchActionsEvent = function _Applet_dispatchActionsEvent(actions) {
|
157
|
+
const actionsEvent = new AppletEvent('actions', {
|
158
|
+
actions,
|
159
|
+
});
|
160
|
+
__classPrivateFieldGet(this, _Applet_dispatchEventAndHandler, "f").call(this, actionsEvent);
|
161
|
+
};
|
@@ -0,0 +1,12 @@
|
|
1
|
+
export class AppletExecutionError extends Error {
|
2
|
+
constructor(message) {
|
3
|
+
super(message);
|
4
|
+
this.name = 'AppletExecutionError';
|
5
|
+
}
|
6
|
+
}
|
7
|
+
export class AppletConnectionError extends Error {
|
8
|
+
constructor(message) {
|
9
|
+
super(message);
|
10
|
+
this.name = 'AppletConnectionError';
|
11
|
+
}
|
12
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { AppletActionDescriptor } from './actions.js';
|
2
|
+
export type AppletEventType = 'connect' | 'actions' | 'resize' | 'data';
|
3
|
+
export interface AppletEventInit extends EventInit {
|
4
|
+
data?: any;
|
5
|
+
actions?: {
|
6
|
+
[id: string]: AppletActionDescriptor;
|
7
|
+
};
|
8
|
+
}
|
9
|
+
export declare class AppletEvent extends Event {
|
10
|
+
data?: any;
|
11
|
+
actions?: {
|
12
|
+
[id: string]: AppletActionDescriptor;
|
13
|
+
};
|
14
|
+
constructor(type: AppletEventType, init?: AppletEventInit | undefined);
|
15
|
+
}
|