@web-applets/sdk 0.0.8 → 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 +69 -99
- 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 -190
- package/dist/context.d.ts +0 -27
- package/dist/context.js +0 -136
- package/dist/types.d.ts +0 -73
- 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
@@ -1,93 +1,97 @@
|
|
1
1
|
# Web Applets
|
2
2
|
|
3
|
-
> An open
|
3
|
+
> An open spec & SDK for creating apps that agents can use.
|
4
4
|
|
5
|
-
🔗 [
|
5
|
+
🔗 [Applets Repo](https://github.com/unternet-co/community-applets) | 🔗 [Mailing List](https://groups.google.com/a/unternet.co/g/community) | 🔗 [Applets Chat Demo](https://github.com/unternet-co/applets-chat)
|
6
6
|
|
7
7
|
## What is it?
|
8
8
|
|
9
|
-
Web Applets is
|
9
|
+
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 software designed for human-AI collaboration. Think of them a bit like artifacts, but they do stuff!
|
10
10
|
|
11
|
-
|
11
|
+

|
12
12
|
|
13
|
-
|
13
|
+
Web Applets are modular pieces of web software that:
|
14
14
|
|
15
|
-
|
15
|
+
- **Can be used directly by humans with rich, graphical interfaces**
|
16
|
+
- **Can be understood and operated by AI through a clear protocol**
|
17
|
+
- **Run locally in your environment, not on distant servers**
|
18
|
+
- **Share context and state with their environment**
|
19
|
+
- **Can be freely combined and composed**
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
Install the applets SDK & CLI:
|
20
|
-
|
21
|
-
```bash
|
22
|
-
npm i --save @web-applets/sdk
|
23
|
-
npm i --save-dev @web-applets/cli
|
24
|
-
```
|
21
|
+
Think of any web software you use today - maps, documents, shopping, calendars - and imagine if instead of visiting these as separate websites, you could pull them into your own environment where both you and AI could use them together seamlessly.
|
25
22
|
|
26
|
-
|
23
|
+
## Key Features
|
27
24
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
25
|
+
- **Built on Web Standards:** Create applets using familiar web technologies (HTML, CSS, JavaScript)
|
26
|
+
- **AI-Native Protocol:** Applets expose their state and actions in a way AI can understand and use
|
27
|
+
- **Rich Interfaces:** Full support for complex graphical UIs, not just text
|
28
|
+
- **Local-First:** Runs in your environment, keeping your data under your control
|
29
|
+
- **Composable:** Applets can work together, sharing context and state
|
30
|
+
- **Open Standard:** Designed for interoperability, not platform lock-in
|
34
31
|
|
35
|
-
|
32
|
+
Web Applets aims to do for AI-enabled software what the web did for documents - create an open platform where anyone can build, share, and connect applications. We believe the future of software should be built on open collaboration, not tight integration with closed platforms.
|
36
33
|
|
37
|
-
|
38
|
-
- `index.html`: Much like a website, this holds the main page for your applet
|
39
|
-
- `src/main.ts`: Declares functions that respond to each action, and a render function that updates the view based on state
|
34
|
+
## Example
|
40
35
|
|
41
|
-
|
36
|
+
Let's say we have a simple website that says hello. It might look something like this:
|
42
37
|
|
43
|
-
|
38
|
+
`index.html`:
|
44
39
|
|
45
|
-
```
|
46
|
-
|
47
|
-
|
48
|
-
"
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
"name": {
|
54
|
-
"type": "string",
|
55
|
-
"description": "The name of the user"
|
56
|
-
}
|
57
|
-
}
|
58
|
-
}
|
59
|
-
]
|
60
|
-
}
|
40
|
+
```html
|
41
|
+
<!DOCTYPE html>
|
42
|
+
<html lang="en">
|
43
|
+
<script src="./main.js" type="module"></script>
|
44
|
+
<body>
|
45
|
+
Hello! <span id="name">whoever you are</span>.
|
46
|
+
</body>
|
47
|
+
</html>
|
61
48
|
```
|
62
49
|
|
63
|
-
|
50
|
+
Let's add some Web Applets functionality, so this can respond to a `set_name` message:
|
51
|
+
|
52
|
+
`main.js`:
|
64
53
|
|
65
54
|
```js
|
66
|
-
|
67
|
-
import { appletContext } from '../../sdk/src';
|
55
|
+
import { applets } from '@web-applets/sdk';
|
68
56
|
|
69
|
-
|
70
|
-
const applet = appletContext.connect();
|
57
|
+
const context = applets.getContext();
|
71
58
|
|
72
|
-
//
|
73
|
-
|
74
|
-
|
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 };
|
75
68
|
});
|
69
|
+
|
70
|
+
// Whenever the data is updated, update the view
|
71
|
+
context.ondata = () => {
|
72
|
+
document.getElementById('name').innerText = applet.data.name;
|
73
|
+
};
|
76
74
|
```
|
77
75
|
|
78
|
-
|
76
|
+
Done! If you load this up in the inspector and introduce yourself, it will respond by greeting you.
|
79
77
|
|
80
|
-
|
78
|
+
## Getting started
|
81
79
|
|
82
|
-
|
83
|
-
// ...
|
80
|
+
Create a new web app with the applets SDK installed. You can do this quickly using our CLI:
|
84
81
|
|
85
|
-
|
86
|
-
|
87
|
-
};
|
82
|
+
```bash
|
83
|
+
npx @web-applets/create
|
88
84
|
```
|
89
85
|
|
90
|
-
|
86
|
+
Inside the generated folder, you'll find a basic web app setup:
|
87
|
+
|
88
|
+
- `public/manifest.json`: A web app manifest, useful when publishing your applet, adding icons, etc.
|
89
|
+
- `index.html`: Much like a website, this holds the main page for your applet
|
90
|
+
- `src/main.ts`: Declares functions that respond to each action, and a render function that updates the view based on state
|
91
|
+
|
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.
|
93
|
+
|
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.
|
91
95
|
|
92
96
|

|
93
97
|
|
@@ -95,16 +99,12 @@ Now if you run `npx applets playground`, you should be able to test out your new
|
|
95
99
|
|
96
100
|
Using Web Applets is just as easy as creating them!
|
97
101
|
|
98
|
-
|
99
|
-
|
100
|
-
Then, run:
|
102
|
+
Install & import the applets client in your app:
|
101
103
|
|
102
104
|
```bash
|
103
|
-
|
105
|
+
npm install @web-applets/sdk
|
104
106
|
```
|
105
107
|
|
106
|
-
Now in your main app, you can import the applets client:
|
107
|
-
|
108
108
|
```js
|
109
109
|
import { applets } from '@web-applets/sdk';
|
110
110
|
```
|
@@ -112,8 +112,8 @@ import { applets } from '@web-applets/sdk';
|
|
112
112
|
Now you can import your applets from wherever they're being served from (note – you can also host them anywhere on the web):
|
113
113
|
|
114
114
|
```js
|
115
|
-
const applet = await applets.load('/helloworld.applet'); // replace with
|
116
|
-
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);
|
117
117
|
applet.dispatchAction('set_name', { name: 'Web Applets' });
|
118
118
|
```
|
119
119
|
|
@@ -125,43 +125,13 @@ document.body.appendChild(container);
|
|
125
125
|
const applet = await applets.load(`/helloworld.applet`, container);
|
126
126
|
```
|
127
127
|
|
128
|
-
To load pre-existing saved
|
128
|
+
To load pre-existing saved data into an applet, simply set the data property:
|
129
129
|
|
130
130
|
```js
|
131
|
-
applet.
|
131
|
+
applet.data = { name: 'Ada Lovelace' };
|
132
132
|
// console.log: { name: "Ada Lovelace" }
|
133
133
|
```
|
134
134
|
|
135
|
-
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.
|
136
|
-
|
137
|
-
```js
|
138
|
-
const headers = await applets.getHeaders('/');
|
139
|
-
```
|
140
|
-
|
141
|
-
This headers object looks like:
|
142
|
-
|
143
|
-
```js
|
144
|
-
[
|
145
|
-
{
|
146
|
-
name: 'Hello World',
|
147
|
-
description: 'Displays a greeting to the user.',
|
148
|
-
url: '/applets/helloworld.applet',
|
149
|
-
actions: [
|
150
|
-
{
|
151
|
-
id: 'set_name',
|
152
|
-
description: 'Sets the name of the user to be greeted',
|
153
|
-
params: {
|
154
|
-
name: 'The name of the user',
|
155
|
-
},
|
156
|
-
},
|
157
|
-
],
|
158
|
-
},
|
159
|
-
// ...
|
160
|
-
];
|
161
|
-
```
|
162
|
-
|
163
|
-
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.
|
164
|
-
|
165
135
|
## Feedback & Community
|
166
136
|
|
167
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
|
+
}
|