@web-applets/sdk 0.0.4
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 +157 -0
- package/dist/client.d.ts +24 -0
- package/dist/client.js +151 -0
- package/dist/context.d.ts +24 -0
- package/dist/context.js +103 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.js +21 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +33 -0
- package/package.json +30 -0
package/README.md
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# Web Applets
|
2
|
+
|
3
|
+
> An open SDK to create interoperable actions & views for agents – _a web of capabilities!_
|
4
|
+
|
5
|
+
## What is it?
|
6
|
+
|
7
|
+
Web Applets is a specification for modular, local web software that can be read and used by both humans and machines. Web Applets aims to be an interoperabe application layer for agents – instead of chatbots that can only interact in plain text, Web Applets allow them to actuate real software, read the results, and render rich, graphical views in response.
|
8
|
+
|
9
|
+
Did we mention it's _interoperable_? We think the future of software should be open & collaborative, not locked down to a single platform.
|
10
|
+
|
11
|
+
## Getting started
|
12
|
+
|
13
|
+
First, clone this repo and run `npm run install`. There are a few sample applets included in `/applets`. To install these applets and start the playground, run `npm run playground`.
|
14
|
+
|
15
|
+
The fastest way to create a new applet is by duplicating one of the applet folders in `/applets`. The folder title will be part of the URL of your applet, so make sure it doesn't include any spaces or other non-URL-safe characters.
|
16
|
+
|
17
|
+
Inside your applet folder, you'll find a basic web app setup:
|
18
|
+
|
19
|
+
- `public/manifest.json`: This file describes the Applet, and tells the model what actions are available and what parameters each action takes
|
20
|
+
- `index.html`: Much like a website, this holds the main page for your applet
|
21
|
+
- `main.js`: Declares functions that respond to each action, and a render function that updates the view based on state
|
22
|
+
|
23
|
+
> 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.
|
24
|
+
|
25
|
+
Let's say we want our applet to respond to a "set_name" action and render the user's name. In our `manifest.json` file we can write:
|
26
|
+
|
27
|
+
```js
|
28
|
+
{
|
29
|
+
// ...
|
30
|
+
"actions": [
|
31
|
+
{
|
32
|
+
"id": "set_name",
|
33
|
+
"description": "Sets the name of the user to be greeted",
|
34
|
+
"params": {
|
35
|
+
"name": {
|
36
|
+
"type": "string",
|
37
|
+
"description": "The name of the user"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
]
|
42
|
+
}
|
43
|
+
```
|
44
|
+
|
45
|
+
Now let's update `main.ts` to assign an action handler:
|
46
|
+
|
47
|
+
```js
|
48
|
+
// First, import the SDK
|
49
|
+
import { appletContext } from '../../sdk/src';
|
50
|
+
|
51
|
+
// Now connect to the applet runtime
|
52
|
+
const applet = appletContext.connect();
|
53
|
+
|
54
|
+
// Attach the action handler, and update the state
|
55
|
+
applet.setActionHandler('set_name', ({ name }) => {
|
56
|
+
applet.setState({ name });
|
57
|
+
});
|
58
|
+
```
|
59
|
+
|
60
|
+
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.
|
61
|
+
|
62
|
+
Finally, we need to render the applet whenever a render signal is received. Again in `main.ts`:
|
63
|
+
|
64
|
+
```js
|
65
|
+
// ...
|
66
|
+
|
67
|
+
applet.onrender = () => {
|
68
|
+
document.body.innerText = `Hello, ${applet.state.name}!`;
|
69
|
+
};
|
70
|
+
```
|
71
|
+
|
72
|
+
Now if you run `npm run playground` from the project root, 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.
|
73
|
+
|
74
|
+

|
75
|
+
|
76
|
+
## Integrating Web Applets into your client
|
77
|
+
|
78
|
+
Integrating Web Applets is just as easy as creating one. First, in your project, make sure you have the sdk installed:
|
79
|
+
|
80
|
+
```bash
|
81
|
+
npm install @unternet/web-applets
|
82
|
+
```
|
83
|
+
|
84
|
+
In your code, you can import the applets client:
|
85
|
+
|
86
|
+
```js
|
87
|
+
import { applets } from '@unternet/web-applets';
|
88
|
+
```
|
89
|
+
|
90
|
+
Now you can create a new applet from a URL:
|
91
|
+
|
92
|
+
```js
|
93
|
+
const applet = await applets.load(
|
94
|
+
`https://unternet.co/applets/helloworld.applet`
|
95
|
+
);
|
96
|
+
applet.onstateupdated = (state) => console.log(state);
|
97
|
+
applet.dispatchAction('set_name', { name: 'Web Applets' });
|
98
|
+
// console.log: { name: "Web Applets" }
|
99
|
+
```
|
100
|
+
|
101
|
+
The above applet is actually running headless, but we can get it to display by attaching it to an iframe. For the loading step, instead run:
|
102
|
+
|
103
|
+
```js
|
104
|
+
const container = document.createElement('iframe');
|
105
|
+
document.body.appendChild(container);
|
106
|
+
const applet = await applets.load(
|
107
|
+
`https://unternet.co/applets/helloworld.applet`,
|
108
|
+
container
|
109
|
+
);
|
110
|
+
```
|
111
|
+
|
112
|
+
To load pre-existing saved state into an applet, simply set the state property:
|
113
|
+
|
114
|
+
```js
|
115
|
+
applet.state = { name: 'Ada Lovelace' };
|
116
|
+
// console.log: { name: "Ada Lovelace" }
|
117
|
+
```
|
118
|
+
|
119
|
+
It may also be helpful to check available applets at a domain, or in your local public folder if you've downloaded a set of Web Applets you want your product to use. 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.
|
120
|
+
|
121
|
+
```js
|
122
|
+
const headers = await applets.getHeaders('/');
|
123
|
+
```
|
124
|
+
|
125
|
+
This headers object looks like:
|
126
|
+
|
127
|
+
```js
|
128
|
+
[
|
129
|
+
{
|
130
|
+
name: 'Hello World',
|
131
|
+
description: 'Displays a greeting to the user.',
|
132
|
+
url: '/applets/helloworld.applet',
|
133
|
+
actions: [
|
134
|
+
{
|
135
|
+
id: 'set_name',
|
136
|
+
description: 'Sets the name of the user to be greeted',
|
137
|
+
params: {
|
138
|
+
name: 'The name of the user',
|
139
|
+
},
|
140
|
+
},
|
141
|
+
],
|
142
|
+
},
|
143
|
+
// ...
|
144
|
+
];
|
145
|
+
```
|
146
|
+
|
147
|
+
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.
|
148
|
+
|
149
|
+
## Feedback & Community
|
150
|
+
|
151
|
+
## License
|
152
|
+
|
153
|
+
[MIT](./LICENSE.md)
|
154
|
+
|
155
|
+
---
|
156
|
+
|
157
|
+
Built by [Unternet](https://unternet.co).
|
package/dist/client.d.ts
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
import { AppletAction, AppletHeader, AppletMessage, ActionParams, AppletManifest } from './types';
|
2
|
+
export declare function getHeaders(url: string): Promise<AppletHeader[]>;
|
3
|
+
export declare function getManifests(url: string): Promise<any[]>;
|
4
|
+
export declare function load(url: string, container: HTMLIFrameElement): Promise<Applet>;
|
5
|
+
export declare class Applet<T = unknown> extends EventTarget {
|
6
|
+
#private;
|
7
|
+
actions: AppletAction[];
|
8
|
+
manifest: AppletManifest;
|
9
|
+
container: HTMLIFrameElement;
|
10
|
+
constructor();
|
11
|
+
get state(): T;
|
12
|
+
set state(state: T);
|
13
|
+
toJson(): {
|
14
|
+
[k: string]: any;
|
15
|
+
};
|
16
|
+
resizeContainer(dimensions: {
|
17
|
+
height: number;
|
18
|
+
width: number;
|
19
|
+
}): void;
|
20
|
+
onstateupdated(event: CustomEvent): void;
|
21
|
+
disconnect(): void;
|
22
|
+
dispatchAction(actionId: string, params: ActionParams): Promise<AppletMessage<any>>;
|
23
|
+
}
|
24
|
+
export declare function loadManifest(url: string): Promise<AppletManifest>;
|
package/dist/client.js
ADDED
@@ -0,0 +1,151 @@
|
|
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_state;
|
13
|
+
import { AppletMessage, } from './types';
|
14
|
+
const hiddenRoot = document.createElement('div');
|
15
|
+
hiddenRoot.style.display = 'none';
|
16
|
+
document.body.appendChild(hiddenRoot);
|
17
|
+
export async function getHeaders(url) {
|
18
|
+
url = parseUrl(url);
|
19
|
+
try {
|
20
|
+
const request = await fetch(`${url}/manifest.json`);
|
21
|
+
const appManifest = await request.json();
|
22
|
+
const appletHeaders = appManifest.applets;
|
23
|
+
return appletHeaders ?? [];
|
24
|
+
}
|
25
|
+
catch {
|
26
|
+
return [];
|
27
|
+
}
|
28
|
+
}
|
29
|
+
export async function getManifests(url) {
|
30
|
+
url = parseUrl(url);
|
31
|
+
const request = await fetch(`${url}/manifest.json`);
|
32
|
+
const headers = (await request.json()).applets;
|
33
|
+
const manifests = await Promise.all(headers.map(async (header) => {
|
34
|
+
const appletUrl = parseUrl(header.url);
|
35
|
+
const request = await fetch(`${appletUrl}/manifest.json`);
|
36
|
+
return await request.json();
|
37
|
+
}));
|
38
|
+
return manifests ?? [];
|
39
|
+
}
|
40
|
+
export async function load(url, container) {
|
41
|
+
url = parseUrl(url);
|
42
|
+
const manifest = await loadManifest(`${url}`);
|
43
|
+
const applet = new Applet();
|
44
|
+
applet.manifest = manifest;
|
45
|
+
applet.actions = manifest.actions; // let the events set this later
|
46
|
+
applet.container = container;
|
47
|
+
container.src = applet.manifest.entrypoint;
|
48
|
+
if (!container.isConnected)
|
49
|
+
hiddenRoot.appendChild(container);
|
50
|
+
return new Promise((resolve) => {
|
51
|
+
window.addEventListener('message', (message) => {
|
52
|
+
if (message.source !== container.contentWindow)
|
53
|
+
return;
|
54
|
+
if (message.data.type === 'ready')
|
55
|
+
resolve(applet);
|
56
|
+
});
|
57
|
+
});
|
58
|
+
}
|
59
|
+
export class Applet extends EventTarget {
|
60
|
+
constructor() {
|
61
|
+
super();
|
62
|
+
this.actions = [];
|
63
|
+
_Applet_state.set(this, void 0);
|
64
|
+
window.addEventListener('message', (message) => {
|
65
|
+
if (message.source !== this.container.contentWindow)
|
66
|
+
return;
|
67
|
+
if (message.data.type === 'state') {
|
68
|
+
__classPrivateFieldSet(this, _Applet_state, message.data.state, "f");
|
69
|
+
this.dispatchEvent(new CustomEvent('stateupdated', { detail: message.data.detail }));
|
70
|
+
this.onstateupdated(message.data.state);
|
71
|
+
}
|
72
|
+
if (message.data.type === 'resize') {
|
73
|
+
this.resizeContainer(message.data.dimensions);
|
74
|
+
}
|
75
|
+
this.container.contentWindow?.postMessage({
|
76
|
+
type: 'resolve',
|
77
|
+
id: message.data.id,
|
78
|
+
}, '*');
|
79
|
+
});
|
80
|
+
}
|
81
|
+
get state() {
|
82
|
+
return __classPrivateFieldGet(this, _Applet_state, "f");
|
83
|
+
}
|
84
|
+
set state(state) {
|
85
|
+
__classPrivateFieldSet(this, _Applet_state, state, "f");
|
86
|
+
const stateMessage = new AppletMessage('state', { state });
|
87
|
+
this.container.contentWindow?.postMessage(stateMessage.toJson(), '*');
|
88
|
+
}
|
89
|
+
toJson() {
|
90
|
+
return Object.fromEntries(Object.entries(this).filter(([_, value]) => {
|
91
|
+
try {
|
92
|
+
JSON.stringify(value);
|
93
|
+
return true;
|
94
|
+
}
|
95
|
+
catch {
|
96
|
+
return false;
|
97
|
+
}
|
98
|
+
}));
|
99
|
+
}
|
100
|
+
resizeContainer(dimensions) {
|
101
|
+
this.container.style.height = `${dimensions.height}px`;
|
102
|
+
// if (!this.#styleOverrides) {
|
103
|
+
// this.#container.style.height = `${dimensions.height}px`;
|
104
|
+
// }
|
105
|
+
}
|
106
|
+
onstateupdated(event) { }
|
107
|
+
disconnect() { }
|
108
|
+
async dispatchAction(actionId, params) {
|
109
|
+
const requestMessage = new AppletMessage('action', {
|
110
|
+
actionId,
|
111
|
+
params,
|
112
|
+
});
|
113
|
+
this.container.contentWindow?.postMessage(requestMessage.toJson(), '*');
|
114
|
+
return new Promise((resolve) => {
|
115
|
+
const listener = (messageEvent) => {
|
116
|
+
if (messageEvent.source !== this.container.contentWindow)
|
117
|
+
return;
|
118
|
+
const responseMessage = new AppletMessage(messageEvent.data.type, messageEvent.data);
|
119
|
+
if (responseMessage.type === 'resolve' &&
|
120
|
+
responseMessage.id === requestMessage.id) {
|
121
|
+
window.removeEventListener('message', listener);
|
122
|
+
resolve(responseMessage);
|
123
|
+
}
|
124
|
+
};
|
125
|
+
window.addEventListener('message', listener);
|
126
|
+
});
|
127
|
+
}
|
128
|
+
}
|
129
|
+
_Applet_state = new WeakMap();
|
130
|
+
function parseUrl(url, base) {
|
131
|
+
if (['http', 'https'].includes(url.split('://')[0])) {
|
132
|
+
return url;
|
133
|
+
}
|
134
|
+
let path = url;
|
135
|
+
if (path.startsWith('/'))
|
136
|
+
path = path.slice(1);
|
137
|
+
if (path.endsWith('/'))
|
138
|
+
path = path.slice(0, -1);
|
139
|
+
url = `${base || window.location.origin}/${path}`;
|
140
|
+
return url;
|
141
|
+
}
|
142
|
+
export async function loadManifest(url) {
|
143
|
+
url = parseUrl(url);
|
144
|
+
const request = await fetch(`${url}/manifest.json`);
|
145
|
+
const appletManifest = await request.json();
|
146
|
+
if (appletManifest.type !== 'applet') {
|
147
|
+
throw new Error("URL doesn't point to a valid applet manifest.");
|
148
|
+
}
|
149
|
+
appletManifest.entrypoint = parseUrl(appletManifest.entrypoint, url);
|
150
|
+
return appletManifest;
|
151
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { ActionHandlerDict, AppletState, AppletMessage, AppletMessageType, AppletMessageCallback, ActionParams, ActionHandler } from './types';
|
2
|
+
/**
|
3
|
+
* Context
|
4
|
+
*/
|
5
|
+
export declare class AppletContext<StateType = AppletState> extends EventTarget {
|
6
|
+
client: AppletClient;
|
7
|
+
actionHandlers: ActionHandlerDict;
|
8
|
+
state: StateType;
|
9
|
+
connect(): this;
|
10
|
+
setActionHandler<T extends ActionParams>(actionId: string, handler: ActionHandler<T>): void;
|
11
|
+
setState(state: StateType): Promise<void>;
|
12
|
+
onload(): Promise<void> | void;
|
13
|
+
onready(): Promise<void> | void;
|
14
|
+
onrender(): void;
|
15
|
+
}
|
16
|
+
/**
|
17
|
+
* Client
|
18
|
+
*/
|
19
|
+
declare class AppletClient {
|
20
|
+
on(messageType: AppletMessageType, callback: AppletMessageCallback): void;
|
21
|
+
send(message: AppletMessage): Promise<void>;
|
22
|
+
}
|
23
|
+
export declare const appletContext: AppletContext<AppletState>;
|
24
|
+
export {};
|
package/dist/context.js
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
import { AppletMessage, } from './types';
|
2
|
+
/**
|
3
|
+
* Context
|
4
|
+
*/
|
5
|
+
export class AppletContext extends EventTarget {
|
6
|
+
constructor() {
|
7
|
+
super(...arguments);
|
8
|
+
this.actionHandlers = {};
|
9
|
+
}
|
10
|
+
connect() {
|
11
|
+
this.client = new AppletClient();
|
12
|
+
const startup = async () => {
|
13
|
+
await this.onload();
|
14
|
+
this.client.send(new AppletMessage('ready'));
|
15
|
+
this.dispatchEvent(new CustomEvent('ready'));
|
16
|
+
await this.onready();
|
17
|
+
};
|
18
|
+
if (document.readyState === 'complete' ||
|
19
|
+
document.readyState === 'interactive') {
|
20
|
+
setTimeout(startup, 1);
|
21
|
+
}
|
22
|
+
else {
|
23
|
+
window.addEventListener('DOMContentLoaded', startup);
|
24
|
+
}
|
25
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
26
|
+
for (let entry of entries) {
|
27
|
+
const message = new AppletMessage('resize', {
|
28
|
+
dimensions: {
|
29
|
+
width: entry.contentRect.width,
|
30
|
+
height: entry.contentRect.height,
|
31
|
+
},
|
32
|
+
});
|
33
|
+
this.client.send(message);
|
34
|
+
}
|
35
|
+
});
|
36
|
+
resizeObserver.observe(document.querySelector('html'));
|
37
|
+
this.client.on('state', (message) => {
|
38
|
+
if (!isStateMessage(message)) {
|
39
|
+
throw new TypeError("Message doesn't match type StateMessage");
|
40
|
+
}
|
41
|
+
this.setState(message.state);
|
42
|
+
});
|
43
|
+
this.client.on('action', async (message) => {
|
44
|
+
if (!isActionMessage(message)) {
|
45
|
+
throw new TypeError("Message doesn't match type AppletMessage.");
|
46
|
+
}
|
47
|
+
if (Object.keys(this.actionHandlers).includes(message.actionId)) {
|
48
|
+
await this.actionHandlers[message.actionId](message.params);
|
49
|
+
}
|
50
|
+
message.resolve();
|
51
|
+
});
|
52
|
+
return this;
|
53
|
+
}
|
54
|
+
setActionHandler(actionId, handler) {
|
55
|
+
this.actionHandlers[actionId] = handler;
|
56
|
+
}
|
57
|
+
async setState(state) {
|
58
|
+
const message = new AppletMessage('state', { state });
|
59
|
+
await this.client.send(message);
|
60
|
+
this.state = state;
|
61
|
+
this.dispatchEvent(new CustomEvent('render'));
|
62
|
+
this.onrender(); // TODO: Should come from client? Or stay here, and only activate if mounted? Need a control for mounting.
|
63
|
+
}
|
64
|
+
onload() { }
|
65
|
+
onready() { }
|
66
|
+
onrender() { }
|
67
|
+
}
|
68
|
+
function isActionMessage(message) {
|
69
|
+
return message.type === 'action';
|
70
|
+
}
|
71
|
+
function isStateMessage(message) {
|
72
|
+
return message.type === 'state';
|
73
|
+
}
|
74
|
+
/**
|
75
|
+
* Client
|
76
|
+
*/
|
77
|
+
class AppletClient {
|
78
|
+
on(messageType, callback) {
|
79
|
+
window.addEventListener('message', (messageEvent) => {
|
80
|
+
if (messageEvent.data.type !== messageType)
|
81
|
+
return;
|
82
|
+
const message = new AppletMessage(messageEvent.data.type, messageEvent.data);
|
83
|
+
message.resolve = () => {
|
84
|
+
window.parent.postMessage(new AppletMessage('resolve', { id: message.id }), '*');
|
85
|
+
};
|
86
|
+
callback(message);
|
87
|
+
});
|
88
|
+
}
|
89
|
+
send(message) {
|
90
|
+
window.parent.postMessage(message.toJson(), '*');
|
91
|
+
return new Promise((resolve) => {
|
92
|
+
const listener = (messageEvent) => {
|
93
|
+
if (messageEvent.data.type === 'resolve' &&
|
94
|
+
messageEvent.data.id === message.id) {
|
95
|
+
window.removeEventListener('message', listener);
|
96
|
+
resolve();
|
97
|
+
}
|
98
|
+
};
|
99
|
+
window.addEventListener('message', listener);
|
100
|
+
});
|
101
|
+
}
|
102
|
+
}
|
103
|
+
export const appletContext = new AppletContext();
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
export interface AppletManifest {
|
2
|
+
type: 'applet';
|
3
|
+
name: string;
|
4
|
+
description: string;
|
5
|
+
icon?: string;
|
6
|
+
entrypoint: string;
|
7
|
+
actions: AppletAction[];
|
8
|
+
}
|
9
|
+
export interface AppletAction {
|
10
|
+
id: string;
|
11
|
+
description: string;
|
12
|
+
params?: ActionParamSchema;
|
13
|
+
}
|
14
|
+
export interface AppletHeader {
|
15
|
+
name: string;
|
16
|
+
description: string;
|
17
|
+
url: string;
|
18
|
+
params: {
|
19
|
+
[key: string]: string;
|
20
|
+
};
|
21
|
+
}
|
22
|
+
export type AppletState = Record<string, Serializable>;
|
23
|
+
export type ActionParamSchema = Record<string, {
|
24
|
+
description: string;
|
25
|
+
type: 'string';
|
26
|
+
}>;
|
27
|
+
export type ActionParams = Record<string, unknown>;
|
28
|
+
export type ActionHandlerDict = {
|
29
|
+
[key: string]: ActionHandler<any>;
|
30
|
+
};
|
31
|
+
export type ActionHandler<T extends ActionParams> = (params: T) => void | Promise<void>;
|
32
|
+
export type AnyAppletMessage = AppletMessage | AppletStateMessage | AppletActionMessage;
|
33
|
+
export interface AppletStateMessage<T = any> extends AppletMessage {
|
34
|
+
type: 'state';
|
35
|
+
state: T;
|
36
|
+
}
|
37
|
+
export interface AppletActionMessage<T = any> extends AppletMessage {
|
38
|
+
type: 'action';
|
39
|
+
actionId: string;
|
40
|
+
params: T;
|
41
|
+
}
|
42
|
+
export declare class AppletMessage<T = any> {
|
43
|
+
type: AppletMessageType;
|
44
|
+
id: string;
|
45
|
+
timeStamp: number;
|
46
|
+
constructor(type: AppletMessageType, values?: T);
|
47
|
+
toJson(): {
|
48
|
+
[k: string]: any;
|
49
|
+
};
|
50
|
+
resolve(): void;
|
51
|
+
}
|
52
|
+
export type AppletMessageType = 'action' | 'render' | 'state' | 'ready' | 'resolve' | 'resize';
|
53
|
+
export type AppletMessageCallback = (message: AnyAppletMessage) => void;
|
54
|
+
type Serializable = string | number | boolean | null | Serializable[] | {
|
55
|
+
[key: string]: Serializable;
|
56
|
+
};
|
57
|
+
export {};
|
package/dist/types.js
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
export class AppletMessage {
|
2
|
+
constructor(type, values) {
|
3
|
+
this.timeStamp = Date.now();
|
4
|
+
this.type = type;
|
5
|
+
this.id = crypto.randomUUID();
|
6
|
+
if (values)
|
7
|
+
Object.assign(this, values);
|
8
|
+
}
|
9
|
+
toJson() {
|
10
|
+
return Object.fromEntries(Object.entries(this).filter(([_, value]) => {
|
11
|
+
try {
|
12
|
+
JSON.stringify(value);
|
13
|
+
return true;
|
14
|
+
}
|
15
|
+
catch {
|
16
|
+
return false;
|
17
|
+
}
|
18
|
+
}));
|
19
|
+
}
|
20
|
+
resolve() { }
|
21
|
+
}
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
function parseUrl(url, base) {
|
2
|
+
if (['http', 'https'].includes(url.split('://')[0])) {
|
3
|
+
return url;
|
4
|
+
}
|
5
|
+
let path = url;
|
6
|
+
if (path.startsWith('/'))
|
7
|
+
path = path.slice(1);
|
8
|
+
if (path.endsWith('/'))
|
9
|
+
path = path.slice(0, -1);
|
10
|
+
url = `${base || window.location.origin}/${path}`;
|
11
|
+
return url;
|
12
|
+
}
|
13
|
+
export async function getAppletsList(url) {
|
14
|
+
url = parseUrl(url);
|
15
|
+
try {
|
16
|
+
const request = await fetch(`${url}/manifest.json`);
|
17
|
+
const appManifest = await request.json();
|
18
|
+
return appManifest.applets ? appManifest.applets : [];
|
19
|
+
}
|
20
|
+
catch {
|
21
|
+
return [];
|
22
|
+
}
|
23
|
+
}
|
24
|
+
export async function loadAppletManifest(url) {
|
25
|
+
url = parseUrl(url);
|
26
|
+
const request = await fetch(`${url}/manifest.json`);
|
27
|
+
const appletManifest = await request.json();
|
28
|
+
if (appletManifest.type !== 'applet') {
|
29
|
+
throw new Error("URL doesn't point to a valid applet manifest.");
|
30
|
+
}
|
31
|
+
appletManifest.entrypoint = parseUrl(appletManifest.entrypoint, url);
|
32
|
+
return appletManifest;
|
33
|
+
}
|
package/package.json
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
{
|
2
|
+
"name": "@web-applets/sdk",
|
3
|
+
"version": "0.0.4",
|
4
|
+
"description": "The Web Applets SDK, for creating & hosting Web Applets.",
|
5
|
+
"author": "Rupert Manfredi <rupert@unternet.co>",
|
6
|
+
"license": "MIT",
|
7
|
+
"type": "module",
|
8
|
+
"main": "dist/index.js",
|
9
|
+
"types": "dist/index.d.ts",
|
10
|
+
"files": [
|
11
|
+
"dist"
|
12
|
+
],
|
13
|
+
"publishConfig": {
|
14
|
+
"access": "public"
|
15
|
+
},
|
16
|
+
"repository": {
|
17
|
+
"type": "git",
|
18
|
+
"url": "git+https://github.com/unternet-co/web-applets.git"
|
19
|
+
},
|
20
|
+
"scripts": {
|
21
|
+
"build": "tsc && cp ../README.md ./README.md",
|
22
|
+
"prepublishOnly": "npm run build"
|
23
|
+
},
|
24
|
+
"devDependencies": {
|
25
|
+
"typescript": "^5.6.2"
|
26
|
+
},
|
27
|
+
"dependencies": {
|
28
|
+
"vite": "^5.4.7"
|
29
|
+
}
|
30
|
+
}
|