@ethisyscore/extension-runtime 1.26.0 → 1.27.1

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.
@@ -6,8 +6,186 @@ import { SduiNode, RenderMode } from '@ethisyscore/protocol';
6
6
  import { RemoteConnection } from '@remote-dom/core';
7
7
  import '../bridge-envelopes-BRKGSiSC.cjs';
8
8
 
9
+ /**
10
+ * A single client-push event delivered from the host to a plugin surface. The host
11
+ * relays the plugin backend's `IClientPushPublisher` events over its realtime
12
+ * channel (SignalR); the transport envelope's extension identity is bound by the
13
+ * host at mount time, so the plugin sees only the event body.
14
+ *
15
+ * SCOPE + ORDERING: which channel/user/group an event concerns is carried INSIDE
16
+ * `payloadJson` by the emitting plugin — the transport envelope intentionally has no
17
+ * group field. Consumers therefore demultiplex + order by their own payload fields
18
+ * (e.g. a per-channel sequence in the payload), NOT by {@link eventSequence}, which
19
+ * is per-group at the host and would produce false gaps when multiple groups
20
+ * multiplex over one connection.
21
+ */
22
+ interface ClientPushEvent {
23
+ /** Plugin-defined discriminator, e.g. `"chatMessageReceived"`. */
24
+ eventType: string;
25
+ /** Raw JSON payload authored by the plugin backend. */
26
+ payloadJson: string;
27
+ /**
28
+ * Host per-group monotonic sequence. Advisory only — do NOT use for
29
+ * cross-group gap detection (see the scope note above).
30
+ */
31
+ eventSequence: number;
32
+ }
33
+ interface ClientPushSubscribeOptions {
34
+ /**
35
+ * Opaque group names to enrol in (e.g. `"chat:channel:{id}"`). The host
36
+ * authorises each subscription via the plugin's `authorize-subscription` tool
37
+ * and enforces org/extension isolation — a plugin cannot subscribe outside its
38
+ * own extension + organisation.
39
+ */
40
+ groups: string[];
41
+ /** Called for each delivered (non-resync) event for the subscribed groups. */
42
+ onEvent: (event: ClientPushEvent) => void;
43
+ /**
44
+ * Called when the host signals a gap/resync for the subscribed groups (a
45
+ * dropped-event backpressure signal, or a reconnect). The consumer should
46
+ * re-fetch authoritative state (e.g. a delta/cold-load) rather than trusting
47
+ * incremental events.
48
+ */
49
+ onResync?: () => void;
50
+ }
51
+ /**
52
+ * Host-provided channel for realtime server-push. The channel is already scoped to
53
+ * the mounted surface's extension + organisation (bound by the host from the trusted
54
+ * mount descriptor — a plugin CANNOT widen it), so {@link subscribe} takes only
55
+ * opaque group names and returns an unsubscribe function.
56
+ */
57
+ interface ClientPushChannel {
58
+ subscribe(options: ClientPushSubscribeOptions): () => void;
59
+ }
60
+ /**
61
+ * `null` = no host channel (standalone/mock, or a host that predates client-push) →
62
+ * {@link useClientPushSubscription} is inert. Provided by
63
+ * {@link ExtensionRuntimeProvider}'s optional `clientPush` prop.
64
+ */
65
+ declare const ClientPushContext: react.Context<ClientPushChannel | null>;
66
+ interface UseClientPushSubscriptionOptions {
67
+ /** Opaque groups to subscribe. Changing the SET re-subscribes; identity/order changes alone do not. */
68
+ groups: string[];
69
+ onEvent: (event: ClientPushEvent) => void;
70
+ onResync?: () => void;
71
+ /** Gate the subscription (e.g. until an id is known). Default `true`. */
72
+ enabled?: boolean;
73
+ }
74
+ /**
75
+ * Subscribe a plugin surface to host client-push events for `groups`.
76
+ *
77
+ * Inert (no-op) when no host channel is present (standalone/mock), when `enabled` is
78
+ * false, or when `groups` is empty. Re-subscribes when the group set changes and
79
+ * unsubscribes on unmount. Callback identities are held in refs, so passing new
80
+ * inline `onEvent`/`onResync` closures every render does NOT churn the subscription.
81
+ */
82
+ declare function useClientPushSubscription(options: UseClientPushSubscriptionOptions): void;
83
+
84
+ /** Current-user identity for a mounted plugin surface, bound by the host. */
85
+ interface HostIdentityUser {
86
+ id: string;
87
+ firstName: string;
88
+ lastName: string;
89
+ fullName: string;
90
+ isExternal: boolean;
91
+ }
92
+ /** One resolved permission grant (bitMask over the plugin's PermissionMask bits). */
93
+ interface HostPermission {
94
+ groupCode: string;
95
+ bitMask: number;
96
+ }
97
+ /** Host-provided identity context for the mounted surface. */
98
+ interface HostIdentity {
99
+ /** null while host auth is still loading (see isLoading) OR when unauthenticated. */
100
+ user: HostIdentityUser | null;
101
+ /** true while the host's /auth/user resolution is in flight — disambiguates loading from unauthenticated. */
102
+ isLoading: boolean;
103
+ /** The mounted surface's OWN grant only, or null when the user has no grant for this extension. */
104
+ permission: HostPermission | null;
105
+ /** The mounted surface's own extension groupCode, host-bound from the trusted manifest. */
106
+ extensionGroupCode: string;
107
+ /**
108
+ * The active organisation id for the mounted surface, or null while host auth is loading /
109
+ * unauthenticated. Host-bound from the SPA's active-organisation context. Surfaces that scope
110
+ * realtime subscriptions or org-keyed queries read this (e.g. the chat client-push gate); it is
111
+ * NOT a security token — the plugin backend derives org from the server session independently.
112
+ */
113
+ organisationId: string | null;
114
+ }
115
+ /**
116
+ * `null` = no host channel (standalone/mock, or a host that predates the identity seam) →
117
+ * {@link useHostIdentity} returns null and the plugin falls back to its deny-by-default path.
118
+ * Provided by {@link ExtensionRuntimeProvider}'s optional `identity` prop.
119
+ */
120
+ declare const HostIdentityContext: react.Context<HostIdentity | null>;
121
+ /** Returns the host identity for the mounted surface, or null when no host context is present. */
122
+ declare function useHostIdentity(): HostIdentity | null;
123
+
124
+ /**
125
+ * A host-supplied realtime subscription source for a plugin.
126
+ *
127
+ * The SDK keeps this intentionally dumb — it only calls `source.subscribe`.
128
+ * All SignalR wiring, extensionId filtering, and connection lifecycle management
129
+ * live host-side (Task 6 in coreconnect-web). This lets the SDK be tested with
130
+ * a simple fake source.
131
+ */
132
+ interface PluginRealtimeSource {
133
+ /**
134
+ * Subscribe to notifications whose `typeCode` matches the given value.
135
+ *
136
+ * @param typeCode The application-level event type code to filter on
137
+ * (e.g. `"HelpdeskTicketCreated"`). Filtering by
138
+ * extensionId is the host's responsibility.
139
+ * @param handler Called with the raw notification payload whenever a
140
+ * matching notification arrives.
141
+ * @returns An unsubscribe function. Calling it removes this handler.
142
+ */
143
+ subscribe(typeCode: string, handler: (payload: unknown) => void): () => void;
144
+ }
145
+ /**
146
+ * React context carrying the plugin's active {@link PluginRealtimeSource}.
147
+ *
148
+ * `null` is the explicit "not provided" sentinel — hooks must treat null as a
149
+ * clean no-op (dev/mock/no-connection) rather than an error.
150
+ *
151
+ * Provided by {@link ExtensionRuntimeProvider} when the host passes a
152
+ * `realtime` prop; consumed by `usePluginRealtimeSource()`.
153
+ */
154
+ declare const PluginRealtimeContext: react.Context<PluginRealtimeSource | null>;
155
+ /**
156
+ * Returns the {@link PluginRealtimeSource} from context, or `null` when none
157
+ * is wired (dev/mock environments, unit tests that only care about MCP).
158
+ *
159
+ * Hooks built on top of this (e.g. `usePluginRealtime` in `plugin-ui`) should
160
+ * skip their subscription entirely when this returns `null`.
161
+ */
162
+ declare function usePluginRealtimeSource(): PluginRealtimeSource | null;
163
+
9
164
  interface ExtensionRuntimeProviderProps {
10
165
  transport: McpTransport;
166
+ /**
167
+ * Optional host realtime channel consumed by {@link useClientPushSubscription}.
168
+ * Absent (or `null`) in standalone/mock hosts and hosts that predate client-push,
169
+ * in which case the hook is inert. The host binds this channel to the mounted
170
+ * surface's trusted extension + organisation identity.
171
+ */
172
+ clientPush?: ClientPushChannel | null;
173
+ /**
174
+ * Optional host identity/permission context consumed by {@link useHostIdentity}.
175
+ * Absent (or `null`) in standalone/mock hosts and hosts that predate this seam,
176
+ * in which case the hook is inert. The host binds this to the mounted surface's
177
+ * trusted extension identity and forwards only that extension's own grant.
178
+ */
179
+ identity?: HostIdentity | null;
180
+ /**
181
+ * Optional realtime subscription source supplied by the host.
182
+ *
183
+ * When provided, descendant components can call `usePluginRealtimeSource()`
184
+ * to obtain it and subscribe to push notifications. When omitted (dev/mock
185
+ * environments or plugins that don't need realtime), the context defaults
186
+ * to `null` and consumers no-op cleanly.
187
+ */
188
+ realtime?: PluginRealtimeSource;
11
189
  children?: ReactNode;
12
190
  }
13
191
  /**
@@ -19,7 +197,7 @@ interface ExtensionRuntimeProviderProps {
19
197
  * over the context value — useful for tests and for plugins that want to
20
198
  * shard work across multiple hosts.
21
199
  */
22
- declare function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode;
200
+ declare function ExtensionRuntimeProvider({ transport, clientPush, identity, realtime, children }: ExtensionRuntimeProviderProps): ReactNode;
23
201
 
24
202
  interface UseMcpResourceOptions {
25
203
  /**
@@ -445,4 +623,4 @@ interface UseFrontendSessionTokenResult {
445
623
  */
446
624
  declare function useFrontendSessionToken(transport: McpTransport): UseFrontendSessionTokenResult;
447
625
 
448
- export { BridgeClientContext, type CreatePortMcpTransportOptions, type CreateRemoteRootOptions, type DeclarativePluginConfig, type EthisysPluginConfig, ExtensionRuntimeProvider, type ExtensionRuntimeProviderProps, type ItemsResponse, LocalePayload, McpTransport, PortBridgeClient, type PortShim, type RemoteRoot, ThemePayload, type UseFrontendSessionTokenResult, type UseMcpQueryOptions, type UseMcpQueryResult, type UseMcpResourceOptions, type UseMcpResourceResult, type UseMcpToolOptions, type UseMcpToolResult, createPortMcpTransport, createRemoteRoot, defineDeclarativePlugin, defineEthisysPlugin, unwrapItems, useBridgeClient, useBridgeLocale, useBridgeTheme, useFrontendSessionToken, useMcpQuery, useMcpResource, useMcpTool };
626
+ export { BridgeClientContext, type ClientPushChannel, ClientPushContext, type ClientPushEvent, type ClientPushSubscribeOptions, type CreatePortMcpTransportOptions, type CreateRemoteRootOptions, type DeclarativePluginConfig, type EthisysPluginConfig, ExtensionRuntimeProvider, type ExtensionRuntimeProviderProps, type HostIdentity, HostIdentityContext, type HostIdentityUser, type HostPermission, type ItemsResponse, LocalePayload, McpTransport, PluginRealtimeContext, type PluginRealtimeSource, PortBridgeClient, type PortShim, type RemoteRoot, ThemePayload, type UseClientPushSubscriptionOptions, type UseFrontendSessionTokenResult, type UseMcpQueryOptions, type UseMcpQueryResult, type UseMcpResourceOptions, type UseMcpResourceResult, type UseMcpToolOptions, type UseMcpToolResult, createPortMcpTransport, createRemoteRoot, defineDeclarativePlugin, defineEthisysPlugin, unwrapItems, useBridgeClient, useBridgeLocale, useBridgeTheme, useClientPushSubscription, useFrontendSessionToken, useHostIdentity, useMcpQuery, useMcpResource, useMcpTool, usePluginRealtimeSource };
@@ -6,8 +6,186 @@ import { SduiNode, RenderMode } from '@ethisyscore/protocol';
6
6
  import { RemoteConnection } from '@remote-dom/core';
7
7
  import '../bridge-envelopes-BRKGSiSC.js';
8
8
 
9
+ /**
10
+ * A single client-push event delivered from the host to a plugin surface. The host
11
+ * relays the plugin backend's `IClientPushPublisher` events over its realtime
12
+ * channel (SignalR); the transport envelope's extension identity is bound by the
13
+ * host at mount time, so the plugin sees only the event body.
14
+ *
15
+ * SCOPE + ORDERING: which channel/user/group an event concerns is carried INSIDE
16
+ * `payloadJson` by the emitting plugin — the transport envelope intentionally has no
17
+ * group field. Consumers therefore demultiplex + order by their own payload fields
18
+ * (e.g. a per-channel sequence in the payload), NOT by {@link eventSequence}, which
19
+ * is per-group at the host and would produce false gaps when multiple groups
20
+ * multiplex over one connection.
21
+ */
22
+ interface ClientPushEvent {
23
+ /** Plugin-defined discriminator, e.g. `"chatMessageReceived"`. */
24
+ eventType: string;
25
+ /** Raw JSON payload authored by the plugin backend. */
26
+ payloadJson: string;
27
+ /**
28
+ * Host per-group monotonic sequence. Advisory only — do NOT use for
29
+ * cross-group gap detection (see the scope note above).
30
+ */
31
+ eventSequence: number;
32
+ }
33
+ interface ClientPushSubscribeOptions {
34
+ /**
35
+ * Opaque group names to enrol in (e.g. `"chat:channel:{id}"`). The host
36
+ * authorises each subscription via the plugin's `authorize-subscription` tool
37
+ * and enforces org/extension isolation — a plugin cannot subscribe outside its
38
+ * own extension + organisation.
39
+ */
40
+ groups: string[];
41
+ /** Called for each delivered (non-resync) event for the subscribed groups. */
42
+ onEvent: (event: ClientPushEvent) => void;
43
+ /**
44
+ * Called when the host signals a gap/resync for the subscribed groups (a
45
+ * dropped-event backpressure signal, or a reconnect). The consumer should
46
+ * re-fetch authoritative state (e.g. a delta/cold-load) rather than trusting
47
+ * incremental events.
48
+ */
49
+ onResync?: () => void;
50
+ }
51
+ /**
52
+ * Host-provided channel for realtime server-push. The channel is already scoped to
53
+ * the mounted surface's extension + organisation (bound by the host from the trusted
54
+ * mount descriptor — a plugin CANNOT widen it), so {@link subscribe} takes only
55
+ * opaque group names and returns an unsubscribe function.
56
+ */
57
+ interface ClientPushChannel {
58
+ subscribe(options: ClientPushSubscribeOptions): () => void;
59
+ }
60
+ /**
61
+ * `null` = no host channel (standalone/mock, or a host that predates client-push) →
62
+ * {@link useClientPushSubscription} is inert. Provided by
63
+ * {@link ExtensionRuntimeProvider}'s optional `clientPush` prop.
64
+ */
65
+ declare const ClientPushContext: react.Context<ClientPushChannel | null>;
66
+ interface UseClientPushSubscriptionOptions {
67
+ /** Opaque groups to subscribe. Changing the SET re-subscribes; identity/order changes alone do not. */
68
+ groups: string[];
69
+ onEvent: (event: ClientPushEvent) => void;
70
+ onResync?: () => void;
71
+ /** Gate the subscription (e.g. until an id is known). Default `true`. */
72
+ enabled?: boolean;
73
+ }
74
+ /**
75
+ * Subscribe a plugin surface to host client-push events for `groups`.
76
+ *
77
+ * Inert (no-op) when no host channel is present (standalone/mock), when `enabled` is
78
+ * false, or when `groups` is empty. Re-subscribes when the group set changes and
79
+ * unsubscribes on unmount. Callback identities are held in refs, so passing new
80
+ * inline `onEvent`/`onResync` closures every render does NOT churn the subscription.
81
+ */
82
+ declare function useClientPushSubscription(options: UseClientPushSubscriptionOptions): void;
83
+
84
+ /** Current-user identity for a mounted plugin surface, bound by the host. */
85
+ interface HostIdentityUser {
86
+ id: string;
87
+ firstName: string;
88
+ lastName: string;
89
+ fullName: string;
90
+ isExternal: boolean;
91
+ }
92
+ /** One resolved permission grant (bitMask over the plugin's PermissionMask bits). */
93
+ interface HostPermission {
94
+ groupCode: string;
95
+ bitMask: number;
96
+ }
97
+ /** Host-provided identity context for the mounted surface. */
98
+ interface HostIdentity {
99
+ /** null while host auth is still loading (see isLoading) OR when unauthenticated. */
100
+ user: HostIdentityUser | null;
101
+ /** true while the host's /auth/user resolution is in flight — disambiguates loading from unauthenticated. */
102
+ isLoading: boolean;
103
+ /** The mounted surface's OWN grant only, or null when the user has no grant for this extension. */
104
+ permission: HostPermission | null;
105
+ /** The mounted surface's own extension groupCode, host-bound from the trusted manifest. */
106
+ extensionGroupCode: string;
107
+ /**
108
+ * The active organisation id for the mounted surface, or null while host auth is loading /
109
+ * unauthenticated. Host-bound from the SPA's active-organisation context. Surfaces that scope
110
+ * realtime subscriptions or org-keyed queries read this (e.g. the chat client-push gate); it is
111
+ * NOT a security token — the plugin backend derives org from the server session independently.
112
+ */
113
+ organisationId: string | null;
114
+ }
115
+ /**
116
+ * `null` = no host channel (standalone/mock, or a host that predates the identity seam) →
117
+ * {@link useHostIdentity} returns null and the plugin falls back to its deny-by-default path.
118
+ * Provided by {@link ExtensionRuntimeProvider}'s optional `identity` prop.
119
+ */
120
+ declare const HostIdentityContext: react.Context<HostIdentity | null>;
121
+ /** Returns the host identity for the mounted surface, or null when no host context is present. */
122
+ declare function useHostIdentity(): HostIdentity | null;
123
+
124
+ /**
125
+ * A host-supplied realtime subscription source for a plugin.
126
+ *
127
+ * The SDK keeps this intentionally dumb — it only calls `source.subscribe`.
128
+ * All SignalR wiring, extensionId filtering, and connection lifecycle management
129
+ * live host-side (Task 6 in coreconnect-web). This lets the SDK be tested with
130
+ * a simple fake source.
131
+ */
132
+ interface PluginRealtimeSource {
133
+ /**
134
+ * Subscribe to notifications whose `typeCode` matches the given value.
135
+ *
136
+ * @param typeCode The application-level event type code to filter on
137
+ * (e.g. `"HelpdeskTicketCreated"`). Filtering by
138
+ * extensionId is the host's responsibility.
139
+ * @param handler Called with the raw notification payload whenever a
140
+ * matching notification arrives.
141
+ * @returns An unsubscribe function. Calling it removes this handler.
142
+ */
143
+ subscribe(typeCode: string, handler: (payload: unknown) => void): () => void;
144
+ }
145
+ /**
146
+ * React context carrying the plugin's active {@link PluginRealtimeSource}.
147
+ *
148
+ * `null` is the explicit "not provided" sentinel — hooks must treat null as a
149
+ * clean no-op (dev/mock/no-connection) rather than an error.
150
+ *
151
+ * Provided by {@link ExtensionRuntimeProvider} when the host passes a
152
+ * `realtime` prop; consumed by `usePluginRealtimeSource()`.
153
+ */
154
+ declare const PluginRealtimeContext: react.Context<PluginRealtimeSource | null>;
155
+ /**
156
+ * Returns the {@link PluginRealtimeSource} from context, or `null` when none
157
+ * is wired (dev/mock environments, unit tests that only care about MCP).
158
+ *
159
+ * Hooks built on top of this (e.g. `usePluginRealtime` in `plugin-ui`) should
160
+ * skip their subscription entirely when this returns `null`.
161
+ */
162
+ declare function usePluginRealtimeSource(): PluginRealtimeSource | null;
163
+
9
164
  interface ExtensionRuntimeProviderProps {
10
165
  transport: McpTransport;
166
+ /**
167
+ * Optional host realtime channel consumed by {@link useClientPushSubscription}.
168
+ * Absent (or `null`) in standalone/mock hosts and hosts that predate client-push,
169
+ * in which case the hook is inert. The host binds this channel to the mounted
170
+ * surface's trusted extension + organisation identity.
171
+ */
172
+ clientPush?: ClientPushChannel | null;
173
+ /**
174
+ * Optional host identity/permission context consumed by {@link useHostIdentity}.
175
+ * Absent (or `null`) in standalone/mock hosts and hosts that predate this seam,
176
+ * in which case the hook is inert. The host binds this to the mounted surface's
177
+ * trusted extension identity and forwards only that extension's own grant.
178
+ */
179
+ identity?: HostIdentity | null;
180
+ /**
181
+ * Optional realtime subscription source supplied by the host.
182
+ *
183
+ * When provided, descendant components can call `usePluginRealtimeSource()`
184
+ * to obtain it and subscribe to push notifications. When omitted (dev/mock
185
+ * environments or plugins that don't need realtime), the context defaults
186
+ * to `null` and consumers no-op cleanly.
187
+ */
188
+ realtime?: PluginRealtimeSource;
11
189
  children?: ReactNode;
12
190
  }
13
191
  /**
@@ -19,7 +197,7 @@ interface ExtensionRuntimeProviderProps {
19
197
  * over the context value — useful for tests and for plugins that want to
20
198
  * shard work across multiple hosts.
21
199
  */
22
- declare function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode;
200
+ declare function ExtensionRuntimeProvider({ transport, clientPush, identity, realtime, children }: ExtensionRuntimeProviderProps): ReactNode;
23
201
 
24
202
  interface UseMcpResourceOptions {
25
203
  /**
@@ -445,4 +623,4 @@ interface UseFrontendSessionTokenResult {
445
623
  */
446
624
  declare function useFrontendSessionToken(transport: McpTransport): UseFrontendSessionTokenResult;
447
625
 
448
- export { BridgeClientContext, type CreatePortMcpTransportOptions, type CreateRemoteRootOptions, type DeclarativePluginConfig, type EthisysPluginConfig, ExtensionRuntimeProvider, type ExtensionRuntimeProviderProps, type ItemsResponse, LocalePayload, McpTransport, PortBridgeClient, type PortShim, type RemoteRoot, ThemePayload, type UseFrontendSessionTokenResult, type UseMcpQueryOptions, type UseMcpQueryResult, type UseMcpResourceOptions, type UseMcpResourceResult, type UseMcpToolOptions, type UseMcpToolResult, createPortMcpTransport, createRemoteRoot, defineDeclarativePlugin, defineEthisysPlugin, unwrapItems, useBridgeClient, useBridgeLocale, useBridgeTheme, useFrontendSessionToken, useMcpQuery, useMcpResource, useMcpTool };
626
+ export { BridgeClientContext, type ClientPushChannel, ClientPushContext, type ClientPushEvent, type ClientPushSubscribeOptions, type CreatePortMcpTransportOptions, type CreateRemoteRootOptions, type DeclarativePluginConfig, type EthisysPluginConfig, ExtensionRuntimeProvider, type ExtensionRuntimeProviderProps, type HostIdentity, HostIdentityContext, type HostIdentityUser, type HostPermission, type ItemsResponse, LocalePayload, McpTransport, PluginRealtimeContext, type PluginRealtimeSource, PortBridgeClient, type PortShim, type RemoteRoot, ThemePayload, type UseClientPushSubscriptionOptions, type UseFrontendSessionTokenResult, type UseMcpQueryOptions, type UseMcpQueryResult, type UseMcpResourceOptions, type UseMcpResourceResult, type UseMcpToolOptions, type UseMcpToolResult, createPortMcpTransport, createRemoteRoot, defineDeclarativePlugin, defineEthisysPlugin, unwrapItems, useBridgeClient, useBridgeLocale, useBridgeTheme, useClientPushSubscription, useFrontendSessionToken, useHostIdentity, useMcpQuery, useMcpResource, useMcpTool, usePluginRealtimeSource };
@@ -1,4 +1,4 @@
1
- import { createContext, useMemo, useState, useRef, useEffect, useCallback, useContext } from 'react';
1
+ import { createContext, useContext, useRef, useEffect, useMemo, useState, useCallback } from 'react';
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import { BatchingRemoteConnection } from '@remote-dom/core/elements';
4
4
  import ReactReconciler from 'react-reconciler';
@@ -6,10 +6,43 @@ import { LegacyRoot, DefaultEventPriority } from 'react-reconciler/constants.js'
6
6
  import { MUTATION_TYPE_UPDATE_TEXT, MUTATION_TYPE_UPDATE_PROPERTY, MUTATION_TYPE_INSERT_CHILD, MUTATION_TYPE_REMOVE_CHILD, NODE_TYPE_TEXT, NODE_TYPE_ELEMENT, ROOT_ID } from '@remote-dom/core';
7
7
 
8
8
  // src/plugin/ExtensionRuntimeProvider.tsx
9
+ var ClientPushContext = createContext(null);
10
+ function useClientPushSubscription(options) {
11
+ const { groups, onEvent, onResync, enabled = true } = options;
12
+ const channel = useContext(ClientPushContext);
13
+ const onEventRef = useRef(onEvent);
14
+ onEventRef.current = onEvent;
15
+ const onResyncRef = useRef(onResync);
16
+ onResyncRef.current = onResync;
17
+ const normalizedGroups = [...new Set(groups)].sort();
18
+ const groupsKey = JSON.stringify(normalizedGroups);
19
+ useEffect(() => {
20
+ if (!channel || !enabled || normalizedGroups.length === 0) {
21
+ return;
22
+ }
23
+ const unsubscribe = channel.subscribe({
24
+ groups: normalizedGroups,
25
+ onEvent: (event) => onEventRef.current(event),
26
+ onResync: () => onResyncRef.current?.()
27
+ });
28
+ return unsubscribe;
29
+ }, [channel, enabled, groupsKey]);
30
+ }
31
+ var HostIdentityContext = createContext(null);
32
+ function useHostIdentity() {
33
+ return useContext(HostIdentityContext);
34
+ }
35
+ var PluginRealtimeContext = createContext(null);
36
+ function usePluginRealtimeSource() {
37
+ return useContext(PluginRealtimeContext);
38
+ }
9
39
  var ExtensionRuntimeContext = createContext(null);
10
- function ExtensionRuntimeProvider({ transport, children }) {
40
+ function ExtensionRuntimeProvider({ transport, clientPush = null, identity = null, realtime, children }) {
11
41
  const value = useMemo(() => transport, [transport]);
12
- return /* @__PURE__ */ jsx(ExtensionRuntimeContext.Provider, { value, children });
42
+ const push = useMemo(() => clientPush, [clientPush]);
43
+ const id = useMemo(() => identity, [identity]);
44
+ const realtimeValue = useMemo(() => realtime ?? null, [realtime]);
45
+ return /* @__PURE__ */ jsx(ExtensionRuntimeContext.Provider, { value, children: /* @__PURE__ */ jsx(ClientPushContext.Provider, { value: push, children: /* @__PURE__ */ jsx(HostIdentityContext.Provider, { value: id, children: /* @__PURE__ */ jsx(PluginRealtimeContext.Provider, { value: realtimeValue, children }) }) }) });
13
46
  }
14
47
  function useExtensionRuntimeTransport(override) {
15
48
  const fromContext = useContext(ExtensionRuntimeContext);
@@ -718,6 +751,6 @@ function useFrontendSessionToken(transport) {
718
751
  return { token, isLoading, error };
719
752
  }
720
753
 
721
- export { BridgeClientContext, ExtensionRuntimeProvider, createPortBridgeClient, createPortMcpTransport, createRemoteRoot, defineDeclarativePlugin, defineEthisysPlugin, unwrapItems, useBridgeClient, useBridgeLocale, useBridgeTheme, useFrontendSessionToken, useMcpQuery, useMcpResource, useMcpTool };
754
+ export { BridgeClientContext, ClientPushContext, ExtensionRuntimeProvider, HostIdentityContext, PluginRealtimeContext, createPortBridgeClient, createPortMcpTransport, createRemoteRoot, defineDeclarativePlugin, defineEthisysPlugin, unwrapItems, useBridgeClient, useBridgeLocale, useBridgeTheme, useClientPushSubscription, useFrontendSessionToken, useHostIdentity, useMcpQuery, useMcpResource, useMcpTool, usePluginRealtimeSource };
722
755
  //# sourceMappingURL=index.js.map
723
756
  //# sourceMappingURL=index.js.map