@thatopen/services 0.13.0 → 0.15.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/built-in/index.d.ts +73 -63
- package/dist/core/channel.d.ts +198 -0
- package/dist/core/channel.test.d.ts +7 -0
- package/dist/core/examples/channel.d.ts +31 -0
- package/dist/core/platform-client.d.ts +26 -0
- package/dist/index.cjs.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +837 -480
- package/docs/client/paths.json +4 -0
- package/package.json +1 -1
- package/src/built-in/ProjectManager/example.ts +4 -43
- package/src/built-in/index.ts +73 -63
package/dist/built-in/index.d.ts
CHANGED
|
@@ -1585,19 +1585,21 @@ export interface OriginData {
|
|
|
1585
1585
|
height: number;
|
|
1586
1586
|
rotation: number;
|
|
1587
1587
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1588
|
+
/** Project-wide coordinate reference system + vertical datum config, shared by
|
|
1589
|
+
* every asset type that needs to decode raw projected coordinates (point
|
|
1590
|
+
* clouds, BIM) — see automatic-file-derived-georeferencing.md
|
|
1591
|
+
* (VisorUnificadoAqualia/docs/adrs). One value per project, not per asset:
|
|
1592
|
+
* every file at the same real-world site shares the same CRS/geoid. */
|
|
1593
|
+
export interface CrsData {
|
|
1594
|
+
/** EPSG code of the project's projected CRS (e.g. 25830 for ETRS89/UTM 30N).
|
|
1595
|
+
* EPSG is a global registry, not region-specific — every project has an
|
|
1596
|
+
* applicable code, most commonly a standard UTM zone. */
|
|
1597
|
+
epsgCode: number;
|
|
1598
|
+
/** Geoid undulation in meters at the project's origin (orthometric ->
|
|
1599
|
+
* ellipsoidal height correction). Sampled once from a geoid model
|
|
1600
|
+
* (client-supplied, or a global EGM2008 fallback) — varies negligibly
|
|
1601
|
+
* across a single project's extent, so one scalar per project suffices. */
|
|
1602
|
+
geoidUndulation: number;
|
|
1601
1603
|
}
|
|
1602
1604
|
export type AssetCoordinateType = "splat" | "ifc" | "point-cloud" | (string & {});
|
|
1603
1605
|
export interface AssetCoordinate {
|
|
@@ -1653,7 +1655,7 @@ export interface ProjectManagerData {
|
|
|
1653
1655
|
namespaces: Record<ProjectNamespaceKey, ProjectNamespace>;
|
|
1654
1656
|
entities: {
|
|
1655
1657
|
origin: ProjectEntity<OriginData>;
|
|
1656
|
-
|
|
1658
|
+
crs: ProjectEntity<CrsData>;
|
|
1657
1659
|
assetCoordinates: ProjectEntity<AssetCoordinate[]>;
|
|
1658
1660
|
graphicsSettings: ProjectEntity<GraphicsSettingsData>;
|
|
1659
1661
|
};
|
|
@@ -1663,19 +1665,15 @@ export interface SingletonEntity<T> {
|
|
|
1663
1665
|
set(data: T): Promise<void>;
|
|
1664
1666
|
update(partial: Partial<T>): Promise<void>;
|
|
1665
1667
|
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
* isn't in `sites` — this is not an upsert; use `createSite` to add one. */
|
|
1676
|
-
setSite(siteId: string, site: BimSite): Promise<void>;
|
|
1677
|
-
setDefaultSite(siteId: string): Promise<void>;
|
|
1678
|
-
removeSite(siteId: string): Promise<void>;
|
|
1668
|
+
/**
|
|
1669
|
+
* A singleton entity that also announces its own changes — for values other
|
|
1670
|
+
* code needs to react to live (recompute/reposition something) rather than
|
|
1671
|
+
* just read on demand. `onChange` fires with the new value on every
|
|
1672
|
+
* `set`/`update`, regardless of whether that change has been persisted yet
|
|
1673
|
+
* (see `ProjectManager.dirty`/`onDirtyChange`).
|
|
1674
|
+
*/
|
|
1675
|
+
export interface ReactiveSingletonEntity<T> extends SingletonEntity<T> {
|
|
1676
|
+
onChange: import('@thatopen/components').Event<T>;
|
|
1679
1677
|
}
|
|
1680
1678
|
export interface AssetCoordinatesEntity {
|
|
1681
1679
|
getAll(): AssetCoordinate[];
|
|
@@ -1698,18 +1696,10 @@ export interface AssetCoordinatesEntity {
|
|
|
1698
1696
|
* Schema shape (see `src/types.ts` for the full rationale):
|
|
1699
1697
|
* - `namespaces`/`entities` are maps keyed by a fixed, readable slug — a
|
|
1700
1698
|
* closed set this code defines, never created at runtime.
|
|
1701
|
-
* - `
|
|
1702
|
-
*
|
|
1703
|
-
* - `origin`/`graphicsSettings` are singletons; `sites`/`assetCoordinates`
|
|
1704
|
-
* are collections.
|
|
1699
|
+
* - `origin`/`crs`/`graphicsSettings` are singletons; `assetCoordinates` is
|
|
1700
|
+
* a collection.
|
|
1705
1701
|
* - `entities[id]` is a uniform envelope: `namespaceId`, mandatory `label`,
|
|
1706
1702
|
* optional `description` (omitted, not stored empty), free-form `data`.
|
|
1707
|
-
*
|
|
1708
|
-
* Id generation rule: whenever a collection needs an opaque id (today just
|
|
1709
|
-
* `sites`, potentially more collections later), `ProjectManager` generates it
|
|
1710
|
-
* internally (`crypto.randomUUID()`, no prefix) — callers never invent their
|
|
1711
|
-
* own. `createSite` is the current example; follow the same pattern for any
|
|
1712
|
-
* future `create*` method.
|
|
1713
1703
|
*/
|
|
1714
1704
|
declare class _ProjectManager extends OBC.Component {
|
|
1715
1705
|
static readonly uuid: "6f3a9c2e-8b1d-4e6a-9c3f-1d7b5a8e2c4f";
|
|
@@ -1720,6 +1710,31 @@ declare class _ProjectManager extends OBC.Component {
|
|
|
1720
1710
|
private _loadingData;
|
|
1721
1711
|
readonly onSaveStart: OBC.Event<void>;
|
|
1722
1712
|
readonly onSaveComplete: OBC.Event<boolean>;
|
|
1713
|
+
/**
|
|
1714
|
+
* Entity ids (e.g. `"origin"`) with changes not yet persisted. Most entities
|
|
1715
|
+
* auto-save (see `_scheduleSave`) so they're only ever dirty for the instant
|
|
1716
|
+
* before their debounce fires — `origin` is the exception: its `set`/`update`
|
|
1717
|
+
* mark it dirty WITHOUT scheduling a save (see `origin` below), so a caller
|
|
1718
|
+
* driving it continuously (e.g. a drag gizmo) can update it every frame
|
|
1719
|
+
* without spamming the network, and a UI can surface "unsaved changes" +
|
|
1720
|
+
* call `flush()` once the user explicitly confirms.
|
|
1721
|
+
*
|
|
1722
|
+
* KNOWN GAP: `_doSave()` persists the whole `_data` blob (one JSON file for
|
|
1723
|
+
* every entity), so an auto-saving entity's save will also flush out
|
|
1724
|
+
* whatever `origin` currently holds in memory, even if the user hasn't
|
|
1725
|
+
* confirmed it yet — there's no separate "committed vs. draft" copy of
|
|
1726
|
+
* `origin`'s data. Accepted for now as a v1 gap (same spirit as this
|
|
1727
|
+
* package's other documented v1 gaps, see the ProjectManager ADR); revisit
|
|
1728
|
+
* if that coincidence turns out to matter in practice.
|
|
1729
|
+
*/
|
|
1730
|
+
private _dirtyEntities;
|
|
1731
|
+
/** True while any entity has unsaved changes. */
|
|
1732
|
+
get dirty(): boolean;
|
|
1733
|
+
/** Ids of entities with unsaved changes right now. */
|
|
1734
|
+
get dirtyEntities(): string[];
|
|
1735
|
+
/** Fires with the current `dirtyEntities` list every time it changes. */
|
|
1736
|
+
readonly onDirtyChange: OBC.Event<string[]>;
|
|
1737
|
+
private _markDirty;
|
|
1723
1738
|
private _client?;
|
|
1724
1739
|
private _fileId?;
|
|
1725
1740
|
private _data;
|
|
@@ -1728,15 +1743,26 @@ declare class _ProjectManager extends OBC.Component {
|
|
|
1728
1743
|
private _scheduleSave;
|
|
1729
1744
|
private _queueSave;
|
|
1730
1745
|
private _doSave;
|
|
1731
|
-
/**
|
|
1746
|
+
/**
|
|
1747
|
+
* Cancels the pending debounce (if any) and runs the save immediately.
|
|
1748
|
+
* Also the only way to persist an entity that marks itself dirty WITHOUT
|
|
1749
|
+
* auto-scheduling (currently just `origin`) — call this once the user
|
|
1750
|
+
* explicitly confirms. Awaits completion.
|
|
1751
|
+
*/
|
|
1732
1752
|
flush(): Promise<void>;
|
|
1733
1753
|
init(client: PlatformClient): Promise<void>;
|
|
1734
|
-
/**
|
|
1735
|
-
*
|
|
1736
|
-
|
|
1737
|
-
|
|
1754
|
+
/**
|
|
1755
|
+
* NOT auto-saving, unlike every other entity below — `set`/`update` mark it
|
|
1756
|
+
* dirty and fire `onChange` immediately, but leave persisting up to an
|
|
1757
|
+
* explicit `flush()` call. This is what lets a continuous interaction (e.g.
|
|
1758
|
+
* a drag gizmo) update `origin` every frame — live for anyone listening to
|
|
1759
|
+
* `onChange` — without writing to the network on every tick, while still
|
|
1760
|
+
* surfacing "there's an unconfirmed change" via `dirty`/`onDirtyChange` for
|
|
1761
|
+
* a UI to gate behind an explicit save action.
|
|
1762
|
+
*/
|
|
1763
|
+
origin: ReactiveSingletonEntity<OriginData>;
|
|
1764
|
+
crs: ReactiveSingletonEntity<CrsData>;
|
|
1738
1765
|
graphicsSettings: SingletonEntity<GraphicsSettingsData>;
|
|
1739
|
-
bimCoordinates: BimCoordinatesEntity;
|
|
1740
1766
|
assetCoordinates: AssetCoordinatesEntity;
|
|
1741
1767
|
getNamespaces(): Record<ProjectNamespaceKey, ProjectNamespace>;
|
|
1742
1768
|
getNamespace(key: ProjectNamespaceKey): Record<string, unknown>;
|
|
@@ -1755,18 +1781,10 @@ declare class _ProjectManager extends OBC.Component {
|
|
|
1755
1781
|
* Schema shape (see `src/types.ts` for the full rationale):
|
|
1756
1782
|
* - `namespaces`/`entities` are maps keyed by a fixed, readable slug — a
|
|
1757
1783
|
* closed set this code defines, never created at runtime.
|
|
1758
|
-
* - `
|
|
1759
|
-
*
|
|
1760
|
-
* - `origin`/`graphicsSettings` are singletons; `sites`/`assetCoordinates`
|
|
1761
|
-
* are collections.
|
|
1784
|
+
* - `origin`/`crs`/`graphicsSettings` are singletons; `assetCoordinates` is
|
|
1785
|
+
* a collection.
|
|
1762
1786
|
* - `entities[id]` is a uniform envelope: `namespaceId`, mandatory `label`,
|
|
1763
1787
|
* optional `description` (omitted, not stored empty), free-form `data`.
|
|
1764
|
-
*
|
|
1765
|
-
* Id generation rule: whenever a collection needs an opaque id (today just
|
|
1766
|
-
* `sites`, potentially more collections later), `ProjectManager` generates it
|
|
1767
|
-
* internally (`crypto.randomUUID()`, no prefix) — callers never invent their
|
|
1768
|
-
* own. `createSite` is the current example; follow the same pattern for any
|
|
1769
|
-
* future `create*` method.
|
|
1770
1788
|
*/
|
|
1771
1789
|
export type ProjectManager = InstanceType<typeof _ProjectManager>;
|
|
1772
1790
|
/**
|
|
@@ -1783,18 +1801,10 @@ export type ProjectManager = InstanceType<typeof _ProjectManager>;
|
|
|
1783
1801
|
* Schema shape (see `src/types.ts` for the full rationale):
|
|
1784
1802
|
* - `namespaces`/`entities` are maps keyed by a fixed, readable slug — a
|
|
1785
1803
|
* closed set this code defines, never created at runtime.
|
|
1786
|
-
* - `
|
|
1787
|
-
*
|
|
1788
|
-
* - `origin`/`graphicsSettings` are singletons; `sites`/`assetCoordinates`
|
|
1789
|
-
* are collections.
|
|
1804
|
+
* - `origin`/`crs`/`graphicsSettings` are singletons; `assetCoordinates` is
|
|
1805
|
+
* a collection.
|
|
1790
1806
|
* - `entities[id]` is a uniform envelope: `namespaceId`, mandatory `label`,
|
|
1791
1807
|
* optional `description` (omitted, not stored empty), free-form `data`.
|
|
1792
|
-
*
|
|
1793
|
-
* Id generation rule: whenever a collection needs an opaque id (today just
|
|
1794
|
-
* `sites`, potentially more collections later), `ProjectManager` generates it
|
|
1795
|
-
* internally (`crypto.randomUUID()`, no prefix) — callers never invent their
|
|
1796
|
-
* own. `createSite` is the current example; follow the same pattern for any
|
|
1797
|
-
* future `create*` method.
|
|
1798
1808
|
*/
|
|
1799
1809
|
export declare const ProjectManager: typeof _ProjectManager & {
|
|
1800
1810
|
uuid: "6f3a9c2e-8b1d-4e6a-9c3f-1d7b5a8e2c4f";
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app side of the platform channel: rooms an app can join, and the
|
|
3
|
+
* messages it exchanges through them.
|
|
4
|
+
*
|
|
5
|
+
* The app never holds a socket. The platform shell, outside the iframe, owns
|
|
6
|
+
* the connection and relays over `postMessage`; this module is the typed
|
|
7
|
+
* layer on top of that contract, reached as {@link PlatformClient.channel}.
|
|
8
|
+
*/
|
|
9
|
+
/** What a command looks like: the payload it carries and the reply it expects. */
|
|
10
|
+
export interface ChannelCommandShape {
|
|
11
|
+
payload?: unknown;
|
|
12
|
+
reply?: unknown;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The default command map, when none is given: every command name is allowed
|
|
16
|
+
* and nothing is typed. Give `external<Commands>()` your own map to get
|
|
17
|
+
* payloads and replies checked at compile time.
|
|
18
|
+
*/
|
|
19
|
+
export type AnyChannelCommands = Record<string, ChannelCommandShape>;
|
|
20
|
+
/** The default event map for {@link CollabRoom}: any name, any payload. */
|
|
21
|
+
export type AnyChannelEvents = Record<string, unknown>;
|
|
22
|
+
type PayloadOf<T> = 'payload' extends keyof T ? T['payload'] : undefined;
|
|
23
|
+
type ReplyOf<T> = 'reply' extends keyof T ? T['reply'] : void;
|
|
24
|
+
/** Someone else on the other end of a room. `kind` is set for external channels. */
|
|
25
|
+
export interface ChannelPeer {
|
|
26
|
+
from: string;
|
|
27
|
+
kind?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Who sent a message, and when. */
|
|
30
|
+
export interface ChannelMessageMeta {
|
|
31
|
+
/** The sender's connection id, stamped by the platform. */
|
|
32
|
+
from: string;
|
|
33
|
+
at: number;
|
|
34
|
+
/** Present when the sender expects an answer. */
|
|
35
|
+
requestId?: string;
|
|
36
|
+
/** For an external sender that declared one: `'mcp'`, `'cli'`, `'revit'`… */
|
|
37
|
+
kind?: string;
|
|
38
|
+
}
|
|
39
|
+
/** How the client talks to the shell. Replaceable, so it can be tested. */
|
|
40
|
+
export interface ChannelTransport {
|
|
41
|
+
post(message: Record<string, unknown>): void;
|
|
42
|
+
listen(handler: (data: unknown) => void): () => void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Talks to the platform shell through `window.parent`. Only messages whose
|
|
46
|
+
* `source` is the parent window are accepted — the same check the shell makes
|
|
47
|
+
* in the other direction — so another frame on the page cannot inject messages
|
|
48
|
+
* into the app.
|
|
49
|
+
*/
|
|
50
|
+
export declare function windowTransport(): ChannelTransport;
|
|
51
|
+
/** A message as the platform delivers it to the app. */
|
|
52
|
+
export interface ChannelIncomingMessage {
|
|
53
|
+
type: string;
|
|
54
|
+
requestId?: string;
|
|
55
|
+
payload?: unknown;
|
|
56
|
+
at: number;
|
|
57
|
+
from: string;
|
|
58
|
+
scope?: 'collab' | 'external';
|
|
59
|
+
kind?: string;
|
|
60
|
+
}
|
|
61
|
+
/** What a room did with a message. */
|
|
62
|
+
export type ChannelOutcome = {
|
|
63
|
+
handled: false;
|
|
64
|
+
} | {
|
|
65
|
+
handled: true;
|
|
66
|
+
value?: unknown;
|
|
67
|
+
error?: string;
|
|
68
|
+
};
|
|
69
|
+
/** What every room shares: membership, peers and presence. */
|
|
70
|
+
export declare abstract class ChannelRoom {
|
|
71
|
+
#private;
|
|
72
|
+
private readonly transport;
|
|
73
|
+
private readonly controls;
|
|
74
|
+
constructor(transport: ChannelTransport, controls: {
|
|
75
|
+
join: Record<string, unknown>;
|
|
76
|
+
leave: Record<string, unknown>;
|
|
77
|
+
});
|
|
78
|
+
/** Whether the platform has confirmed this tab is in the room. */
|
|
79
|
+
get joined(): boolean;
|
|
80
|
+
/** Who else is in the room right now, as the platform last reported. */
|
|
81
|
+
get peers(): readonly ChannelPeer[];
|
|
82
|
+
/**
|
|
83
|
+
* Enters the room. Resolves when the platform confirms; rejects if it
|
|
84
|
+
* refuses or does not answer in time. Joining twice is harmless.
|
|
85
|
+
*/
|
|
86
|
+
join(): Promise<void>;
|
|
87
|
+
/** Leaves the room. Handlers stay registered, so `join()` resumes them. */
|
|
88
|
+
leave(): Promise<void>;
|
|
89
|
+
/** Fires when `joined` or `peers` changes. Returns a function that stops it. */
|
|
90
|
+
onChange(listener: () => void): () => void;
|
|
91
|
+
onPeerJoined(listener: (peer: ChannelPeer) => void): () => void;
|
|
92
|
+
onPeerLeft(listener: (peer: ChannelPeer) => void): () => void;
|
|
93
|
+
/** @internal Does a message arriving with this scope/kind belong to this room? */
|
|
94
|
+
abstract accepts(scope: string | undefined, kind: string | undefined): boolean;
|
|
95
|
+
/** @internal */
|
|
96
|
+
abstract handle(message: ChannelIncomingMessage): Promise<ChannelOutcome>;
|
|
97
|
+
/** @internal */
|
|
98
|
+
applyJoined(): void;
|
|
99
|
+
/** @internal */
|
|
100
|
+
applyLeft(): void;
|
|
101
|
+
/** @internal */
|
|
102
|
+
applyPeers(peers: ChannelPeer[]): void;
|
|
103
|
+
/** @internal */
|
|
104
|
+
applyPeerJoined(peer: ChannelPeer): void;
|
|
105
|
+
/** @internal */
|
|
106
|
+
applyPeerLeft(from: string): void;
|
|
107
|
+
/** @internal */
|
|
108
|
+
failPending(error: Error): boolean;
|
|
109
|
+
protected post(message: Record<string, unknown>): void;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The collaboration room: every user's tab of this app that joined it, across
|
|
113
|
+
* accounts. Messages here are events — nobody replies.
|
|
114
|
+
*
|
|
115
|
+
* `E` maps each event name to its payload, so `send` and `on` are checked.
|
|
116
|
+
*/
|
|
117
|
+
export declare class CollabRoom<E extends object = AnyChannelEvents> extends ChannelRoom {
|
|
118
|
+
#private;
|
|
119
|
+
/** @internal Use `client.channel.collab()`. */
|
|
120
|
+
constructor(transport: ChannelTransport);
|
|
121
|
+
/**
|
|
122
|
+
* Listens for an event other tabs send. Any number of listeners per event.
|
|
123
|
+
* @returns a function that removes the listener.
|
|
124
|
+
*/
|
|
125
|
+
on<K extends keyof E & string>(type: K, listener: (payload: E[K], meta: ChannelMessageMeta) => void): () => void;
|
|
126
|
+
/**
|
|
127
|
+
* Sends an event to the other tabs in the room. Does nothing while this tab
|
|
128
|
+
* is not in it, matching what the platform does with a message from outside.
|
|
129
|
+
*/
|
|
130
|
+
send<K extends keyof E & string>(type: K, payload: E[K]): void;
|
|
131
|
+
accepts(scope: string | undefined): boolean;
|
|
132
|
+
handle(message: ChannelIncomingMessage): Promise<ChannelOutcome>;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Commands from this account's external tools — a CLI, an MCP server, a Revit
|
|
136
|
+
* plugin — of one `kind`, or of every kind when none is given.
|
|
137
|
+
*
|
|
138
|
+
* `C` maps each command name to `{ payload, reply }`, so a handler's argument
|
|
139
|
+
* and return value are checked. A command without `reply` is a notification.
|
|
140
|
+
*/
|
|
141
|
+
export declare class ExternalRoom<C extends {
|
|
142
|
+
[K in keyof C]: ChannelCommandShape;
|
|
143
|
+
} = AnyChannelCommands> extends ChannelRoom {
|
|
144
|
+
#private;
|
|
145
|
+
readonly kind?: string | undefined;
|
|
146
|
+
/** @internal Use `client.channel.external(kind)`. */
|
|
147
|
+
constructor(transport: ChannelTransport, kind?: string | undefined);
|
|
148
|
+
/**
|
|
149
|
+
* Handles a command. When the sender asked for an answer, what you return
|
|
150
|
+
* is sent back to it; if you throw, it gets an error instead of waiting for
|
|
151
|
+
* a timeout. When it did not ask (a notification), the return value is
|
|
152
|
+
* ignored.
|
|
153
|
+
*
|
|
154
|
+
* One handler per command: a second one for the same name throws, since two
|
|
155
|
+
* would both try to answer.
|
|
156
|
+
* @returns a function that removes the handler.
|
|
157
|
+
*/
|
|
158
|
+
on<K extends keyof C & string>(type: K, handler: (payload: PayloadOf<C[K]>, meta: ChannelMessageMeta) => ReplyOf<C[K]> | Promise<ReplyOf<C[K]>>): () => void;
|
|
159
|
+
accepts(scope: string | undefined, kind: string | undefined): boolean;
|
|
160
|
+
/** @internal Whether this room has any handler at all. */
|
|
161
|
+
get hasHandlers(): boolean;
|
|
162
|
+
handle(message: ChannelIncomingMessage): Promise<ChannelOutcome>;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Typed access to the platform channel from inside an app: a collaboration
|
|
166
|
+
* room shared with other users, and rooms for commands from external tools.
|
|
167
|
+
* Reach it as `client.channel`.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* type McpCommands = {
|
|
172
|
+
* 'select-by-category': { payload: { category: string }; reply: { count: number } };
|
|
173
|
+
* };
|
|
174
|
+
*
|
|
175
|
+
* const mcp = client.channel.external<McpCommands>('mcp');
|
|
176
|
+
* mcp.on('select-by-category', async ({ category }) => ({
|
|
177
|
+
* count: await selectByCategory(category),
|
|
178
|
+
* }));
|
|
179
|
+
* await mcp.join();
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export declare class ChannelClient {
|
|
183
|
+
#private;
|
|
184
|
+
constructor(transport: ChannelTransport);
|
|
185
|
+
/** The collaboration room for this app. Always the same instance. */
|
|
186
|
+
collab<E extends object = AnyChannelEvents>(): CollabRoom<E>;
|
|
187
|
+
/**
|
|
188
|
+
* The room for commands from this account's external tools of one `kind`
|
|
189
|
+
* (`'mcp'`, `'cli'`, `'revit'`…), or from any of them when `kind` is omitted.
|
|
190
|
+
* Always the same instance for the same kind.
|
|
191
|
+
*/
|
|
192
|
+
external<C extends {
|
|
193
|
+
[K in keyof C]: ChannelCommandShape;
|
|
194
|
+
} = AnyChannelCommands>(kind?: string): ExternalRoom<C>;
|
|
195
|
+
/** Stops listening. For tests and teardown; an app normally never calls it. */
|
|
196
|
+
dispose(): void;
|
|
197
|
+
}
|
|
198
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PlatformClient } from '../platform-client';
|
|
2
|
+
|
|
3
|
+
type McpCommands = {
|
|
4
|
+
'select-by-category': {
|
|
5
|
+
payload: {
|
|
6
|
+
category: string;
|
|
7
|
+
};
|
|
8
|
+
reply: {
|
|
9
|
+
count: number;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
'get-loaded-models': {
|
|
13
|
+
payload: undefined;
|
|
14
|
+
reply: {
|
|
15
|
+
models: string[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type CollabEvents = {
|
|
20
|
+
'cursor-position': {
|
|
21
|
+
userName: string;
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
z: number;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export declare function setupChannel(client: PlatformClient): Promise<{
|
|
28
|
+
mcp: import('../channel').ExternalRoom<McpCommands>;
|
|
29
|
+
collab: import('../channel').CollabRoom<CollabEvents>;
|
|
30
|
+
}>;
|
|
31
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EngineServicesClient, EngineServicesClientProps } from './client';
|
|
2
2
|
import { Project, ProjectData } from '../types/projects';
|
|
3
|
+
import { ChannelClient } from './channel';
|
|
3
4
|
import { MarkNotificationsReadResultDto, NotificationPageDto, NotificationSubscriptionView } from '../types/notifications';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -103,6 +104,31 @@ export declare class PlatformClient extends EngineServicesClient {
|
|
|
103
104
|
* carries a fresh JWT on every navigation.
|
|
104
105
|
*/
|
|
105
106
|
static fromPlatformContext(props?: Omit<EngineServicesClientProps, 'useBearer'>): PlatformClient;
|
|
107
|
+
/**
|
|
108
|
+
* The platform channel, from inside an app: a collaboration room shared with
|
|
109
|
+
* other users of the app, and rooms for commands from external tools (a CLI,
|
|
110
|
+
* an MCP server, a Revit plugin). Typed, and built on the `postMessage`
|
|
111
|
+
* contract with the platform shell — the app never opens a socket.
|
|
112
|
+
*
|
|
113
|
+
* Only available in an app running in the platform's iframe; reading it
|
|
114
|
+
* anywhere else throws. Created on first use.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* type McpCommands = {
|
|
119
|
+
* 'select-by-category': { payload: { category: string }; reply: { count: number } };
|
|
120
|
+
* };
|
|
121
|
+
*
|
|
122
|
+
* const mcp = client.channel.external<McpCommands>('mcp');
|
|
123
|
+
* mcp.on('select-by-category', async ({ category }) => ({ count: 3 }));
|
|
124
|
+
* await mcp.join();
|
|
125
|
+
*
|
|
126
|
+
* const collab = client.channel.collab();
|
|
127
|
+
* collab.onPeerLeft(({ from }) => removeAvatar(from));
|
|
128
|
+
* await collab.join();
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
get channel(): ChannelClient;
|
|
106
132
|
/**
|
|
107
133
|
* Gets a project by ID. JWT-only — lives here because
|
|
108
134
|
* `GET /project/:id` is guarded by `AccountActiveGuard + ProjectAccessGuard`.
|