@web-applets/sdk 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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);