@web-applets/sdk 0.0.6 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -2,23 +2,41 @@
2
2
 
3
3
  > An open SDK to create interoperable actions & views for agents – _a web of capabilities!_
4
4
 
5
+ 🔗 [Community Applets Repo](https://github.com/unternet-co/community-applets) | 🔗 [Community Mailing List](https://groups.google.com/a/unternet.co/g/community)
6
+
5
7
  ## What is it?
6
8
 
7
9
  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
10
 
11
+ In short, your model can use & respond with artibrary bits of web UI that a human can then interact with – a map, a rich text document, an item on Amazon with a purchase button, whatever you can build with the web you can turn it into an applet that an agent can use and display.
12
+
9
13
  Did we mention it's _interoperable_? We think the future of software should be open & collaborative, not locked down to a single platform.
10
14
 
15
+ For more, see [why](./docs/why.md).
16
+
11
17
  ## Getting started
12
18
 
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`.
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
+ ```
25
+
26
+ Then, initialize the `applets.config.json` and create a new blank applet:
14
27
 
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.
28
+ ```bash
29
+ npx applets init
30
+ npx applets create <your-applet-name>
31
+ ```
32
+
33
+ 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.
16
34
 
17
35
  Inside your applet folder, you'll find a basic web app setup:
18
36
 
19
37
  - `public/manifest.json`: This file describes the Applet, and tells the model what actions are available and what parameters each action takes
20
38
  - `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
39
+ - `src/main.ts`: Declares functions that respond to each action, and a render function that updates the view based on state
22
40
 
23
41
  > 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
42
 
@@ -42,7 +60,7 @@ Let's say we want our applet to respond to a "set_name" action and render the us
42
60
  }
43
61
  ```
44
62
 
45
- Now let's update `main.ts` to assign an action handler:
63
+ Now let's update `src/main.ts` to assign an action handler:
46
64
 
47
65
  ```js
48
66
  // First, import the SDK
@@ -69,44 +87,42 @@ applet.onrender = () => {
69
87
  };
70
88
  ```
71
89
 
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.
90
+ 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.
73
91
 
74
92
  ![A screenshot showing the 'playground' editing UI, with a web applets showing 'Hello, Web Applets'](docs/assets/web-applets-playground.png)
75
93
 
76
94
  ## Integrating Web Applets into your client
77
95
 
78
- Integrating Web Applets is just as easy as creating one. First, in your project, make sure you have the sdk installed:
96
+ Using Web Applets is just as easy as creating them!
97
+
98
+ First, build your applets. By default, this goes into a folder called `dist/`, but you'll likely want to change this in `applets.config.json` to point to wherever you're serving public files from. For example, in a Vite project, edit this to be `./public`.
99
+
100
+ Then, run:
79
101
 
80
102
  ```bash
81
- npm install @unternet/web-applets
103
+ npx applets build
82
104
  ```
83
105
 
84
- In your code, you can import the applets client:
106
+ Now in your main app, you can import the applets client:
85
107
 
86
108
  ```js
87
- import { applets } from '@unternet/web-applets';
109
+ import { applets } from '@web-applets/sdk';
88
110
  ```
89
111
 
90
- Now you can create a new applet from a URL:
112
+ Now you can import your applets from wherever they're being served from (note – you can also host them anywhere on the web):
91
113
 
92
114
  ```js
93
- const applet = await applets.load(
94
- `https://unternet.co/applets/helloworld.applet`
95
- );
115
+ const applet = await applets.load('/helloworld.applet'); // replace with a URL if hosted remotely
96
116
  applet.onstateupdated = (state) => console.log(state);
97
117
  applet.dispatchAction('set_name', { name: 'Web Applets' });
98
- // console.log: { name: "Web Applets" }
99
118
  ```
100
119
 
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:
120
+ The above applet is actually running headless, but we can get it to display by attaching it to a container. For the loading step, instead run:
102
121
 
103
122
  ```js
104
123
  const container = document.createElement('iframe');
105
124
  document.body.appendChild(container);
106
- const applet = await applets.load(
107
- `https://unternet.co/applets/helloworld.applet`,
108
- container
109
- );
125
+ const applet = await applets.load(`/helloworld.applet`, container);
110
126
  ```
111
127
 
112
128
  To load pre-existing saved state into an applet, simply set the state property:
@@ -116,7 +132,7 @@ applet.state = { name: 'Ada Lovelace' };
116
132
  // console.log: { name: "Ada Lovelace" }
117
133
  ```
118
134
 
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.
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.
120
136
 
121
137
  ```js
122
138
  const headers = await applets.getHeaders('/');
@@ -148,6 +164,10 @@ You can use it to present a quick summary of available tools to your model, and
148
164
 
149
165
  ## Feedback & Community
150
166
 
167
+ This is a community project, and we're open to community members discussing the project direction, and submitting code!
168
+
169
+ To join the conversation, visit the Applets mailing list at [groups.google.com/a/unternet.co/g/community](https://groups.google.com/a/unternet.co/g/community). You can also find more about the company that's kicking off this work at [unternet.co](https://unternet.co)
170
+
151
171
  ## License
152
172
 
153
173
  [MIT](./LICENSE.md)
package/dist/client.d.ts CHANGED
@@ -1,7 +1,10 @@
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>;
1
+ import { AppletAction, AppletHeader, AppletMessage, ActionParams, AppletManifest, AppletMessageType, AppletMessageCallback, AppletManifestDict } from './types';
2
+ export declare function list(url: string): Promise<AppletManifestDict>;
3
+ interface AppletOpts {
4
+ headless?: boolean;
5
+ unsafe?: boolean;
6
+ }
7
+ export declare function load(url: string, container?: HTMLIFrameElement, opts?: AppletOpts): Promise<Applet>;
5
8
  export declare class Applet<T = unknown> extends EventTarget {
6
9
  #private;
7
10
  actions: AppletAction[];
@@ -20,5 +23,10 @@ export declare class Applet<T = unknown> extends EventTarget {
20
23
  onstateupdated(event: CustomEvent): void;
21
24
  disconnect(): void;
22
25
  dispatchAction(actionId: string, params: ActionParams): Promise<AppletMessage<any>>;
26
+ send(message: AppletMessage): Promise<AppletMessage<any>>;
27
+ on(messageType: AppletMessageType, callback: AppletMessageCallback): Promise<void>;
23
28
  }
24
29
  export declare function loadManifest(url: string): Promise<AppletManifest>;
30
+ export declare function getHeaders(url: string): Promise<AppletHeader[]>;
31
+ export declare function getManifests(url: string): Promise<any[]>;
32
+ export {};
package/dist/client.js CHANGED
@@ -9,73 +9,72 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
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
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _Applet_state;
12
+ var _Applet_instances, _Applet_state, _Applet_dispatchEvent;
13
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) {
14
+ const hiddenContainer = document.createElement('iframe');
15
+ hiddenContainer.style.display = 'none';
16
+ document.body.appendChild(hiddenContainer);
17
+ export async function list(url) {
18
18
  url = parseUrl(url);
19
19
  try {
20
20
  const request = await fetch(`${url}/manifest.json`);
21
21
  const appManifest = await request.json();
22
- const appletHeaders = appManifest.applets;
23
- return appletHeaders ?? [];
22
+ const appletUrls = appManifest.applets;
23
+ const manifests = {};
24
+ const manifestRequests = appletUrls.map(async (appletUrl) => {
25
+ appletUrl = parseUrl(appletUrl, url);
26
+ const request = await fetch(`${appletUrl}/manifest.json`);
27
+ const manifest = await request.json();
28
+ manifests[appletUrl] = manifest;
29
+ });
30
+ await Promise.all(manifestRequests);
31
+ return manifests;
24
32
  }
25
33
  catch {
26
- return [];
34
+ return {};
27
35
  }
28
36
  }
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) {
37
+ const defaultOpts = {
38
+ headless: false,
39
+ unsafe: false,
40
+ };
41
+ export async function load(url, container, opts) {
42
+ const _opts = Object.assign(defaultOpts, opts ?? {});
43
+ if (!container) {
44
+ container = hiddenContainer;
45
+ _opts.headless = true;
46
+ }
47
+ if (!_opts.unsafe)
48
+ container.setAttribute('sandbox', 'allow-scripts allow-forms');
41
49
  url = parseUrl(url);
42
50
  const manifest = await loadManifest(`${url}`);
43
51
  const applet = new Applet();
44
52
  applet.manifest = manifest;
45
- applet.actions = manifest.actions; // let the events set this later
53
+ applet.actions = manifest.actions;
46
54
  applet.container = container;
47
55
  container.src = applet.manifest.entrypoint;
48
- if (!container.isConnected)
49
- hiddenRoot.appendChild(container);
50
56
  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);
57
+ applet.on('ready', () => {
58
+ const initMessage = new AppletMessage('init', {
59
+ headless: _opts.headless,
60
+ });
61
+ applet.send(initMessage);
62
+ resolve(applet);
56
63
  });
57
64
  });
58
65
  }
59
66
  export class Applet extends EventTarget {
60
67
  constructor() {
61
68
  super();
69
+ _Applet_instances.add(this);
62
70
  this.actions = [];
63
71
  _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
- }, '*');
72
+ this.on('state', (message) => {
73
+ __classPrivateFieldSet(this, _Applet_state, message.state, "f");
74
+ __classPrivateFieldGet(this, _Applet_instances, "m", _Applet_dispatchEvent).call(this, 'stateupdated', message.state);
75
+ });
76
+ this.on('resize', (message) => {
77
+ this.resizeContainer(message.dimensions);
79
78
  });
80
79
  }
81
80
  get state() {
@@ -83,8 +82,7 @@ export class Applet extends EventTarget {
83
82
  }
84
83
  set state(state) {
85
84
  __classPrivateFieldSet(this, _Applet_state, state, "f");
86
- const stateMessage = new AppletMessage('state', { state });
87
- this.container.contentWindow?.postMessage(stateMessage.toJson(), '*');
85
+ this.send(new AppletMessage('state', { state }));
88
86
  }
89
87
  toJson() {
90
88
  return Object.fromEntries(Object.entries(this).filter(([_, value]) => {
@@ -98,7 +96,7 @@ export class Applet extends EventTarget {
98
96
  }));
99
97
  }
100
98
  resizeContainer(dimensions) {
101
- this.container.style.height = `${dimensions.height}px`;
99
+ this.container.style.height = `${dimensions.height + 2}px`;
102
100
  // if (!this.#styleOverrides) {
103
101
  // this.#container.style.height = `${dimensions.height}px`;
104
102
  // }
@@ -110,14 +108,15 @@ export class Applet extends EventTarget {
110
108
  actionId,
111
109
  params,
112
110
  });
113
- this.container.contentWindow?.postMessage(requestMessage.toJson(), '*');
111
+ return await this.send(requestMessage);
112
+ }
113
+ async send(message) {
114
+ this.container.contentWindow?.postMessage(message.toJson(), '*');
114
115
  return new Promise((resolve) => {
115
116
  const listener = (messageEvent) => {
116
- if (messageEvent.source !== this.container.contentWindow)
117
- return;
118
117
  const responseMessage = new AppletMessage(messageEvent.data.type, messageEvent.data);
119
118
  if (responseMessage.type === 'resolve' &&
120
- responseMessage.id === requestMessage.id) {
119
+ responseMessage.id === message.id) {
121
120
  window.removeEventListener('message', listener);
122
121
  resolve(responseMessage);
123
122
  }
@@ -125,20 +124,37 @@ export class Applet extends EventTarget {
125
124
  window.addEventListener('message', listener);
126
125
  });
127
126
  }
127
+ async on(messageType, callback) {
128
+ const listener = async (messageEvent) => {
129
+ if (messageEvent.source !== this.container.contentWindow)
130
+ return;
131
+ if (messageEvent.data.type !== messageType)
132
+ return;
133
+ const message = new AppletMessage(messageEvent.data.type, messageEvent.data);
134
+ await callback(message);
135
+ this.container.contentWindow?.postMessage(new AppletMessage('resolve', { id: message.id }), '*');
136
+ };
137
+ window.addEventListener('message', listener);
138
+ }
128
139
  }
129
- _Applet_state = new WeakMap();
140
+ _Applet_state = new WeakMap(), _Applet_instances = new WeakSet(), _Applet_dispatchEvent = function _Applet_dispatchEvent(id, detail) {
141
+ if (typeof this[`on${id}`] === 'function') {
142
+ this[`on${id}`](detail);
143
+ }
144
+ this.dispatchEvent(new CustomEvent(id, { detail }));
145
+ };
146
+ /* Helpers */
130
147
  function parseUrl(url, base) {
131
148
  if (['http', 'https'].includes(url.split('://')[0])) {
132
149
  return url;
133
150
  }
134
- let path = url;
135
- if (path.startsWith('/'))
136
- path = path.slice(1);
137
- if (path.endsWith('/'))
138
- path = path.slice(0, -1);
151
+ let path = trimSlashes(url);
139
152
  url = `${base || window.location.origin}/${path}`;
140
153
  return url;
141
154
  }
155
+ function trimSlashes(str) {
156
+ return str.replace(/^\/+|\/+$/g, '');
157
+ }
142
158
  export async function loadManifest(url) {
143
159
  url = parseUrl(url);
144
160
  const request = await fetch(`${url}/manifest.json`);
@@ -149,3 +165,26 @@ export async function loadManifest(url) {
149
165
  appletManifest.entrypoint = parseUrl(appletManifest.entrypoint, url);
150
166
  return appletManifest;
151
167
  }
168
+ export async function getHeaders(url) {
169
+ url = parseUrl(url);
170
+ try {
171
+ const request = await fetch(`${url}/manifest.json`);
172
+ const appManifest = await request.json();
173
+ const appletHeaders = appManifest.applets;
174
+ return appletHeaders ?? [];
175
+ }
176
+ catch {
177
+ return [];
178
+ }
179
+ }
180
+ export async function getManifests(url) {
181
+ url = parseUrl(url);
182
+ const request = await fetch(`${url}/manifest.json`);
183
+ const headers = (await request.json()).applets;
184
+ const manifests = await Promise.all(headers.map(async (header) => {
185
+ const appletUrl = parseUrl(header.url);
186
+ const request = await fetch(`${appletUrl}/manifest.json`);
187
+ return await request.json();
188
+ }));
189
+ return manifests ?? [];
190
+ }
package/dist/context.d.ts CHANGED
@@ -1,14 +1,17 @@
1
- import { ActionHandlerDict, AppletState, AppletMessage, AppletMessageType, AppletMessageCallback, ActionParams, ActionHandler } from './types';
1
+ import { ActionHandlerDict, AppletMessage, AppletMessageType, AppletMessageCallback, ActionParams, ActionHandler } from './types';
2
2
  /**
3
3
  * Context
4
4
  */
5
- export declare class AppletContext<StateType = AppletState> extends EventTarget {
5
+ export declare class AppletContext<StateType = any> extends EventTarget {
6
+ #private;
6
7
  client: AppletClient;
7
8
  actionHandlers: ActionHandlerDict;
8
- state: StateType;
9
+ headless: boolean;
9
10
  connect(): this;
10
11
  setActionHandler<T extends ActionParams>(actionId: string, handler: ActionHandler<T>): void;
11
- setState(state: StateType): Promise<void>;
12
+ set state(state: StateType);
13
+ get state(): StateType;
14
+ setState(state: StateType, shouldRender?: boolean): Promise<void>;
12
15
  onload(): Promise<void> | void;
13
16
  onready(): Promise<void> | void;
14
17
  onrender(): void;
@@ -20,5 +23,5 @@ declare class AppletClient {
20
23
  on(messageType: AppletMessageType, callback: AppletMessageCallback): void;
21
24
  send(message: AppletMessage): Promise<void>;
22
25
  }
23
- export declare const appletContext: AppletContext<AppletState>;
26
+ export declare const appletContext: AppletContext<any>;
24
27
  export {};
package/dist/context.js CHANGED
@@ -1,3 +1,15 @@
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_state;
1
13
  import { AppletMessage, } from './types';
2
14
  /**
3
15
  * Context
@@ -6,6 +18,8 @@ export class AppletContext extends EventTarget {
6
18
  constructor() {
7
19
  super(...arguments);
8
20
  this.actionHandlers = {};
21
+ _AppletContext_state.set(this, void 0);
22
+ this.headless = false;
9
23
  }
10
24
  connect() {
11
25
  this.client = new AppletClient();
@@ -34,11 +48,24 @@ export class AppletContext extends EventTarget {
34
48
  }
35
49
  });
36
50
  resizeObserver.observe(document.querySelector('html'));
51
+ this.client.on('init', (message) => {
52
+ const initMessage = message;
53
+ this.headless = initMessage.headless;
54
+ });
37
55
  this.client.on('state', (message) => {
38
56
  if (!isStateMessage(message)) {
39
57
  throw new TypeError("Message doesn't match type StateMessage");
40
58
  }
41
- this.setState(message.state);
59
+ // Don't render when state updates match the current state
60
+ // this retains cursor positions in text fields, for example
61
+ if (JSON.stringify(message.state) === JSON.stringify(__classPrivateFieldGet(this, _AppletContext_state, "f")))
62
+ return;
63
+ __classPrivateFieldSet(this, _AppletContext_state, message.state, "f");
64
+ // BUG: For some reason regular applets were loading headless, when instantiated not on a page reload
65
+ // if (!this.headless) {
66
+ this.onrender();
67
+ this.dispatchEvent(new CustomEvent('render'));
68
+ // }
42
69
  });
43
70
  this.client.on('action', async (message) => {
44
71
  if (!isActionMessage(message)) {
@@ -47,24 +74,32 @@ export class AppletContext extends EventTarget {
47
74
  if (Object.keys(this.actionHandlers).includes(message.actionId)) {
48
75
  await this.actionHandlers[message.actionId](message.params);
49
76
  }
50
- message.resolve();
51
77
  });
52
78
  return this;
53
79
  }
54
80
  setActionHandler(actionId, handler) {
55
81
  this.actionHandlers[actionId] = handler;
56
82
  }
57
- async setState(state) {
83
+ set state(state) {
84
+ this.setState(state);
85
+ }
86
+ get state() {
87
+ return __classPrivateFieldGet(this, _AppletContext_state, "f");
88
+ }
89
+ async setState(state, shouldRender) {
58
90
  const message = new AppletMessage('state', { state });
59
91
  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.
92
+ __classPrivateFieldSet(this, _AppletContext_state, state, "f");
93
+ if (shouldRender !== false && !this.headless) {
94
+ this.onrender();
95
+ this.dispatchEvent(new CustomEvent('render'));
96
+ }
63
97
  }
64
98
  onload() { }
65
99
  onready() { }
66
100
  onrender() { }
67
101
  }
102
+ _AppletContext_state = new WeakMap();
68
103
  function isActionMessage(message) {
69
104
  return message.type === 'action';
70
105
  }
@@ -76,14 +111,12 @@ function isStateMessage(message) {
76
111
  */
77
112
  class AppletClient {
78
113
  on(messageType, callback) {
79
- window.addEventListener('message', (messageEvent) => {
114
+ window.addEventListener('message', async (messageEvent) => {
80
115
  if (messageEvent.data.type !== messageType)
81
116
  return;
82
117
  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);
118
+ await callback(message);
119
+ window.parent.postMessage(new AppletMessage('resolve', { id: message.id }), '*');
87
120
  });
88
121
  }
89
122
  send(message) {
package/dist/types.d.ts CHANGED
@@ -1,14 +1,19 @@
1
1
  export interface AppletManifest {
2
2
  type: 'applet';
3
3
  name: string;
4
- description: string;
4
+ description?: string;
5
5
  icon?: string;
6
- entrypoint: string;
7
- actions: AppletAction[];
6
+ frameless?: boolean;
7
+ entrypoint?: string;
8
+ actions?: AppletAction[];
9
+ }
10
+ export interface AppletManifestDict {
11
+ [url: string]: AppletManifest;
8
12
  }
9
13
  export interface AppletAction {
10
14
  id: string;
11
- description: string;
15
+ name?: string;
16
+ description?: string;
12
17
  params?: ActionParamSchema;
13
18
  }
14
19
  export interface AppletHeader {
@@ -23,12 +28,12 @@ export interface AppletHeader {
23
28
  };
24
29
  }[];
25
30
  }
26
- export type AppletState = Record<string, Serializable>;
31
+ export type AppletState = any;
27
32
  export type ActionParamSchema = Record<string, {
28
33
  description: string;
29
34
  type: 'string';
30
35
  }>;
31
- export type ActionParams = Record<string, unknown>;
36
+ export type ActionParams<T = any> = Record<string, T>;
32
37
  export type ActionHandlerDict = {
33
38
  [key: string]: ActionHandler<any>;
34
39
  };
@@ -38,11 +43,22 @@ export interface AppletStateMessage<T = any> extends AppletMessage {
38
43
  type: 'state';
39
44
  state: T;
40
45
  }
46
+ export interface AppletResizeMessage extends AppletMessage {
47
+ type: 'resize';
48
+ dimensions: {
49
+ height: number;
50
+ width: number;
51
+ };
52
+ }
41
53
  export interface AppletActionMessage<T = any> extends AppletMessage {
42
54
  type: 'action';
43
55
  actionId: string;
44
56
  params: T;
45
57
  }
58
+ export interface AppletInitMessage extends AppletMessage {
59
+ type: 'init';
60
+ headless: boolean;
61
+ }
46
62
  export declare class AppletMessage<T = any> {
47
63
  type: AppletMessageType;
48
64
  id: string;
@@ -53,9 +69,5 @@ export declare class AppletMessage<T = any> {
53
69
  };
54
70
  resolve(): void;
55
71
  }
56
- export type AppletMessageType = 'action' | 'render' | 'state' | 'ready' | 'resolve' | 'resize';
57
- export type AppletMessageCallback = (message: AnyAppletMessage) => void;
58
- type Serializable = string | number | boolean | null | Serializable[] | {
59
- [key: string]: Serializable;
60
- };
61
- export {};
72
+ export type AppletMessageType = 'action' | 'actions' | 'render' | 'state' | 'init' | 'ready' | 'resolve' | 'resize';
73
+ export type AppletMessageCallback = (message: AnyAppletMessage) => Promise<void> | void;
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,18 @@
1
- import { type AppletManifest } from './types';
1
+ import { AppletAction, type AppletManifest } from './types';
2
2
  export declare function getAppletsList(url: string): Promise<any>;
3
3
  export declare function loadAppletManifest(url: string): Promise<AppletManifest>;
4
+ export declare function createOpenAISchemaForAction(action: AppletAction): {
5
+ strict: boolean;
6
+ name: string;
7
+ schema: {
8
+ type: string;
9
+ required: string[];
10
+ properties: {
11
+ id: {
12
+ type: string;
13
+ };
14
+ params: import("./types").ActionParamSchema;
15
+ };
16
+ additionalProperties: boolean;
17
+ };
18
+ };
package/dist/utils.js CHANGED
@@ -31,3 +31,18 @@ export async function loadAppletManifest(url) {
31
31
  appletManifest.entrypoint = parseUrl(appletManifest.entrypoint, url);
32
32
  return appletManifest;
33
33
  }
34
+ export function createOpenAISchemaForAction(action) {
35
+ return {
36
+ strict: true,
37
+ name: 'action_schema',
38
+ schema: {
39
+ type: 'object',
40
+ required: Object.keys(action),
41
+ properties: {
42
+ id: { type: 'string' },
43
+ params: action.params,
44
+ },
45
+ additionalProperties: false,
46
+ },
47
+ };
48
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import { applets } from '../index';
2
+ class AppletView extends HTMLElement {
3
+ connectedCallback() {
4
+ const styles = document.createElement('style');
5
+ styles.textContent = this.styles;
6
+ this.appendChild(styles);
7
+ this.container = document.createElement('iframe');
8
+ this.appendChild(this.container);
9
+ }
10
+ get styles() {
11
+ return /*css*/ `
12
+ applet-frame {
13
+ display: flex;
14
+ flex-direction: column;
15
+ }
16
+
17
+ applet-frame iframe {
18
+ border: none;
19
+ }
20
+
21
+ applet-frame:not(.frameless) {
22
+ border: 1px solid #ddd;
23
+ }
24
+
25
+ applet-frame.frameless {
26
+ padding: 0 7px;
27
+ }
28
+ `;
29
+ }
30
+ set url(url) {
31
+ setTimeout(() => this.loadApplet(url), 1);
32
+ }
33
+ async loadApplet(url) {
34
+ if (!this.container)
35
+ return;
36
+ this.applet = await applets.load(url, this.container);
37
+ if (this.applet.manifest.frameless)
38
+ this.classList.add('frameless');
39
+ this.applet.onstateupdated = () => {
40
+ this.dispatchEvent(new CustomEvent('stateupdated', { detail: this.applet.state }));
41
+ };
42
+ this.dispatchEvent(new CustomEvent('load'));
43
+ }
44
+ set state(state) {
45
+ if (this.applet)
46
+ this.applet.state = state;
47
+ this.addEventListener('load', () => {
48
+ this.applet.state = state;
49
+ });
50
+ }
51
+ }
52
+ customElements.define('applet-frame', AppletView);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import { applets } from '../index.js';
2
+ class AppletFrame extends HTMLElement {
3
+ connectedCallback() {
4
+ const styles = document.createElement('style');
5
+ styles.textContent = this.styles;
6
+ this.appendChild(styles);
7
+ this.container = document.createElement('iframe');
8
+ this.appendChild(this.container);
9
+ }
10
+ get styles() {
11
+ return /*css*/ `
12
+ applet-frame {
13
+ display: flex;
14
+ flex-direction: column;
15
+ }
16
+
17
+ applet-frame iframe {
18
+ border: none;
19
+ }
20
+
21
+ applet-frame:not(.frameless) {
22
+ border: 1px solid #ddd;
23
+ }
24
+
25
+ applet-frame.frameless {
26
+ padding: 0 7px;
27
+ }
28
+ `;
29
+ }
30
+ set url(url) {
31
+ setTimeout(() => this.loadApplet(url), 1);
32
+ }
33
+ async loadApplet(url) {
34
+ if (!this.container)
35
+ return;
36
+ this.applet = await applets.load(url, this.container);
37
+ if (this.applet.manifest.frameless)
38
+ this.classList.add('frameless');
39
+ this.applet.onstateupdated = () => {
40
+ this.dispatchEvent(new CustomEvent('stateupdated', { detail: this.applet.state }));
41
+ };
42
+ this.dispatchEvent(new CustomEvent('load'));
43
+ }
44
+ set state(state) {
45
+ if (this.applet)
46
+ this.applet.state = state;
47
+ this.addEventListener('load', () => {
48
+ this.applet.state = state;
49
+ });
50
+ }
51
+ }
52
+ customElements.define('applet-frame', AppletFrame);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@web-applets/sdk",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "The Web Applets SDK, for creating & hosting Web Applets.",
5
5
  "author": "Rupert Manfredi <rupert@unternet.co>",
6
6
  "license": "MIT",
@@ -25,6 +25,7 @@
25
25
  "typescript": "^5.6.2"
26
26
  },
27
27
  "dependencies": {
28
+ "marked": "^14.1.3",
28
29
  "vite": "^5.4.7"
29
30
  }
30
31
  }