@web-applets/sdk 0.0.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|

|
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
|
+
}
|