@agentshouse/app-bridge 0.1.0-alpha.2 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,11 @@ port's one listener. It carries four families:
13
13
  returns there when it completes. An App names an address House gave it in a
14
14
  reading and composes none.
15
15
  - **Status** - the App reference the Runtime port is attached under, sync
16
- state, credential state, release availability and observed failure.
16
+ state, credential state, release availability and observed failure, and
17
+ `ga`: whether the page measures and the visitor's stored analytics choice.
18
+ The App reports the visitor's choice with `gaConsent('accept')` or
19
+ `gaConsent('decline')`; the shell hands it to House's analytics loader, and
20
+ the choice never reaches House.
17
21
  - **Site image read** - given a Published Room and an original attachment
18
22
  reference, it answers the web copy's path and metadata, or `null` when no
19
23
  copy exists. The original bytes never cross this bridge at a Site.
@@ -66,6 +70,11 @@ host.open('/rooms/atlas');
66
70
  ```
67
71
 
68
72
  The host plays the shell and the worker: it delivers the address the App starts
69
- at, records every push and replace and answers each with the address the shell
70
- would then show, records an admitted ceremony, refuses one that is not a
71
- same-origin absolute path, publishes a status, and attaches a Runtime port.
73
+ at and each address `open` plays as Back or Forward through the shell's own
74
+ slice below the App's path, admitting nothing, so it delivers `//notes` exactly
75
+ as a browser at `/apps/<app>//notes` does. It records every push and replace
76
+ and answers each with the address the shell would then show, records an
77
+ admitted ceremony, refuses a push, replace or ceremony that is not a
78
+ same-origin absolute path, publishes a status, records in `consents` each
79
+ `gaConsent` it takes while that status measures and publishes the status the
80
+ choice leaves, and attaches a Runtime port.
@@ -1,4 +1,4 @@
1
- import type { HouseBridgePort, HouseMessagePort, HouseStatus } from './wire.js';
1
+ import type { HouseBridgePort, HouseGaChoice, HouseMessagePort, HouseStatus } from './wire.js';
2
2
  export declare class HouseCeremonyRefused extends Error {
3
3
  readonly path: string | null;
4
4
  constructor(path: string | null);
@@ -23,5 +23,6 @@ export interface HouseBridge {
23
23
  readAttachment(room: string, attachment: string): Promise<HouseImageCopy | null>;
24
24
  status(): HouseStatus | null;
25
25
  onStatus(listener: (status: HouseStatus) => void): () => void;
26
+ gaConsent(choice: HouseGaChoice): void;
26
27
  }
27
28
  export declare function connectHouseBridge(source?: Promise<HouseBridgePort>): HouseBridge;
@@ -150,5 +150,8 @@ export function connectHouseBridge(source = heldBridge()) {
150
150
  statusListeners.delete(listener);
151
151
  };
152
152
  },
153
+ gaConsent(choice) {
154
+ send({ house: 'ga/consent', choice });
155
+ },
153
156
  };
154
157
  }
@@ -1,3 +1,3 @@
1
1
  export { connectHouseBridge, HouseCeremonyRefused, HouseNavigationRefused, type HouseBridge, type HouseImageCopy, } from './bridge.js';
2
2
  export { HOUSE_ADDRESS_MAX_CHARS } from './wire.js';
3
- export type { HouseBridgeEvent, HouseBridgePort, HouseCredentialState, HouseMessagePort, HouseNavigationMode, HouseStatus, HouseSyncState, } from './wire.js';
3
+ export type { HouseBridgeEvent, HouseBridgePort, HouseCredentialState, HouseGa, HouseGaChoice, HouseMessagePort, HouseNavigationMode, HouseStatus, HouseSyncState, } from './wire.js';
@@ -1,5 +1,5 @@
1
1
  import type { HouseImageCopy } from './bridge.js';
2
- import type { HouseBridgePort, HouseMessagePort, HouseNavigationMode, HouseStatus } from './wire.js';
2
+ import type { HouseBridgePort, HouseGaChoice, HouseMessagePort, HouseNavigationMode, HouseStatus } from './wire.js';
3
3
  export interface HouseBridgeNavigation {
4
4
  readonly mode: HouseNavigationMode;
5
5
  readonly address: string;
@@ -8,6 +8,7 @@ export interface HouseBridgeTestHost {
8
8
  readonly port: Promise<HouseBridgePort>;
9
9
  readonly navigations: readonly HouseBridgeNavigation[];
10
10
  readonly ceremonies: readonly string[];
11
+ readonly consents: readonly HouseGaChoice[];
11
12
  address(): string;
12
13
  open(address: string): void;
13
14
  publish(status: HouseStatus): void;
@@ -1,3 +1,4 @@
1
+ import { appAddressOf } from '../../src/apps/remainder.js';
1
2
  import { HOUSE_ADDRESS_MAX_CHARS } from './wire.js';
2
3
  const HOST_ORIGIN = 'https://house.invalid';
3
4
  function sameOriginPath(value) {
@@ -13,10 +14,12 @@ function sameOriginPath(value) {
13
14
  export function createHouseBridgeTestHost(options = {}) {
14
15
  const navigations = [];
15
16
  const ceremonies = [];
17
+ const consents = [];
16
18
  const listeners = new Set();
17
19
  const queued = [];
18
20
  let started = false;
19
- let address = sameOriginPath(options.address ?? '/') ?? '/';
21
+ let address = appAddressOf('/', options.address ?? '/');
22
+ let status = options.status ?? null;
20
23
  function deliver(data, ports = []) {
21
24
  const event = { data, ports };
22
25
  if (!started) {
@@ -52,6 +55,18 @@ export function createHouseBridgeTestHost(options = {}) {
52
55
  }
53
56
  ceremonies.push(target);
54
57
  }
58
+ function publish(next) {
59
+ status = next;
60
+ deliver({ house: 'status', status });
61
+ }
62
+ function consent(choice) {
63
+ if (status === null || !status.ga.measures)
64
+ return;
65
+ if (choice !== 'accept' && choice !== 'decline')
66
+ return;
67
+ consents.push(choice);
68
+ publish({ ...status, ga: { measures: true, choice } });
69
+ }
55
70
  function receive(message) {
56
71
  const data = message;
57
72
  if (data === null || typeof data !== 'object')
@@ -62,6 +77,9 @@ export function createHouseBridgeTestHost(options = {}) {
62
77
  else if (data.house === 'ceremony') {
63
78
  ceremony(data.path);
64
79
  }
80
+ else if (data.house === 'ga/consent') {
81
+ consent(data.choice);
82
+ }
65
83
  else if (data.house === 'attachment-read') {
66
84
  const asked = data;
67
85
  const image = options.images?.find((entry) => entry.room === asked.room && entry.attachment === asked.attachment);
@@ -97,24 +115,21 @@ export function createHouseBridgeTestHost(options = {}) {
97
115
  });
98
116
  },
99
117
  };
100
- if (options.status !== undefined)
101
- deliver({ house: 'status', status: options.status });
118
+ if (status !== null)
119
+ deliver({ house: 'status', status });
102
120
  deliver({ house: 'address', address, navigated: false });
103
121
  return {
104
122
  port: Promise.resolve(port),
105
123
  navigations,
106
124
  ceremonies,
125
+ consents,
107
126
  address() {
108
127
  return address;
109
128
  },
110
129
  open(asked) {
111
- const target = sameOriginPath(asked);
112
- if (target !== null)
113
- arrive(target, false);
114
- },
115
- publish(status) {
116
- deliver({ house: 'status', status });
130
+ arrive(appAddressOf('/', asked), false);
117
131
  },
132
+ publish,
118
133
  attachRuntime(runtime) {
119
134
  deliver({ house: 'runtime' }, [runtime]);
120
135
  },
@@ -16,6 +16,11 @@ export interface HouseCredentialState {
16
16
  readonly state: 'none' | 'held' | 'rejected';
17
17
  readonly expires_at: number | null;
18
18
  }
19
+ export type HouseGaChoice = 'accept' | 'decline';
20
+ export interface HouseGa {
21
+ readonly measures: boolean;
22
+ readonly choice: HouseGaChoice | null;
23
+ }
19
24
  export interface HouseStatus {
20
25
  readonly app: string;
21
26
  readonly runtime: string;
@@ -26,6 +31,7 @@ export interface HouseStatus {
26
31
  } | null;
27
32
  readonly loss: string | null;
28
33
  readonly release: unknown;
34
+ readonly ga: HouseGa;
29
35
  }
30
36
  export type HouseNavigationMode = 'push' | 'replace';
31
37
  export declare const HOUSE_ADDRESS_MAX_CHARS = 1000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/app-bridge",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "Renderer-side client of the agents.house App bridge: navigation, ceremonies, status and Site image reads, with an in-page test host.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -10,17 +10,19 @@
10
10
  },
11
11
  "exports": {
12
12
  ".": {
13
- "types": "./index.d.ts",
14
- "default": "./index.js"
13
+ "types": "./client/src/index.d.ts",
14
+ "default": "./client/src/index.js"
15
15
  },
16
16
  "./test-host": {
17
- "types": "./test-host.d.ts",
18
- "default": "./test-host.js"
17
+ "types": "./client/src/test-host.d.ts",
18
+ "default": "./client/src/test-host.js"
19
19
  }
20
20
  },
21
21
  "files": [
22
- "*.js",
23
- "*.d.ts",
22
+ "client/src/*.js",
23
+ "client/src/*.d.ts",
24
+ "src/apps/remainder.js",
25
+ "src/apps/remainder.d.ts",
24
26
  "README.md"
25
27
  ]
26
28
  }
@@ -0,0 +1,2 @@
1
+ export declare function appAddressOf(root: string, address: string): string;
2
+ export declare function shellAddressOf(root: string, appAddress: string): string;
@@ -0,0 +1,16 @@
1
+ function split(address) {
2
+ const mark = address.search(/[?#]/u);
3
+ return mark < 0
4
+ ? { path: address, rest: '' }
5
+ : { path: address.slice(0, mark), rest: address.slice(mark) };
6
+ }
7
+ export function appAddressOf(root, address) {
8
+ const { path, rest } = split(address);
9
+ const below = root === '/' ? path : path.slice(root.length);
10
+ return `${below.startsWith('/') ? below : `/${below}`}${rest}`;
11
+ }
12
+ export function shellAddressOf(root, appAddress) {
13
+ const { path, rest } = split(appAddress);
14
+ const below = path === '/' ? '' : path;
15
+ return `${root === '/' ? '' : root}${below}${rest}` || '/';
16
+ }
File without changes
File without changes