@agentshouse/app-bridge 0.1.0-alpha.9 → 0.1.0-c357a960443ec

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 CHANGED
@@ -2,56 +2,10 @@
2
2
 
3
3
  The renderer-side client of the agents.house App bridge. An App served by House
4
4
  receives one message port before its own scripts run; this package is that
5
- port's one listener. It carries six families:
6
-
7
- - **Navigation** - the part of the shell's URL below the App's path, with its
8
- query and fragment. The shell delivers it when the App starts and whenever the
9
- User navigates; the App asks the shell to push or replace it. The App also
10
- sets its tab's title and icon with `setTab({ title, icon })`, either or both:
11
- a title of at most 200 characters and an icon given as a base64 `data:` URL
12
- of an SVG, PNG, GIF or WebP image of at most 1,048,576 characters. The shell
13
- shows them over the App's display name, or a Site's title, and its tab icon
14
- until the next load, and ignores a request outside those bounds.
15
- - **Ceremony** - the App asks the shell to leave for one of House's own
16
- purpose-bound authenticated pages. The shell navigates only to a same-origin
17
- absolute path, carries its current address as the return target, and the page
18
- returns there when it completes. An App names an address House gave it in a
19
- reading and composes none.
20
- - **Installation** - `install()` offers the page as a Home-screen app. Where the
21
- browser has an install prompt ready the shell shows it and the call resolves
22
- `'accepted'` or `'dismissed'` with the User's choice, spending that prompt;
23
- otherwise, as on every iPhone and iPad, it resolves `'manual'` and the App
24
- shows its own instructions. A prompt the browser refuses to show, as for a
25
- call made without a click, rejects with the browser's own `DOMException`
26
- and stays ready. Call it from a click. The standard
27
- `display-mode: standalone` media query tells the App it runs installed.
28
- - **Status** - the App reference the Runtime port is attached under, sync
29
- state, credential state, release availability and observed failure, `ga`:
30
- whether the page measures and the visitor's stored analytics choice, and
31
- `service_mode`: `open`, `write-halted` while House refuses writes, so the
32
- App can stop retrying them, or `null` while the runtime holds no House
33
- connection.
34
- The App reports the visitor's choice with `gaConsent('accept')` or
35
- `gaConsent('decline')`; the shell hands it to House's analytics loader, and
36
- the choice never reaches House.
37
- - **Site image read** - given a Published Room and an original attachment
38
- reference, it answers the web copy's path and metadata, or `null` when no
39
- copy exists. The original bytes never cross this bridge at a Site.
40
- - **App release code** - `sourceTop(app, release)` answers the top level of
41
- the latest successful release of an App the User may use: its folders, its
42
- files with their sizes and its root README's text, or `null` for none;
43
- `sourceDownload(app, release)` answers every file of that release with its
44
- path, its size and its `text`, or its bytes as `base64`. The browser keeps
45
- each answer per App and release: a read naming a release it holds answers
46
- without asking House, and keeping a newer release drops the earlier one. A
47
- refusal is House's `{ error: { code } }`, `app_release_not_found` for an
48
- App with no release; a read House does not answer, or one that outlived
49
- the browser's signed-in User, is `null`.
50
- - **App screenshot read** - given an App reference, `readScreenshot(app)`
51
- answers the image this browser captured of that App's screen the first time
52
- it ran its current release, as a PNG `Blob` at most 1200 pixels wide, or
53
- `null` when this browser holds none. The image stays in the browser; a Site
54
- holds none.
5
+ port's one listener. It carries eleven families: navigation, ceremonies,
6
+ installation, notifications, status, Site image reads, App release code,
7
+ external calls, App messages and App and Site screenshot reads.
8
+ [docs/families.md](docs/families.md) describes each and the test host.
55
9
 
56
10
  The package carries no credential handling and no Runtime family. It surfaces
57
11
  the dedicated Runtime port the worker transfers over the bridge as a plain
@@ -100,18 +54,3 @@ const bridge = connectHouseBridge(host.port);
100
54
  bridge.push('/rooms/atlas/members');
101
55
  host.open('/rooms/atlas');
102
56
  ```
103
-
104
- The host plays the shell and the worker: it delivers the address the App starts
105
- at and each address `open` plays as Back or Forward through the shell's own
106
- slice below the App's path, admitting nothing, so it delivers `//notes` exactly
107
- as a browser at `/apps/<app>//notes` does. It records every push and replace
108
- and answers each with the address the shell would then show, records an
109
- admitted ceremony, refuses a push, replace or ceremony that is not a
110
- same-origin absolute path, publishes a status, its `service_mode` included,
111
- records in `consents` each
112
- `gaConsent` it takes while that status measures and publishes the status the
113
- choice leaves, records in `tabs` each `setTab` the shell would take and ignores
114
- any other, answers a release code read from the `sources` it was given, or
115
- `null`, answers `readScreenshot` from the `screenshots` it was given by App
116
- reference, answers every `install()` `'manual'`, as a browser with no install
117
- prompt does, and attaches a Runtime port.
@@ -1,4 +1,4 @@
1
- import type { HouseBridgePort, HouseGaChoice, HouseMessagePort, HouseSourceDownload, HouseSourceRefusal, HouseSourceTop, HouseStatus, HouseTab } from './wire.js';
1
+ import type { HouseAppMessage, HouseExternalAnswer, HouseExternalRequest, HouseBridgePort, HouseGaChoice, HouseMessagePort, HouseRefusal, HouseSourceDownload, HouseSourceTop, HouseStatus, HouseTab } from './wire.js';
2
2
  export declare class HouseCeremonyRefused extends Error {
3
3
  readonly path: string | null;
4
4
  constructor(path: string | null);
@@ -14,6 +14,7 @@ export interface HouseImageCopy {
14
14
  readonly media_type: string;
15
15
  }
16
16
  type HouseInstallation = 'accepted' | 'dismissed' | 'manual';
17
+ type HouseNotifications = 'on' | 'off' | 'blocked' | 'unavailable';
17
18
  export interface HouseBridge {
18
19
  readonly runtime: Promise<HouseMessagePort>;
19
20
  address(): string | null;
@@ -22,13 +23,18 @@ export interface HouseBridge {
22
23
  replace(address: string): Promise<string>;
23
24
  ceremony(path: string): Promise<never>;
24
25
  install(): Promise<HouseInstallation>;
26
+ notifications(on?: boolean): Promise<HouseNotifications>;
27
+ setBadge(unread: readonly string[]): Promise<void>;
25
28
  setTab(tab: HouseTab): void;
26
29
  readAttachment(room: string, attachment: string): Promise<HouseImageCopy | null>;
27
- sourceTop(app: string, release?: string): Promise<HouseSourceTop | HouseSourceRefusal | null>;
28
- sourceDownload(app: string, release?: string): Promise<HouseSourceDownload | HouseSourceRefusal | null>;
30
+ sourceTop(app: string, release?: string): Promise<HouseSourceTop | HouseRefusal | null>;
31
+ sourceDownload(app: string, release?: string): Promise<HouseSourceDownload | HouseRefusal | null>;
32
+ callExternal(request: HouseExternalRequest): Promise<HouseExternalAnswer | HouseRefusal | null>;
29
33
  readScreenshot(app: string): Promise<Blob | null>;
34
+ readSiteScreenshot(site: string): Promise<Blob | null>;
30
35
  status(): HouseStatus | null;
31
36
  onStatus(listener: (status: HouseStatus) => void): () => void;
37
+ messages(listener: (message: HouseAppMessage) => void): () => void;
32
38
  gaConsent(choice: HouseGaChoice): void;
33
39
  }
34
40
  export declare function connectHouseBridge(source?: Promise<HouseBridgePort>): HouseBridge;
@@ -14,6 +14,20 @@ export class HouseNavigationRefused extends Error {
14
14
  this.address = address;
15
15
  }
16
16
  }
17
+ function answered(pending, answer, value) {
18
+ if (typeof answer.id !== 'number')
19
+ return;
20
+ const asked = pending.get(answer.id);
21
+ if (asked === undefined)
22
+ return;
23
+ pending.delete(answer.id);
24
+ if (answer.refused === true)
25
+ asked.reject(new HouseCeremonyRefused(null));
26
+ else if (answer.refusal !== undefined)
27
+ asked.reject(new DOMException(answer.refusal.message, answer.refusal.name));
28
+ else
29
+ asked.resolve(value);
30
+ }
17
31
  function settled() {
18
32
  let settle = null;
19
33
  const promise = new Promise((resolve, reject) => {
@@ -37,10 +51,17 @@ export function connectHouseBridge(source = heldBridge()) {
37
51
  let nextSource = 0;
38
52
  const installations = new Map();
39
53
  let nextInstallation = 0;
54
+ const notifications = new Map();
55
+ let nextNotifications = 0;
56
+ const badges = new Map();
57
+ let nextBadge = 0;
40
58
  const screenshots = new Map();
41
59
  let nextScreenshot = 0;
60
+ const externals = new Map();
61
+ let nextExternal = 0;
42
62
  const addressListeners = new Set();
43
63
  const statusListeners = new Set();
64
+ const messageListeners = new Set();
44
65
  let outbox = [];
45
66
  let held = null;
46
67
  let address = null;
@@ -67,6 +88,12 @@ export function connectHouseBridge(source = heldBridge()) {
67
88
  listener(status);
68
89
  return;
69
90
  }
91
+ if (data.house === 'app-message') {
92
+ const { from, body } = data;
93
+ for (const listener of messageListeners)
94
+ listener({ from, body });
95
+ return;
96
+ }
70
97
  if (data.house === 'address') {
71
98
  address = String(data.address);
72
99
  if (data.navigated === true)
@@ -95,27 +122,38 @@ export function connectHouseBridge(source = heldBridge()) {
95
122
  return;
96
123
  }
97
124
  if (data.house === 'install') {
125
+ const answer = data;
126
+ answered(installations, answer, answer.outcome);
127
+ return;
128
+ }
129
+ if (data.house === 'notifications') {
130
+ const answer = data;
131
+ answered(notifications, answer, answer.state);
132
+ return;
133
+ }
134
+ if (data.house === 'badge') {
135
+ answered(badges, data, undefined);
136
+ return;
137
+ }
138
+ if (data.house === 'source') {
98
139
  const answer = data;
99
140
  if (typeof answer.id !== 'number')
100
141
  return;
101
- const pending = installations.get(answer.id);
142
+ const pending = sources.get(answer.id);
102
143
  if (pending === undefined)
103
144
  return;
104
- installations.delete(answer.id);
105
- if (answer.outcome !== undefined)
106
- pending.resolve(answer.outcome);
107
- else
108
- pending.reject(new DOMException(answer.refusal?.message, answer.refusal?.name));
145
+ sources.delete(answer.id);
146
+ pending.resolve(answer.answer ?? null);
109
147
  return;
110
148
  }
111
- if (data.house === 'source') {
149
+ if (data.house === 'external-call') {
112
150
  const answer = data;
113
151
  if (typeof answer.id !== 'number')
114
152
  return;
115
- const pending = sources.get(answer.id);
153
+ const pending = externals.get(answer.id);
116
154
  if (pending === undefined)
117
155
  return;
118
- sources.delete(answer.id);
156
+ externals.delete(answer.id);
119
157
  pending.resolve(answer.answer ?? null);
120
158
  return;
121
159
  }
@@ -154,6 +192,13 @@ export function connectHouseBridge(source = heldBridge()) {
154
192
  send({ house, id, app, release });
155
193
  return pending.promise;
156
194
  }
195
+ function screenshot(house, asked) {
196
+ const pending = settled();
197
+ const id = ++nextScreenshot;
198
+ screenshots.set(id, pending.settle);
199
+ send({ house, id, ...asked });
200
+ return pending.promise;
201
+ }
157
202
  function navigate(mode, asked) {
158
203
  const pending = settled();
159
204
  navigations.push({ address: asked, settle: pending.settle });
@@ -190,6 +235,20 @@ export function connectHouseBridge(source = heldBridge()) {
190
235
  send({ house: 'install', id });
191
236
  return pending.promise;
192
237
  },
238
+ notifications(on) {
239
+ const pending = settled();
240
+ const id = ++nextNotifications;
241
+ notifications.set(id, pending.settle);
242
+ send({ house: 'notifications', id, on });
243
+ return pending.promise;
244
+ },
245
+ setBadge(unread) {
246
+ const pending = settled();
247
+ const id = ++nextBadge;
248
+ badges.set(id, pending.settle);
249
+ send({ house: 'badge', id, unread });
250
+ return pending.promise;
251
+ },
193
252
  setTab(tab) {
194
253
  send({ house: 'tab', title: tab.title, icon: tab.icon });
195
254
  },
@@ -206,13 +265,19 @@ export function connectHouseBridge(source = heldBridge()) {
206
265
  sourceDownload(app, release) {
207
266
  return readSource('source-download', app, release);
208
267
  },
209
- readScreenshot(app) {
268
+ callExternal(request) {
210
269
  const pending = settled();
211
- const id = ++nextScreenshot;
212
- screenshots.set(id, pending.settle);
213
- send({ house: 'screenshot-read', id, app });
270
+ const id = ++nextExternal;
271
+ externals.set(id, pending.settle);
272
+ send({ house: 'external-call', id, request });
214
273
  return pending.promise;
215
274
  },
275
+ readScreenshot(app) {
276
+ return screenshot('screenshot-read', { app });
277
+ },
278
+ readSiteScreenshot(site) {
279
+ return screenshot('site-screenshot-read', { site });
280
+ },
216
281
  status() {
217
282
  return status;
218
283
  },
@@ -222,6 +287,12 @@ export function connectHouseBridge(source = heldBridge()) {
222
287
  statusListeners.delete(listener);
223
288
  };
224
289
  },
290
+ messages(listener) {
291
+ messageListeners.add(listener);
292
+ return () => {
293
+ messageListeners.delete(listener);
294
+ };
295
+ },
225
296
  gaConsent(choice) {
226
297
  send({ house: 'ga/consent', choice });
227
298
  },
@@ -0,0 +1,3 @@
1
+ export { connectHouseBridge, HouseCeremonyRefused, HouseNavigationRefused, type HouseBridge, type HouseImageCopy, } from './bridge.js';
2
+ export { HOUSE_ADDRESS_MAX_CHARS } from './wire.js';
3
+ export type { HouseAppMessage, HouseBridgeEvent, HouseBridgePort, HouseCredentialState, HouseExternalAnswer, HouseExternalRequest, HouseGa, HouseGaChoice, HouseMessagePort, HouseNavigationMode, HouseRefusal, HouseSourceDownload, HouseSourceFile, HouseSourceTop, HouseStatus, HouseSyncState, } from './wire.js';
@@ -1,5 +1,5 @@
1
1
  import type { HouseImageCopy } from './bridge.js';
2
- import type { HouseBridgePort, HouseGaChoice, HouseMessagePort, HouseNavigationMode, HouseSourceDownload, HouseSourceTop, HouseStatus, HouseTab } from './wire.js';
2
+ import type { HouseAppMessage, HouseBridgePort, HouseExternalAnswer, HouseExternalRequest, HouseGaChoice, HouseMessagePort, HouseNavigationMode, HouseRefusal, HouseSourceDownload, HouseSourceTop, HouseStatus, HouseTab } from './wire.js';
3
3
  export interface HouseBridgeNavigation {
4
4
  readonly mode: HouseNavigationMode;
5
5
  readonly address: string;
@@ -10,9 +10,11 @@ export interface HouseBridgeTestHost {
10
10
  readonly ceremonies: readonly string[];
11
11
  readonly consents: readonly HouseGaChoice[];
12
12
  readonly tabs: readonly HouseTab[];
13
+ readonly externalCalls: readonly HouseExternalRequest[];
13
14
  address(): string;
14
15
  open(address: string): void;
15
16
  publish(status: HouseStatus): void;
17
+ message(message: HouseAppMessage): void;
16
18
  attachRuntime(runtime: HouseMessagePort): void;
17
19
  }
18
20
  export interface HouseBridgeTestHostOptions {
@@ -26,5 +28,7 @@ export interface HouseBridgeTestHostOptions {
26
28
  readonly download: HouseSourceDownload;
27
29
  }[];
28
30
  readonly screenshots?: Readonly<Record<string, Blob>>;
31
+ readonly siteScreenshots?: Readonly<Record<string, Blob>>;
32
+ readonly external?: (request: HouseExternalRequest) => HouseExternalAnswer | HouseRefusal | null | Promise<HouseExternalAnswer | HouseRefusal | null>;
29
33
  }
30
34
  export declare function createHouseBridgeTestHost(options?: HouseBridgeTestHostOptions): HouseBridgeTestHost;
@@ -17,6 +17,7 @@ export function createHouseBridgeTestHost(options = {}) {
17
17
  const ceremonies = [];
18
18
  const consents = [];
19
19
  const tabs = [];
20
+ const externalCalls = [];
20
21
  const listeners = new Set();
21
22
  const queued = [];
22
23
  let started = false;
@@ -82,6 +83,12 @@ export function createHouseBridgeTestHost(options = {}) {
82
83
  else if (data.house === 'install') {
83
84
  deliver({ house: 'install', id: data.id, outcome: 'manual' });
84
85
  }
86
+ else if (data.house === 'notifications') {
87
+ deliver({ house: 'notifications', id: data.id, state: 'unavailable' });
88
+ }
89
+ else if (data.house === 'badge') {
90
+ deliver({ house: 'badge', id: data.id });
91
+ }
85
92
  else if (data.house === 'tab') {
86
93
  const change = tabChange(data);
87
94
  if (change !== null)
@@ -112,11 +119,21 @@ export function createHouseBridgeTestHost(options = {}) {
112
119
  },
113
120
  });
114
121
  }
122
+ else if (data.house === 'external-call') {
123
+ const asked = data;
124
+ externalCalls.push(asked.request);
125
+ void Promise.resolve(options.external?.(asked.request) ?? null).then((answer) => deliver({ house: 'external-call', id: asked.id, answer }));
126
+ }
115
127
  else if (data.house === 'screenshot-read') {
116
128
  const asked = data;
117
129
  const held = typeof asked.app === 'string' ? options.screenshots?.[asked.app] : undefined;
118
130
  deliver({ house: 'screenshot', id: asked.id, image: held ?? null });
119
131
  }
132
+ else if (data.house === 'site-screenshot-read') {
133
+ const asked = data;
134
+ const held = typeof asked.site === 'string' ? options.siteScreenshots?.[asked.site] : undefined;
135
+ deliver({ house: 'screenshot', id: asked.id, image: held ?? null });
136
+ }
120
137
  }
121
138
  const port = {
122
139
  postMessage(message) {
@@ -145,6 +162,7 @@ export function createHouseBridgeTestHost(options = {}) {
145
162
  ceremonies,
146
163
  consents,
147
164
  tabs,
165
+ externalCalls,
148
166
  address() {
149
167
  return address;
150
168
  },
@@ -152,6 +170,9 @@ export function createHouseBridgeTestHost(options = {}) {
152
170
  arrive(appAddressOf('/', asked), false);
153
171
  },
154
172
  publish,
173
+ message(message) {
174
+ deliver({ house: 'app-message', from: message.from, body: message.body });
175
+ },
155
176
  attachRuntime(runtime) {
156
177
  deliver({ house: 'runtime' }, [runtime]);
157
178
  },
@@ -31,7 +31,6 @@ export interface HouseStatus {
31
31
  readonly reason: string;
32
32
  } | null;
33
33
  readonly loss: string | null;
34
- readonly release: unknown;
35
34
  readonly ga: HouseGa;
36
35
  readonly service_mode: 'open' | 'write-halted' | null;
37
36
  }
@@ -59,11 +58,32 @@ export type HouseSourceFile = {
59
58
  export interface HouseSourceDownload extends HouseRelease {
60
59
  readonly files: readonly HouseSourceFile[];
61
60
  }
62
- export interface HouseSourceRefusal {
61
+ export type HouseTab = TabChange;
62
+ export interface HouseExternalRequest {
63
+ readonly url: string;
64
+ readonly method?: string;
65
+ readonly headers?: Readonly<Record<string, string>>;
66
+ readonly body?: string;
67
+ readonly body_base64?: string;
68
+ readonly account?: string;
69
+ readonly slot?: string;
70
+ }
71
+ export type HouseExternalAnswer = {
72
+ readonly status: number;
73
+ readonly headers: Readonly<Record<string, string | readonly string[]>>;
74
+ } & ({
75
+ readonly body: string;
76
+ } | {
77
+ readonly body_base64: string;
78
+ });
79
+ export interface HouseRefusal {
63
80
  readonly error: {
64
81
  readonly code: string;
65
82
  };
66
83
  }
67
- export type HouseTab = TabChange;
84
+ export interface HouseAppMessage {
85
+ readonly from: string;
86
+ readonly body: unknown;
87
+ }
68
88
  export declare const HOUSE_ADDRESS_MAX_CHARS = 1000;
69
89
  export {};
@@ -0,0 +1,103 @@
1
+ # The App bridge's families
2
+
3
+ - **Navigation** - the part of the shell's URL below the App's path, with its
4
+ query and fragment. The shell delivers it when the App starts and whenever the
5
+ User navigates; the App asks the shell to push or replace it. The App also
6
+ sets its tab's title and icon with `setTab({ title, icon })`, either or both:
7
+ a title of at most 200 characters and an icon given as a base64 `data:` URL
8
+ of an SVG, PNG, GIF or WebP image of at most 1,048,576 characters. The shell
9
+ shows them over the App's display name, or a Site's title, and its tab icon
10
+ until the next load, and ignores a request outside those bounds.
11
+ - **Ceremony** - the App asks the shell to leave for one of House's own
12
+ purpose-bound authenticated pages. The shell navigates only to a same-origin
13
+ absolute path, carries its current address as the return target, and the page
14
+ returns there when it completes. An App names an address House gave it in a
15
+ reading and composes none.
16
+ - **Installation** - `install()` offers the page as a Home-screen app. Where the
17
+ browser has an install prompt ready the shell shows it and the call resolves
18
+ `'accepted'` or `'dismissed'` with the User's choice, spending that prompt;
19
+ otherwise, as on every iPhone and iPad, it resolves `'manual'` and the App
20
+ shows its own instructions. A prompt the browser refuses to show, as for a
21
+ call made without a click, rejects with the browser's own `DOMException`
22
+ and stays ready. Call it from a click. The standard
23
+ `display-mode: standalone` media query tells the App it runs installed.
24
+ - **Notifications** - House's own panel alone; an App's or a Site's call
25
+ rejects with `HouseCeremonyRefused`. `notifications()` answers this browser
26
+ storage's push state: `'on'`, `'off'`, `'blocked'` where the browser refuses
27
+ notifications, or `'unavailable'` where it has no push, as on an iPhone or
28
+ iPad outside a Home-screen app. `notifications(true)` asks the browser's
29
+ permission and subscribes, `notifications(false)` unsubscribes, and each
30
+ answers the state it leaves; a failure of the browser's push service rejects
31
+ with the browser's own `DOMException`. Call `notifications(true)` from a
32
+ click. `setBadge(unread)` takes the identifiers of the Human Notifications
33
+ unread in this storage and sets the icon's badge to their number, or clears
34
+ it at none, where the browser shows one.
35
+ - **Status** - the App reference the Runtime port is attached under, sync
36
+ state, credential state and observed failure, `ga`:
37
+ whether the page measures and the visitor's stored analytics choice, and
38
+ `service_mode`: `open`, `write-halted` while House refuses writes, so the
39
+ App can stop retrying them, or `null` while the runtime holds no House
40
+ connection.
41
+ The App reports the visitor's choice with `gaConsent('accept')` or
42
+ `gaConsent('decline')`; the shell hands it to House's analytics loader, and
43
+ the choice never reaches House.
44
+ - **Site image read** - given a Published Room and an original attachment
45
+ reference, it answers the web copy's path and metadata, or `null` when no
46
+ copy exists. The original bytes never cross this bridge at a Site.
47
+ - **App release code** - `sourceTop(app, release)` answers the top level of
48
+ the latest successful release of an App the User may use: its folders, its
49
+ files with their sizes and its root README's text, or `null` for none;
50
+ `sourceDownload(app, release)` answers every file of that release with its
51
+ path, its size and its `text`, or its bytes as `base64`. The browser keeps
52
+ each answer per App and release: a read naming a release it holds answers
53
+ without asking House, and keeping a newer release drops the earlier one. A
54
+ refusal is House's `{ error: { code } }`, `app_release_not_found` for an
55
+ App with no release; a read House does not answer, or one that outlived
56
+ the browser's signed-in User, is `null`.
57
+ - **External calls** - `callExternal({ url, method, headers, body, slot })`
58
+ has House's server send an HTTPS request to one of the release's declared
59
+ external origins and resolves with the service's `status`, `headers` and its
60
+ body as `body` text or `body_base64`, or a refusal as House's
61
+ `{ error: { code } }`, or `null` when House does not answer. House fills in the
62
+ App and its running release; naming an account slot places the key of the
63
+ account the User connected to it, which the App never sees, and naming none
64
+ sends no key. A Site's call names no slot, and the House panel names an
65
+ `account` instead. `body_base64` replaces `body` for bytes.
66
+ - **App messages** - `messages(listener)` hands the listener each message
67
+ another holder of this App sends to this User's running copies, as `from`,
68
+ the sender's User id, and `body`, the JSON value they sent, until the
69
+ function it returns is called. App code sends one through the Runtime's
70
+ `call('apps/messages/send', { to, body })` with the recipient's User id and
71
+ a body of at most 32 KiB of JSON; House fills in the App and relays it only
72
+ to copies running now, storing nothing. The panel sends none.
73
+ - **App screenshot read** - given an App reference, `readScreenshot(app)`
74
+ answers the image this browser captured of that App's screen the first time
75
+ it ran its current release, as a PNG `Blob` at most 1200 pixels wide, or
76
+ `null` when this browser holds none. The image stays in the browser; a Site
77
+ holds none.
78
+ - **Site screenshot read** - given one of the User's Site identifiers,
79
+ `readSiteScreenshot(site)` answers the image House captured of that Site
80
+ after its latest release, as a JPEG `Blob`, or `null` when House holds none
81
+ or does not answer. A Site's own renderer reads none.
82
+
83
+ ## The test host
84
+
85
+ The host plays the shell and the worker: it delivers the address the App starts
86
+ at and each address `open` plays as Back or Forward through the shell's own
87
+ slice below the App's path, admitting nothing, so it delivers `//notes` exactly
88
+ as a browser at `/apps/<app>//notes` does. It records every push and replace
89
+ and answers each with the address the shell would then show, records an
90
+ admitted ceremony, refuses a push, replace or ceremony that is not a
91
+ same-origin absolute path, publishes a status, its `service_mode` included,
92
+ records in `consents` each
93
+ `gaConsent` it takes while that status measures and publishes the status the
94
+ choice leaves, records in `tabs` each `setTab` the shell would take and ignores
95
+ any other, answers a release code read from the `sources` it was given, or
96
+ `null`, answers each `callExternal` from the `external` function it was given, or
97
+ `null`, and records the request in `externalCalls`, answers `readScreenshot`
98
+ from the `screenshots` it was given by App
99
+ reference and `readSiteScreenshot` from the `siteScreenshots` it was given by
100
+ Site identifier, answers every `install()` `'manual'`, as a browser with no install
101
+ prompt does, answers every `notifications()` `'unavailable'`, as a browser
102
+ without push does, settles every `setBadge`, delivers each App message
103
+ `message({ from, body })` plays, and attaches a Runtime port.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentshouse/app-bridge",
3
- "version": "0.1.0-alpha.9",
4
- "description": "Renderer-side client of the agents.house App bridge: navigation, the tab title and icon, ceremonies, installation, status, Site image reads, App release code and App screenshot reads, with an in-page test host.",
3
+ "version": "0.1.0-c357a960443ec",
4
+ "description": "Renderer-side client of the agents.house App bridge: navigation, the tab title and icon, ceremonies, installation, notifications and the icon badge, status, Site image reads, App release code, App and Site screenshot reads, and App messages, with an in-page test host.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -10,21 +10,17 @@
10
10
  },
11
11
  "exports": {
12
12
  ".": {
13
- "types": "./client/src/index.d.ts",
14
- "default": "./client/src/index.js"
13
+ "types": "./dist/client/src/index.d.ts",
14
+ "default": "./dist/client/src/index.js"
15
15
  },
16
16
  "./test-host": {
17
- "types": "./client/src/test-host.d.ts",
18
- "default": "./client/src/test-host.js"
17
+ "types": "./dist/client/src/test-host.d.ts",
18
+ "default": "./dist/client/src/test-host.js"
19
19
  }
20
20
  },
21
21
  "files": [
22
- "client/src/*.js",
23
- "client/src/*.d.ts",
24
- "src/apps/remainder.js",
25
- "src/apps/remainder.d.ts",
26
- "src/apps/tab.js",
27
- "src/apps/tab.d.ts",
22
+ "dist",
23
+ "docs",
28
24
  "README.md"
29
25
  ]
30
26
  }
@@ -1,3 +0,0 @@
1
- export { connectHouseBridge, HouseCeremonyRefused, HouseNavigationRefused, type HouseBridge, type HouseImageCopy, } from './bridge.js';
2
- export { HOUSE_ADDRESS_MAX_CHARS } from './wire.js';
3
- export type { HouseBridgeEvent, HouseBridgePort, HouseCredentialState, HouseGa, HouseGaChoice, HouseMessagePort, HouseNavigationMode, HouseSourceDownload, HouseSourceFile, HouseSourceRefusal, HouseSourceTop, HouseStatus, HouseSyncState, } from './wire.js';
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes