@web-applets/sdk 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +29 -138
- package/dist/components/applet-frame.d.ts +19 -0
- package/dist/components/applet-frame.js +93 -0
- package/dist/core/applet.d.ts +21 -0
- package/dist/core/applet.js +117 -0
- package/dist/core/context.d.ts +32 -0
- package/dist/core/context.js +138 -0
- package/dist/core/host.d.ts +31 -0
- package/dist/core/host.js +134 -0
- package/dist/core/shared.d.ts +121 -0
- package/dist/core/shared.js +171 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +9 -6
- package/dist/lib/utils.d.ts +17 -0
- package/dist/lib/utils.js +37 -0
- package/dist/utils.d.ts +3 -4
- package/dist/utils.js +10 -30
- package/package.json +1 -1
- package/dist/client.d.ts +0 -32
- package/dist/client.js +0 -197
- package/dist/context.d.ts +0 -27
- package/dist/context.js +0 -136
- package/dist/types.d.ts +0 -74
- package/dist/types.js +0 -21
- package/dist/web-component/index.d.ts +0 -1
- package/dist/web-component/index.js +0 -52
- package/dist/web-components/applet-frame.d.ts +0 -1
- package/dist/web-components/applet-frame.js +0 -52
package/README.md
CHANGED
@@ -33,7 +33,7 @@ Web Applets aims to do for AI-enabled software what the web did for documents -
|
|
33
33
|
|
34
34
|
## Example
|
35
35
|
|
36
|
-
|
36
|
+
Let's say we have a simple website that says hello. It might look something like this:
|
37
37
|
|
38
38
|
`index.html`:
|
39
39
|
|
@@ -47,125 +47,51 @@ This is a simple applet that prints "Hello, [your name]" when given the `set_nam
|
|
47
47
|
</html>
|
48
48
|
```
|
49
49
|
|
50
|
+
Let's add some Web Applets functionality, so this can respond to a `set_name` message:
|
51
|
+
|
50
52
|
`main.js`:
|
51
53
|
|
52
54
|
```js
|
53
|
-
import {
|
54
|
-
|
55
|
-
// Get view element we want to manipulate
|
56
|
-
const nameElem = document.getElementById('name');
|
55
|
+
import { applets } from '@web-applets/sdk';
|
57
56
|
|
58
|
-
|
59
|
-
const applet = appletContext.connect();
|
57
|
+
const context = applets.getContext();
|
60
58
|
|
61
|
-
//
|
62
|
-
|
63
|
-
|
59
|
+
// Define a 'set_name' action, and make it update the shared data object with the new name
|
60
|
+
context.defineAction('set_name', {
|
61
|
+
params: {
|
62
|
+
name: {
|
63
|
+
type: string,
|
64
|
+
description: 'The name of the person to be greeted.',
|
65
|
+
},
|
66
|
+
},
|
67
|
+
handler: ({ name }) => applet.data = { name };
|
64
68
|
});
|
65
69
|
|
66
|
-
// Whenever
|
67
|
-
|
68
|
-
|
70
|
+
// Whenever the data is updated, update the view
|
71
|
+
context.ondata = () => {
|
72
|
+
document.getElementById('name').innerText = applet.data.name;
|
69
73
|
};
|
70
74
|
```
|
71
75
|
|
72
|
-
|
73
|
-
|
74
|
-
```json
|
75
|
-
{
|
76
|
-
"type": "applet",
|
77
|
-
"name": "Hello World",
|
78
|
-
"description": "Displays a greeting to the user.",
|
79
|
-
"entrypoint": "index.html",
|
80
|
-
"actions": [
|
81
|
-
{
|
82
|
-
"id": "set_name",
|
83
|
-
"description": "Sets the name of the user to be greeted",
|
84
|
-
"params": {
|
85
|
-
"name": {
|
86
|
-
"type": "string",
|
87
|
-
"description": "The name of the user"
|
88
|
-
}
|
89
|
-
}
|
90
|
-
}
|
91
|
-
]
|
92
|
-
}
|
93
|
-
```
|
76
|
+
Done! If you load this up in the inspector and introduce yourself, it will respond by greeting you.
|
94
77
|
|
95
78
|
## Getting started
|
96
79
|
|
97
|
-
|
80
|
+
Create a new web app with the applets SDK installed. You can do this quickly using our CLI:
|
98
81
|
|
99
82
|
```bash
|
100
|
-
|
101
|
-
npm i --save-dev @web-applets/cli
|
83
|
+
npx @web-applets/create
|
102
84
|
```
|
103
85
|
|
104
|
-
|
105
|
-
|
106
|
-
```bash
|
107
|
-
npx applets init
|
108
|
-
npx applets create <your-applet-name>
|
109
|
-
```
|
110
|
-
|
111
|
-
This creates an applet folder, with a build system built-in using Vite. You can change this to anything you want. We recommend building at this stage, as the SDK currently needs to be bundled. We're working on adding a statically hosted script to import.
|
112
|
-
|
113
|
-
Inside your applet folder, you'll find a basic web app setup:
|
86
|
+
Inside the generated folder, you'll find a basic web app setup:
|
114
87
|
|
115
|
-
- `public/manifest.json`:
|
88
|
+
- `public/manifest.json`: A web app manifest, useful when publishing your applet, adding icons, etc.
|
116
89
|
- `index.html`: Much like a website, this holds the main page for your applet
|
117
90
|
- `src/main.ts`: Declares functions that respond to each action, and a render function that updates the view based on state
|
118
91
|
|
119
92
|
> 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.
|
120
93
|
|
121
|
-
|
122
|
-
|
123
|
-
```js
|
124
|
-
{
|
125
|
-
// ...
|
126
|
-
"actions": [
|
127
|
-
{
|
128
|
-
"id": "set_name",
|
129
|
-
"description": "Sets the name of the user to be greeted",
|
130
|
-
"params": {
|
131
|
-
"name": {
|
132
|
-
"type": "string",
|
133
|
-
"description": "The name of the user"
|
134
|
-
}
|
135
|
-
}
|
136
|
-
}
|
137
|
-
]
|
138
|
-
}
|
139
|
-
```
|
140
|
-
|
141
|
-
Now let's update `src/main.ts` to assign an action handler:
|
142
|
-
|
143
|
-
```js
|
144
|
-
// First, import the SDK
|
145
|
-
import { appletContext } from '../../sdk/src';
|
146
|
-
|
147
|
-
// Now connect to the applet runtime
|
148
|
-
const applet = appletContext.connect();
|
149
|
-
|
150
|
-
// Attach the action handler, and update the state
|
151
|
-
applet.setActionHandler('set_name', ({ name }) => {
|
152
|
-
applet.setState({ name });
|
153
|
-
});
|
154
|
-
```
|
155
|
-
|
156
|
-
When this state updates, it will inform the client which can then store the state somewhere, for example in a database so the applet will persist between uses.
|
157
|
-
|
158
|
-
Finally, we need to render the applet whenever a render signal is received. Again in `main.ts`:
|
159
|
-
|
160
|
-
```js
|
161
|
-
// ...
|
162
|
-
|
163
|
-
applet.onrender = () => {
|
164
|
-
document.body.innerText = `Hello, ${applet.state.name}!`;
|
165
|
-
};
|
166
|
-
```
|
167
|
-
|
168
|
-
Now if you run `npx applets playground`, you should be able to test out your new applet action directly. This applet will now work in any environment where the SDK is installed.
|
94
|
+
Now if you run `npx @web-applets/inspector`, you should be able to test out your new applet directly. This applet will now work in any environment where the SDK is installed.
|
169
95
|
|
170
96
|
![A screenshot showing the 'playground' editing UI, with a web applets showing 'Hello, Web Applets'](docs/assets/web-applets-playground.png)
|
171
97
|
|
@@ -173,16 +99,12 @@ Now if you run `npx applets playground`, you should be able to test out your new
|
|
173
99
|
|
174
100
|
Using Web Applets is just as easy as creating them!
|
175
101
|
|
176
|
-
|
177
|
-
|
178
|
-
Then, run:
|
102
|
+
Install & import the applets client in your app:
|
179
103
|
|
180
104
|
```bash
|
181
|
-
|
105
|
+
npm install @web-applets/sdk
|
182
106
|
```
|
183
107
|
|
184
|
-
Now in your main app, you can import the applets client:
|
185
|
-
|
186
108
|
```js
|
187
109
|
import { applets } from '@web-applets/sdk';
|
188
110
|
```
|
@@ -190,8 +112,8 @@ import { applets } from '@web-applets/sdk';
|
|
190
112
|
Now you can import your applets from wherever they're being served from (note – you can also host them anywhere on the web):
|
191
113
|
|
192
114
|
```js
|
193
|
-
const applet = await applets.load('/helloworld.applet'); // replace with
|
194
|
-
applet.
|
115
|
+
const applet = await applets.load('/helloworld.applet'); // replace with an https URL if hosted remotely
|
116
|
+
applet.ondata = (e) => console.log(e.data);
|
195
117
|
applet.dispatchAction('set_name', { name: 'Web Applets' });
|
196
118
|
```
|
197
119
|
|
@@ -203,44 +125,13 @@ document.body.appendChild(container);
|
|
203
125
|
const applet = await applets.load(`/helloworld.applet`, container);
|
204
126
|
```
|
205
127
|
|
206
|
-
To load pre-existing saved
|
128
|
+
To load pre-existing saved data into an applet, simply set the data property:
|
207
129
|
|
208
130
|
```js
|
209
|
-
applet.
|
131
|
+
applet.data = { name: 'Ada Lovelace' };
|
210
132
|
// console.log: { name: "Ada Lovelace" }
|
211
133
|
```
|
212
134
|
|
213
|
-
It may also be helpful to check available applets at a domain, or in your public folder. For that you can extract the applet headers from the App Manifest at the public root (`/manifest.json`), and see the available applets and a shorthand for the actions you can take in them. This is automatically created when you build your applets.
|
214
|
-
|
215
|
-
```js
|
216
|
-
const applets = await applets.list('/');
|
217
|
-
```
|
218
|
-
|
219
|
-
This applets object looks like:
|
220
|
-
|
221
|
-
```js
|
222
|
-
{
|
223
|
-
'/helloworld.applet': {
|
224
|
-
name: 'Hello World',
|
225
|
-
description: 'Displays a greeting to the user.',
|
226
|
-
url: '/applets/helloworld.applet',
|
227
|
-
actions: {
|
228
|
-
set_name: {
|
229
|
-
description: 'Sets the name of the user to be greeted',
|
230
|
-
params: {
|
231
|
-
name: {
|
232
|
-
type: 'string',
|
233
|
-
description: 'The name of the user'
|
234
|
-
}
|
235
|
-
},
|
236
|
-
},
|
237
|
-
},
|
238
|
-
// ...
|
239
|
-
};
|
240
|
-
```
|
241
|
-
|
242
|
-
You can use it to present a quick summary of available tools to your model, and then decide on an applet and action to use.
|
243
|
-
|
244
135
|
## Feedback & Community
|
245
136
|
|
246
137
|
This is a community project, and we're open to community members discussing the project direction, and submitting code!
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { Applet } from '../index';
|
2
|
+
export declare class AppletFrame extends HTMLElement {
|
3
|
+
#private;
|
4
|
+
container?: HTMLIFrameElement;
|
5
|
+
applet?: Applet;
|
6
|
+
loaded?: boolean;
|
7
|
+
static observedAttributes: string[];
|
8
|
+
connectedCallback(): void;
|
9
|
+
set src(value: string);
|
10
|
+
get src(): string;
|
11
|
+
attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
|
12
|
+
loadApplet(url: string): Promise<void>;
|
13
|
+
set data(data: any);
|
14
|
+
resizeContainer(dimensions: {
|
15
|
+
height: number;
|
16
|
+
width: number;
|
17
|
+
}): void;
|
18
|
+
get styles(): string;
|
19
|
+
}
|
@@ -0,0 +1,93 @@
|
|
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 _AppletFrame_root, _AppletFrame_src;
|
13
|
+
import { applets, } from '../index';
|
14
|
+
// TODO: Add resize event handler, and resize DOM element
|
15
|
+
export class AppletFrame extends HTMLElement {
|
16
|
+
constructor() {
|
17
|
+
super(...arguments);
|
18
|
+
_AppletFrame_root.set(this, void 0);
|
19
|
+
_AppletFrame_src.set(this, void 0);
|
20
|
+
}
|
21
|
+
connectedCallback() {
|
22
|
+
__classPrivateFieldSet(this, _AppletFrame_root, this.attachShadow({ mode: 'open' }), "f");
|
23
|
+
this.container = document.createElement('iframe');
|
24
|
+
__classPrivateFieldGet(this, _AppletFrame_root, "f").appendChild(this.container);
|
25
|
+
const styles = document.createElement('style');
|
26
|
+
styles.textContent = this.styles;
|
27
|
+
__classPrivateFieldGet(this, _AppletFrame_root, "f").appendChild(styles);
|
28
|
+
const url = this.getAttribute('src');
|
29
|
+
if (url)
|
30
|
+
this.loadApplet(url);
|
31
|
+
}
|
32
|
+
set src(value) {
|
33
|
+
__classPrivateFieldSet(this, _AppletFrame_src, value, "f");
|
34
|
+
this.loadApplet(value);
|
35
|
+
}
|
36
|
+
get src() {
|
37
|
+
return __classPrivateFieldGet(this, _AppletFrame_src, "f");
|
38
|
+
}
|
39
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
40
|
+
if (name === 'src') {
|
41
|
+
this.src = newValue;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
async loadApplet(url) {
|
45
|
+
if (!this.container)
|
46
|
+
return;
|
47
|
+
this.applet = await applets.load(url, this.container);
|
48
|
+
// When data received, bubble the event up
|
49
|
+
this.applet.ondata = (dataEvent) => {
|
50
|
+
this.dispatchEvent(dataEvent);
|
51
|
+
};
|
52
|
+
// Resize
|
53
|
+
this.applet.onresize = (resizeEvent) => {
|
54
|
+
this.resizeContainer(resizeEvent.dimensions);
|
55
|
+
};
|
56
|
+
this.applet.onactions = (e) => { };
|
57
|
+
// Emit a load event when loading complete
|
58
|
+
this.dispatchEvent(new Event('load'));
|
59
|
+
this.loaded = true;
|
60
|
+
}
|
61
|
+
set data(data) {
|
62
|
+
if (this.applet && this.loaded) {
|
63
|
+
this.applet.data = data;
|
64
|
+
}
|
65
|
+
else {
|
66
|
+
const loadListener = () => {
|
67
|
+
this.applet.data = data;
|
68
|
+
this.removeEventListener('load', loadListener);
|
69
|
+
};
|
70
|
+
this.addEventListener('load', loadListener);
|
71
|
+
}
|
72
|
+
}
|
73
|
+
resizeContainer(dimensions) {
|
74
|
+
this.container.style.height = `${dimensions.height + 2}px`;
|
75
|
+
}
|
76
|
+
get styles() {
|
77
|
+
return /*css*/ `
|
78
|
+
:host {
|
79
|
+
display: flex;
|
80
|
+
flex-direction: column;
|
81
|
+
}
|
82
|
+
|
83
|
+
iframe {
|
84
|
+
border: none;
|
85
|
+
height: 100%;
|
86
|
+
width: 100%;
|
87
|
+
}
|
88
|
+
`;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
_AppletFrame_root = new WeakMap(), _AppletFrame_src = new WeakMap();
|
92
|
+
AppletFrame.observedAttributes = ['src'];
|
93
|
+
customElements.define('applet-frame', AppletFrame);
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { AppletAction, AppletMessage, ActionParams, AppletManifest, AppletDataEvent, AppletResizeEvent, AppletActionsEvent, AppletMessageRelay, AppletReadyEvent } from './shared';
|
2
|
+
export declare function load(url: string, container?: HTMLIFrameElement): Promise<Applet>;
|
3
|
+
export interface AppletOptions {
|
4
|
+
manifest: AppletManifest;
|
5
|
+
container: HTMLIFrameElement;
|
6
|
+
}
|
7
|
+
export declare class Applet<T = any> extends EventTarget {
|
8
|
+
#private;
|
9
|
+
messageRelay: AppletMessageRelay;
|
10
|
+
actions: AppletAction[];
|
11
|
+
container: HTMLIFrameElement;
|
12
|
+
constructor(manifest: AppletManifest, targetWindow: Window);
|
13
|
+
dispatchAction(actionId: string, params?: ActionParams): Promise<AppletMessage>;
|
14
|
+
get data(): T;
|
15
|
+
set data(data: T);
|
16
|
+
get manifest(): AppletManifest;
|
17
|
+
onready(event: AppletReadyEvent): void;
|
18
|
+
onresize(event: AppletResizeEvent): void;
|
19
|
+
onactions(event: AppletActionsEvent): void;
|
20
|
+
ondata(event: AppletDataEvent): void;
|
21
|
+
}
|
@@ -0,0 +1,117 @@
|
|
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_manifest, _Applet_data, _Applet_addListeners;
|
13
|
+
import { parseUrl } from '../utils';
|
14
|
+
import { AppletDataMessage, AppletInitMessage, AppletDataEvent, AppletResizeEvent, AppletActionsEvent, AppletMessageRelay, loadManifest, AppletReadyEvent, AppletActionMessage, } from './shared';
|
15
|
+
// Container for initializing applets without an explicit container
|
16
|
+
const hiddenContainer = document.createElement('iframe');
|
17
|
+
hiddenContainer.style.display = 'none';
|
18
|
+
document.body.appendChild(hiddenContainer);
|
19
|
+
// Options for loading an applet
|
20
|
+
// interface LoadOpts {
|
21
|
+
// unsafe?: boolean;
|
22
|
+
// }
|
23
|
+
// const defaultOpts: LoadOpts = {
|
24
|
+
// unsafe: false,
|
25
|
+
// };
|
26
|
+
// Load an applet object from a URL
|
27
|
+
export async function load(url, container
|
28
|
+
// opts?: LoadOpts
|
29
|
+
) {
|
30
|
+
if (!container)
|
31
|
+
container = hiddenContainer;
|
32
|
+
url = parseUrl(url);
|
33
|
+
const manifest = await loadManifest(url);
|
34
|
+
// If unsafe enabled, allow same origin sandbox
|
35
|
+
// This is required for e.g. YouTube embeds
|
36
|
+
// const _opts = Object.assign(defaultOpts, opts ?? {});
|
37
|
+
// if (_opts.unsafe && manifest.unsafe) {
|
38
|
+
// container.setAttribute(
|
39
|
+
// 'sandbox',
|
40
|
+
// 'allow-scripts allow-forms allow-same-origin'
|
41
|
+
// );
|
42
|
+
// } else {
|
43
|
+
// container.setAttribute('sandbox', 'allow-scripts allow-forms');
|
44
|
+
// }
|
45
|
+
container.setAttribute('sandbox', 'allow-scripts allow-forms');
|
46
|
+
container.src = url;
|
47
|
+
const applet = new Applet(manifest, container.contentWindow);
|
48
|
+
return new Promise((resolve) => {
|
49
|
+
applet.onready = () => resolve(applet);
|
50
|
+
});
|
51
|
+
}
|
52
|
+
export class Applet extends EventTarget {
|
53
|
+
constructor(manifest, targetWindow) {
|
54
|
+
super();
|
55
|
+
_Applet_instances.add(this);
|
56
|
+
this.actions = [];
|
57
|
+
_Applet_manifest.set(this, void 0);
|
58
|
+
_Applet_data.set(this, void 0);
|
59
|
+
this.messageRelay = new AppletMessageRelay(targetWindow);
|
60
|
+
__classPrivateFieldSet(this, _Applet_manifest, manifest, "f");
|
61
|
+
__classPrivateFieldGet(this, _Applet_instances, "m", _Applet_addListeners).call(this);
|
62
|
+
this.messageRelay.on('ready', () => {
|
63
|
+
this.messageRelay.send(new AppletInitMessage());
|
64
|
+
});
|
65
|
+
}
|
66
|
+
async dispatchAction(actionId, params) {
|
67
|
+
const actionMessage = new AppletActionMessage({
|
68
|
+
actionId,
|
69
|
+
params,
|
70
|
+
});
|
71
|
+
return await this.messageRelay.send(actionMessage);
|
72
|
+
}
|
73
|
+
get data() {
|
74
|
+
return __classPrivateFieldGet(this, _Applet_data, "f");
|
75
|
+
}
|
76
|
+
set data(data) {
|
77
|
+
__classPrivateFieldSet(this, _Applet_data, data, "f");
|
78
|
+
this.messageRelay.send(new AppletDataMessage({ data }));
|
79
|
+
}
|
80
|
+
get manifest() {
|
81
|
+
return __classPrivateFieldGet(this, _Applet_manifest, "f");
|
82
|
+
}
|
83
|
+
onready(event) { }
|
84
|
+
onresize(event) { }
|
85
|
+
onactions(event) { }
|
86
|
+
ondata(event) { }
|
87
|
+
}
|
88
|
+
_Applet_manifest = new WeakMap(), _Applet_data = new WeakMap(), _Applet_instances = new WeakSet(), _Applet_addListeners = function _Applet_addListeners() {
|
89
|
+
this.messageRelay.on('ready', (message) => {
|
90
|
+
const readyEvent = new AppletReadyEvent();
|
91
|
+
if (typeof this.onready === 'function')
|
92
|
+
this.onready(readyEvent);
|
93
|
+
this.dispatchEvent(readyEvent);
|
94
|
+
});
|
95
|
+
this.messageRelay.on('data', (message) => {
|
96
|
+
__classPrivateFieldSet(this, _Applet_data, message.data, "f");
|
97
|
+
const dataEvent = new AppletDataEvent({ data: message.data });
|
98
|
+
if (typeof this.ondata === 'function')
|
99
|
+
this.ondata(dataEvent);
|
100
|
+
this.dispatchEvent(dataEvent);
|
101
|
+
});
|
102
|
+
this.messageRelay.on('resize', (message) => {
|
103
|
+
const resizeEvent = new AppletResizeEvent({
|
104
|
+
dimensions: message.dimensions,
|
105
|
+
});
|
106
|
+
if (typeof this.onresize === 'function')
|
107
|
+
this.onresize(resizeEvent);
|
108
|
+
this.dispatchEvent(resizeEvent);
|
109
|
+
});
|
110
|
+
this.messageRelay.on('actions', (message) => {
|
111
|
+
this.actions = message.actions;
|
112
|
+
const actionsEvent = new AppletActionsEvent({ actions: message.actions });
|
113
|
+
if (typeof this.onactions === 'function')
|
114
|
+
this.onactions(actionsEvent);
|
115
|
+
this.dispatchEvent(actionsEvent);
|
116
|
+
});
|
117
|
+
};
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { ActionParams, AppletDataEvent, AppletLoadEvent, AppletReadyEvent, JSONSchemaProperties, AppletManifest, AppletAction, AppletMessageRelay } from './shared';
|
2
|
+
export type ActionHandler<T extends ActionParams> = (params: T) => void | Promise<void>;
|
3
|
+
export type ActionHandlerDict = {
|
4
|
+
[key: string]: ActionHandler<any>;
|
5
|
+
};
|
6
|
+
export declare class AppletContext extends EventTarget {
|
7
|
+
#private;
|
8
|
+
messageRelay: AppletMessageRelay;
|
9
|
+
actionHandlers: ActionHandlerDict;
|
10
|
+
manifest: AppletManifest;
|
11
|
+
constructor();
|
12
|
+
connect(): void;
|
13
|
+
initialize(): Promise<void>;
|
14
|
+
createResizeObserver(): void;
|
15
|
+
attachListeners(): void;
|
16
|
+
setActionHandler<T = ActionParams>(actionId: string, handler: ActionHandler<T>): void;
|
17
|
+
defineAction<T = ActionParams>(actionId: string, definition: ActionDefinition<T>): void;
|
18
|
+
set actions(actions: AppletAction[]);
|
19
|
+
get actions(): AppletAction[];
|
20
|
+
set data(data: any);
|
21
|
+
get data(): any;
|
22
|
+
setData(data: any): Promise<void>;
|
23
|
+
onload(event: AppletLoadEvent): Promise<void> | void;
|
24
|
+
onready(event: AppletReadyEvent): void;
|
25
|
+
ondata(event: AppletDataEvent): void;
|
26
|
+
}
|
27
|
+
interface ActionDefinition<T> extends Omit<AppletAction, 'id'> {
|
28
|
+
params?: JSONSchemaProperties;
|
29
|
+
handler?: ActionHandler<T>;
|
30
|
+
}
|
31
|
+
export declare function getContext(): AppletContext;
|
32
|
+
export {};
|
@@ -0,0 +1,138 @@
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
3
|
+
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");
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
5
|
+
};
|
6
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
7
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
9
|
+
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");
|
10
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
11
|
+
};
|
12
|
+
var _AppletContext_actions, _AppletContext_data;
|
13
|
+
import { AppletMessage, AppletDataEvent, AppletLoadEvent, AppletReadyEvent, AppletActionsMessage, AppletReadyMessage, AppletMessageRelay, } from './shared';
|
14
|
+
export class AppletContext extends EventTarget {
|
15
|
+
constructor() {
|
16
|
+
super();
|
17
|
+
this.actionHandlers = {};
|
18
|
+
_AppletContext_actions.set(this, {});
|
19
|
+
_AppletContext_data.set(this, void 0);
|
20
|
+
this.connect();
|
21
|
+
}
|
22
|
+
connect() {
|
23
|
+
this.messageRelay = new AppletMessageRelay(window.parent);
|
24
|
+
// When document loads/if it's loaded, call the initialize function
|
25
|
+
if (document.readyState === 'complete' ||
|
26
|
+
document.readyState === 'interactive') {
|
27
|
+
// Document has loaded already.
|
28
|
+
// Timeout added so if the caller defines the onload function, it will exist by now
|
29
|
+
setTimeout(this.initialize.bind(this), 1);
|
30
|
+
}
|
31
|
+
else {
|
32
|
+
// Document not yet loaded, we'll add an event listener to call when it does
|
33
|
+
window.addEventListener('DOMContentLoaded', this.initialize.bind(this));
|
34
|
+
}
|
35
|
+
this.createResizeObserver();
|
36
|
+
this.attachListeners();
|
37
|
+
}
|
38
|
+
async initialize() {
|
39
|
+
const manifestLinkElem = document.querySelector('link[rel="manifest"]');
|
40
|
+
if (!manifestLinkElem)
|
41
|
+
return;
|
42
|
+
try {
|
43
|
+
const manifestRequest = await fetch(manifestLinkElem.href);
|
44
|
+
const manifest = await manifestRequest.json();
|
45
|
+
this.manifest = manifest;
|
46
|
+
this.actions = manifest.actions ?? [];
|
47
|
+
}
|
48
|
+
catch (e) {
|
49
|
+
return;
|
50
|
+
}
|
51
|
+
// Call the onload function
|
52
|
+
const loadEvent = new AppletLoadEvent();
|
53
|
+
this.dispatchEvent(loadEvent);
|
54
|
+
if (typeof this.onload === 'function')
|
55
|
+
await this.onload(loadEvent);
|
56
|
+
// Tell the host we're ready
|
57
|
+
this.messageRelay.send(new AppletReadyMessage());
|
58
|
+
// Emit a local ready event
|
59
|
+
const readyEvent = new AppletReadyEvent();
|
60
|
+
this.dispatchEvent(readyEvent);
|
61
|
+
if (typeof this.onready === 'function')
|
62
|
+
this.onready(readyEvent);
|
63
|
+
}
|
64
|
+
createResizeObserver() {
|
65
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
66
|
+
for (let entry of entries) {
|
67
|
+
const message = new AppletMessage('resize', {
|
68
|
+
dimensions: {
|
69
|
+
width: entry.contentRect.width,
|
70
|
+
height: entry.contentRect.height,
|
71
|
+
},
|
72
|
+
});
|
73
|
+
this.messageRelay.send(message);
|
74
|
+
}
|
75
|
+
});
|
76
|
+
resizeObserver.observe(document.querySelector('html'));
|
77
|
+
}
|
78
|
+
attachListeners() {
|
79
|
+
this.messageRelay.on('init', (message) => {
|
80
|
+
this.manifest = message.manifest;
|
81
|
+
this.actions = this.manifest?.actions || [];
|
82
|
+
});
|
83
|
+
this.messageRelay.on('data', (message) => {
|
84
|
+
this.setData(message.data);
|
85
|
+
});
|
86
|
+
this.messageRelay.on('action', async (message) => {
|
87
|
+
if (Object.keys(this.actionHandlers).includes(message.actionId)) {
|
88
|
+
await this.actionHandlers[message.actionId](message.params);
|
89
|
+
}
|
90
|
+
});
|
91
|
+
}
|
92
|
+
setActionHandler(actionId, handler) {
|
93
|
+
this.actionHandlers[actionId] = handler;
|
94
|
+
}
|
95
|
+
defineAction(actionId, definition) {
|
96
|
+
const { handler, ...properties } = definition;
|
97
|
+
this.actions = [
|
98
|
+
...this.actions,
|
99
|
+
{
|
100
|
+
id: actionId,
|
101
|
+
...properties,
|
102
|
+
},
|
103
|
+
];
|
104
|
+
this.setActionHandler(actionId, handler);
|
105
|
+
}
|
106
|
+
set actions(actions) {
|
107
|
+
if (!actions)
|
108
|
+
return;
|
109
|
+
for (let action of actions) {
|
110
|
+
__classPrivateFieldGet(this, _AppletContext_actions, "f")[action.id] = action;
|
111
|
+
}
|
112
|
+
this.messageRelay.send(new AppletActionsMessage({ actions: this.actions }));
|
113
|
+
}
|
114
|
+
get actions() {
|
115
|
+
return Object.values(__classPrivateFieldGet(this, _AppletContext_actions, "f"));
|
116
|
+
}
|
117
|
+
set data(data) {
|
118
|
+
this.setData(data);
|
119
|
+
}
|
120
|
+
get data() {
|
121
|
+
return __classPrivateFieldGet(this, _AppletContext_data, "f");
|
122
|
+
}
|
123
|
+
async setData(data) {
|
124
|
+
const dataMessage = new AppletMessage('data', { data });
|
125
|
+
await this.messageRelay.send(dataMessage);
|
126
|
+
__classPrivateFieldSet(this, _AppletContext_data, data, "f");
|
127
|
+
const dataEvent = new AppletDataEvent({ data });
|
128
|
+
this.dispatchEvent(dataEvent);
|
129
|
+
this.ondata(dataEvent);
|
130
|
+
}
|
131
|
+
onload(event) { }
|
132
|
+
onready(event) { }
|
133
|
+
ondata(event) { }
|
134
|
+
}
|
135
|
+
_AppletContext_actions = new WeakMap(), _AppletContext_data = new WeakMap();
|
136
|
+
export function getContext() {
|
137
|
+
return new AppletContext();
|
138
|
+
}
|