@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/dist/client.js DELETED
@@ -1,197 +0,0 @@
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_state, _Applet_dispatchEvent;
13
- import { AppletMessage, } from './types';
14
- const hiddenContainer = document.createElement('iframe');
15
- hiddenContainer.style.display = 'none';
16
- document.body.appendChild(hiddenContainer);
17
- export async function list(url) {
18
- url = parseUrl(url);
19
- try {
20
- const request = await fetch(`${url}/manifest.json`);
21
- const appManifest = await request.json();
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;
32
- }
33
- catch {
34
- return {};
35
- }
36
- }
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
- url = parseUrl(url);
44
- const manifest = await loadManifest(`${url}`);
45
- if (!container) {
46
- container = hiddenContainer;
47
- _opts.headless = true;
48
- }
49
- if (_opts.unsafe || manifest.unsafe) {
50
- container.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin');
51
- }
52
- else {
53
- container.setAttribute('sandbox', 'allow-scripts allow-forms');
54
- }
55
- const applet = new Applet();
56
- applet.manifest = manifest;
57
- applet.actions = manifest.actions;
58
- applet.container = container;
59
- container.src = applet.manifest.entrypoint;
60
- return new Promise((resolve) => {
61
- applet.on('ready', () => {
62
- const initMessage = new AppletMessage('init', {
63
- headless: _opts.headless,
64
- });
65
- applet.send(initMessage);
66
- resolve(applet);
67
- });
68
- });
69
- }
70
- export class Applet extends EventTarget {
71
- constructor() {
72
- super();
73
- _Applet_instances.add(this);
74
- this.actions = [];
75
- _Applet_state.set(this, void 0);
76
- this.on('state', (message) => {
77
- __classPrivateFieldSet(this, _Applet_state, message.state, "f");
78
- __classPrivateFieldGet(this, _Applet_instances, "m", _Applet_dispatchEvent).call(this, 'stateupdated', message.state);
79
- });
80
- this.on('resize', (message) => {
81
- this.resizeContainer(message.dimensions);
82
- });
83
- }
84
- get state() {
85
- return __classPrivateFieldGet(this, _Applet_state, "f");
86
- }
87
- set state(state) {
88
- __classPrivateFieldSet(this, _Applet_state, state, "f");
89
- this.send(new AppletMessage('state', { state }));
90
- }
91
- toJson() {
92
- return Object.fromEntries(Object.entries(this).filter(([_, value]) => {
93
- try {
94
- JSON.stringify(value);
95
- return true;
96
- }
97
- catch {
98
- return false;
99
- }
100
- }));
101
- }
102
- resizeContainer(dimensions) {
103
- this.container.style.height = `${dimensions.height + 2}px`;
104
- // if (!this.#styleOverrides) {
105
- // this.#container.style.height = `${dimensions.height}px`;
106
- // }
107
- }
108
- onstateupdated(event) { }
109
- disconnect() {
110
- this.onstateupdated = () => { };
111
- this.container.src = 'about:blank';
112
- }
113
- async dispatchAction(actionId, params) {
114
- const requestMessage = new AppletMessage('action', {
115
- actionId,
116
- params,
117
- });
118
- return await this.send(requestMessage);
119
- }
120
- async send(message) {
121
- this.container.contentWindow?.postMessage(message.toJson(), '*');
122
- return new Promise((resolve) => {
123
- const listener = (messageEvent) => {
124
- const responseMessage = new AppletMessage(messageEvent.data.type, messageEvent.data);
125
- if (responseMessage.type === 'resolve' &&
126
- responseMessage.id === message.id) {
127
- window.removeEventListener('message', listener);
128
- resolve(responseMessage);
129
- }
130
- };
131
- window.addEventListener('message', listener);
132
- });
133
- }
134
- async on(messageType, callback) {
135
- const listener = async (messageEvent) => {
136
- if (messageEvent.source !== this.container.contentWindow)
137
- return;
138
- if (messageEvent.data.type !== messageType)
139
- return;
140
- const message = new AppletMessage(messageEvent.data.type, messageEvent.data);
141
- await callback(message);
142
- this.container.contentWindow?.postMessage(new AppletMessage('resolve', { id: message.id }), '*');
143
- };
144
- window.addEventListener('message', listener);
145
- }
146
- }
147
- _Applet_state = new WeakMap(), _Applet_instances = new WeakSet(), _Applet_dispatchEvent = function _Applet_dispatchEvent(id, detail) {
148
- if (typeof this[`on${id}`] === 'function') {
149
- this[`on${id}`](detail);
150
- }
151
- this.dispatchEvent(new CustomEvent(id, { detail }));
152
- };
153
- /* Helpers */
154
- function parseUrl(url, base) {
155
- if (['http', 'https'].includes(url.split('://')[0])) {
156
- return url;
157
- }
158
- let path = trimSlashes(url);
159
- url = `${base || window.location.origin}/${path}`;
160
- return url;
161
- }
162
- function trimSlashes(str) {
163
- return str.replace(/^\/+|\/+$/g, '');
164
- }
165
- export async function loadManifest(url) {
166
- url = parseUrl(url);
167
- const request = await fetch(`${url}/manifest.json`);
168
- const appletManifest = await request.json();
169
- if (appletManifest.type !== 'applet') {
170
- throw new Error("URL doesn't point to a valid applet manifest.");
171
- }
172
- appletManifest.entrypoint = parseUrl(appletManifest.entrypoint, url);
173
- return appletManifest;
174
- }
175
- export async function getHeaders(url) {
176
- url = parseUrl(url);
177
- try {
178
- const request = await fetch(`${url}/manifest.json`);
179
- const appManifest = await request.json();
180
- const appletHeaders = appManifest.applets;
181
- return appletHeaders ?? [];
182
- }
183
- catch {
184
- return [];
185
- }
186
- }
187
- export async function getManifests(url) {
188
- url = parseUrl(url);
189
- const request = await fetch(`${url}/manifest.json`);
190
- const headers = (await request.json()).applets;
191
- const manifests = await Promise.all(headers.map(async (header) => {
192
- const appletUrl = parseUrl(header.url);
193
- const request = await fetch(`${appletUrl}/manifest.json`);
194
- return await request.json();
195
- }));
196
- return manifests ?? [];
197
- }
package/dist/context.d.ts DELETED
@@ -1,27 +0,0 @@
1
- import { ActionHandlerDict, AppletMessage, AppletMessageType, AppletMessageCallback, ActionParams, ActionHandler } from './types';
2
- /**
3
- * Context
4
- */
5
- export declare class AppletContext<StateType = any> extends EventTarget {
6
- #private;
7
- client: AppletClient;
8
- actionHandlers: ActionHandlerDict;
9
- headless: boolean;
10
- connect(): this;
11
- setActionHandler<T extends ActionParams>(actionId: string, handler: ActionHandler<T>): void;
12
- set state(state: StateType);
13
- get state(): StateType;
14
- setState(state: StateType, shouldRender?: boolean): Promise<void>;
15
- onload(): Promise<void> | void;
16
- onready(): Promise<void> | void;
17
- onrender(): void;
18
- }
19
- /**
20
- * Client
21
- */
22
- declare class AppletClient {
23
- on(messageType: AppletMessageType, callback: AppletMessageCallback): void;
24
- send(message: AppletMessage): Promise<void>;
25
- }
26
- export declare const appletContext: AppletContext<any>;
27
- export {};
package/dist/context.js DELETED
@@ -1,136 +0,0 @@
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;
13
- import { AppletMessage, } from './types';
14
- /**
15
- * Context
16
- */
17
- export class AppletContext extends EventTarget {
18
- constructor() {
19
- super(...arguments);
20
- this.actionHandlers = {};
21
- _AppletContext_state.set(this, void 0);
22
- this.headless = false;
23
- }
24
- connect() {
25
- this.client = new AppletClient();
26
- const startup = async () => {
27
- await this.onload();
28
- this.client.send(new AppletMessage('ready'));
29
- this.dispatchEvent(new CustomEvent('ready'));
30
- await this.onready();
31
- };
32
- if (document.readyState === 'complete' ||
33
- document.readyState === 'interactive') {
34
- setTimeout(startup, 1);
35
- }
36
- else {
37
- window.addEventListener('DOMContentLoaded', startup);
38
- }
39
- const resizeObserver = new ResizeObserver((entries) => {
40
- for (let entry of entries) {
41
- const message = new AppletMessage('resize', {
42
- dimensions: {
43
- width: entry.contentRect.width,
44
- height: entry.contentRect.height,
45
- },
46
- });
47
- this.client.send(message);
48
- }
49
- });
50
- resizeObserver.observe(document.querySelector('html'));
51
- this.client.on('init', (message) => {
52
- const initMessage = message;
53
- this.headless = initMessage.headless;
54
- });
55
- this.client.on('state', (message) => {
56
- if (!isStateMessage(message)) {
57
- throw new TypeError("Message doesn't match type StateMessage");
58
- }
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
- // }
69
- });
70
- this.client.on('action', async (message) => {
71
- if (!isActionMessage(message)) {
72
- throw new TypeError("Message doesn't match type AppletMessage.");
73
- }
74
- if (Object.keys(this.actionHandlers).includes(message.actionId)) {
75
- await this.actionHandlers[message.actionId](message.params);
76
- }
77
- });
78
- return this;
79
- }
80
- setActionHandler(actionId, handler) {
81
- this.actionHandlers[actionId] = handler;
82
- }
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) {
90
- const message = new AppletMessage('state', { state });
91
- await this.client.send(message);
92
- __classPrivateFieldSet(this, _AppletContext_state, state, "f");
93
- if (shouldRender !== false && !this.headless) {
94
- this.onrender();
95
- this.dispatchEvent(new CustomEvent('render'));
96
- }
97
- }
98
- onload() { }
99
- onready() { }
100
- onrender() { }
101
- }
102
- _AppletContext_state = new WeakMap();
103
- function isActionMessage(message) {
104
- return message.type === 'action';
105
- }
106
- function isStateMessage(message) {
107
- return message.type === 'state';
108
- }
109
- /**
110
- * Client
111
- */
112
- class AppletClient {
113
- on(messageType, callback) {
114
- window.addEventListener('message', async (messageEvent) => {
115
- if (messageEvent.data.type !== messageType)
116
- return;
117
- const message = new AppletMessage(messageEvent.data.type, messageEvent.data);
118
- await callback(message);
119
- window.parent.postMessage(new AppletMessage('resolve', { id: message.id }), '*');
120
- });
121
- }
122
- send(message) {
123
- window.parent.postMessage(message.toJson(), '*');
124
- return new Promise((resolve) => {
125
- const listener = (messageEvent) => {
126
- if (messageEvent.data.type === 'resolve' &&
127
- messageEvent.data.id === message.id) {
128
- window.removeEventListener('message', listener);
129
- resolve();
130
- }
131
- };
132
- window.addEventListener('message', listener);
133
- });
134
- }
135
- }
136
- export const appletContext = new AppletContext();
package/dist/types.d.ts DELETED
@@ -1,74 +0,0 @@
1
- export interface AppletManifest {
2
- type: 'applet';
3
- name: string;
4
- description?: string;
5
- icon?: string;
6
- unsafe?: boolean;
7
- frameless?: boolean;
8
- entrypoint?: string;
9
- actions?: AppletAction[];
10
- }
11
- export interface AppletManifestDict {
12
- [url: string]: AppletManifest;
13
- }
14
- export interface AppletAction {
15
- id: string;
16
- name?: string;
17
- description?: string;
18
- params?: ActionParamSchema;
19
- }
20
- export interface AppletHeader {
21
- name: string;
22
- description: string;
23
- url: string;
24
- actions: {
25
- id: string;
26
- description: string;
27
- params: {
28
- [key: string]: string;
29
- };
30
- }[];
31
- }
32
- export type AppletState = any;
33
- export type ActionParamSchema = Record<string, {
34
- description: string;
35
- type: 'string';
36
- }>;
37
- export type ActionParams<T = any> = Record<string, T>;
38
- export type ActionHandlerDict = {
39
- [key: string]: ActionHandler<any>;
40
- };
41
- export type ActionHandler<T extends ActionParams> = (params: T) => void | Promise<void>;
42
- export type AnyAppletMessage = AppletMessage | AppletStateMessage | AppletActionMessage;
43
- export interface AppletStateMessage<T = any> extends AppletMessage {
44
- type: 'state';
45
- state: T;
46
- }
47
- export interface AppletResizeMessage extends AppletMessage {
48
- type: 'resize';
49
- dimensions: {
50
- height: number;
51
- width: number;
52
- };
53
- }
54
- export interface AppletActionMessage<T = any> extends AppletMessage {
55
- type: 'action';
56
- actionId: string;
57
- params: T;
58
- }
59
- export interface AppletInitMessage extends AppletMessage {
60
- type: 'init';
61
- headless: boolean;
62
- }
63
- export declare class AppletMessage<T = any> {
64
- type: AppletMessageType;
65
- id: string;
66
- timeStamp: number;
67
- constructor(type: AppletMessageType, values?: T);
68
- toJson(): {
69
- [k: string]: any;
70
- };
71
- resolve(): void;
72
- }
73
- export type AppletMessageType = 'action' | 'actions' | 'render' | 'state' | 'init' | 'ready' | 'resolve' | 'resize';
74
- export type AppletMessageCallback = (message: AnyAppletMessage) => Promise<void> | void;
package/dist/types.js DELETED
@@ -1,21 +0,0 @@
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
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,52 +0,0 @@
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);
@@ -1 +0,0 @@
1
- export {};
@@ -1,52 +0,0 @@
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);