@ggui-ai/mcp-apps-react-native 0.0.1-placeholder → 0.9.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.
@@ -0,0 +1,121 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { AdapterPermissions, PermissionStatus, InterfaceContext, EndUserIdentity, AppDisplayConfig } from '@ggui-ai/protocol';
3
+
4
+ /**
5
+ * Open interface — host runtime registry of **client/device adapter
6
+ * implementations**. Empty on purpose: capability packages or host
7
+ * apps augment this interface via TypeScript declaration merging so
8
+ * the slot for each adapter lands with its concrete type.
9
+ *
10
+ * Example (host app or downstream capability package):
11
+ * ```ts
12
+ * declare module '@ggui-ai/mcp-apps-react-native' {
13
+ * interface AdapterRegistry {
14
+ * printer?: PrinterAdapter;
15
+ * }
16
+ * }
17
+ * ```
18
+ *
19
+ * Mirrors {@link AdapterRegistry} in `@ggui-ai/mcp-apps-react`. The augmenting
20
+ * party can augment one or both registries depending on which SDKs
21
+ * the adapter ships against. (Browser-capability hooks — camera,
22
+ * geolocation, clipboard, filePicker, notifications, microphone —
23
+ * are better served by the gadget pattern in `@ggui-ai/gadgets`,
24
+ * which doesn't need a Provider-wired adapter; generated UI imports
25
+ * the hook directly.)
26
+ */
27
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
28
+ export interface AdapterRegistry {}
29
+
30
+ /**
31
+ * Shape of the value provided by {@link GguiContext}.
32
+ *
33
+ * Contains app identity, WebSocket configuration, adapter permissions,
34
+ * interface context, and optional auth/session state consumed by hooks.
35
+ * Extends the web version with `reactVersion` and `designSystemUrl` for
36
+ * WebView import map configuration.
37
+ */
38
+ export interface GguiContextValue {
39
+ appId: string;
40
+ wsEndpoint?: string;
41
+ adapterPermissions: AdapterPermissions;
42
+ requestPermission: (adapter: string) => Promise<PermissionStatus>;
43
+ /** Host-registered adapter implementations, keyed by capability name.
44
+ * Generated UI reads from this registry via {@link useAdapter} (or
45
+ * capability-specific hooks exposed by host apps that augment
46
+ * {@link AdapterRegistry}). Grant decisions live on
47
+ * `clientCapabilities.gadgets[*].permission` — this registry is
48
+ * purely the runtime implementation slot. */
49
+ adapterImpls: AdapterRegistry;
50
+ /** Current interface context (device/viewport info) */
51
+ interfaceContext: InterfaceContext;
52
+ /** Auth context surfaced to embedding hosts and renderer surfaces. */
53
+ auth?: {
54
+ currentUser?: EndUserIdentity;
55
+ userId?: string;
56
+ token?: string;
57
+ isAuthenticated: boolean;
58
+ };
59
+ /**
60
+ * Conversation envelope identity. Forwarded by {@link useInvoke} as
61
+ * the `X-Ggui-Host-Session-Id` header so the agent threads multi-turn
62
+ * invokes through its own keyed conversation state. Names the chat
63
+ * thread, not a render.
64
+ */
65
+ hostSessionId?: string;
66
+ /** React version for WebView import map (default: '18.2.0') */
67
+ reactVersion?: string;
68
+ /** Base URL for design system modules in WebView import map */
69
+ designSystemUrl?: string;
70
+ /**
71
+ * App config (endpointUrl, defaultShellType, etc). Mirrors the web SDK's
72
+ * context field — `useInvoke` reads `endpointUrl` from here when the
73
+ * caller doesn't override it. Populated by {@link GguiProvider} via its
74
+ * `appConfig` prop.
75
+ */
76
+ appConfig?: AppDisplayConfig | null;
77
+ }
78
+
79
+ /**
80
+ * React context that carries ggui configuration to all descendant components.
81
+ *
82
+ * Provided by {@link GguiProvider}. Access the value with {@link useGguiContext}.
83
+ */
84
+ export const GguiContext = createContext<GguiContextValue | null>(null);
85
+
86
+ /**
87
+ * Access the nearest {@link GguiContext} value.
88
+ *
89
+ * Must be called inside a `<GguiProvider>`. Throws if no provider is found.
90
+ *
91
+ * @returns The current {@link GguiContextValue}
92
+ * @throws Error if called outside a GguiProvider
93
+ */
94
+ export function useGguiContext(): GguiContextValue {
95
+ const ctx = useContext(GguiContext);
96
+ if (!ctx) {
97
+ throw new Error('useGguiContext must be used within a GguiProvider');
98
+ }
99
+ return ctx;
100
+ }
101
+
102
+ /**
103
+ * Read a host-registered adapter implementation by capability name.
104
+ *
105
+ * Returns `undefined` when the host hasn't wired an implementation
106
+ * for this capability — callers MUST handle that case (e.g. render
107
+ * a fallback UI). Grant decisions live on `clientCapabilities.gadgets
108
+ * [*].permission` and surface to the iframe via `Permissions-Policy`;
109
+ * the SDK context just exposes whatever the host wired into
110
+ * `adapterImpls`.
111
+ *
112
+ * Mirrors {@link useAdapter} in `@ggui-ai/mcp-apps-react`. Capability packages
113
+ * augment both {@link AdapterRegistry} surfaces via declaration
114
+ * merging.
115
+ */
116
+ export function useAdapter<K extends keyof AdapterRegistry>(
117
+ name: K,
118
+ ): AdapterRegistry[K] | undefined {
119
+ const ctx = useGguiContext();
120
+ return ctx.adapterImpls[name];
121
+ }
@@ -0,0 +1,24 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { AppState, type AppStateStatus } from 'react-native';
3
+
4
+ /**
5
+ * Hook to track React Native AppState (active, background, inactive).
6
+ * Useful for pausing/resuming connections and animations.
7
+ */
8
+ export function useAppState(): AppStateStatus {
9
+ const [appState, setAppState] = useState<AppStateStatus>(AppState.currentState);
10
+ const appStateRef = useRef(AppState.currentState);
11
+
12
+ useEffect(() => {
13
+ const subscription = AppState.addEventListener('change', (nextState) => {
14
+ appStateRef.current = nextState;
15
+ setAppState(nextState);
16
+ });
17
+
18
+ return () => {
19
+ subscription.remove();
20
+ };
21
+ }, []);
22
+
23
+ return appState;
24
+ }
package/src/index.ts ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @ggui-ai/mcp-apps-react-native - React Native SDK for ggui
3
+ *
4
+ * Provides React Native components, hooks, and utilities for embedding ggui
5
+ * agent interfaces in mobile applications. The host primitive is
6
+ * `<McpAppIframe>` — the WebView-backed MCP Apps host (RN analog of the
7
+ * web's `<AppRenderer>` from `@mcp-ui/client`) — surrounded by the
8
+ * message-grouping helpers (`chat-helpers` subpath), the Streamable
9
+ * Invoke hook (`useInvoke`), and a React Native theme system that
10
+ * mirrors the web design tokens.
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+
15
+ // Theme System
16
+ export { ThemeProvider, useTheme, buildTheme } from './theme';
17
+ export type { ThemeProviderProps, RNTheme, RNThemeColors, RNThemeSemantic, RNShadow, RNTransitionPreset, RNAccessibility } from './theme';
18
+ export {
19
+ rnColors,
20
+ rnSemantic,
21
+ rnSpacing,
22
+ rnSpacingNamed,
23
+ rnFontSize,
24
+ rnFontWeight,
25
+ rnLineHeight,
26
+ rnFontFamily,
27
+ rnRadius,
28
+ rnShadow,
29
+ rnDuration,
30
+ rnEasing,
31
+ rnTransition,
32
+ rnAccessibility,
33
+ } from './theme';
34
+
35
+ // Re-export transport types from the transport subpath
36
+ export type {
37
+ ConnectionStatus,
38
+ WebSocketMessage,
39
+ WebSocketMessageType,
40
+ } from '@ggui-ai/protocol/transport/websocket';
41
+
42
+ // Re-export types from protocol
43
+ export type {
44
+ ActionEnvelope,
45
+ EventType,
46
+ // Single GguiSession union (ComponentGguiSession, SystemGguiSession, McpAppsGguiSession)
47
+ // keyed by the flat `sessionId`.
48
+ GguiSession,
49
+ ComponentGguiSession,
50
+ SystemGguiSession,
51
+ GguiSessionStatus,
52
+ AdapterPermissions,
53
+ PermissionStatus,
54
+ SubscribePayload,
55
+ AckPayload,
56
+ StreamEnvelope,
57
+ ErrorPayload,
58
+ RenderPayload,
59
+ PropsUpdatePayload,
60
+ ShellType,
61
+ InterfaceContext,
62
+ DeviceCategory,
63
+ EndUserIdentity,
64
+ } from '@ggui-ai/protocol';
65
+ export { BRIDGE_EVENTS, detectInterfaceContext, getDeviceCategory } from '@ggui-ai/protocol';
66
+
67
+ // Invoke protocol message block types — re-exported at root so facade
68
+ // consumers can pull them from the same import path as useInvoke.
69
+ // Parity: identical type re-export block exists on @ggui-ai/mcp-apps-react.
70
+ export type {
71
+ ContentBlock,
72
+ TextBlock,
73
+ ToolUseBlock,
74
+ ToolResultBlock,
75
+ InvokeTurn,
76
+ } from '@ggui-ai/protocol';
77
+
78
+ // ProtocolError typed union — the canonical shape for every failure
79
+ // the renderer classifies outward. `<McpAppIframe onError>` surfaces
80
+ // it; embedding apps pattern-match on `err.kind`. The sibling package
81
+ // `@ggui-ai/iframe-runtime` owns the declaration; the RN SDK re-exports
82
+ // it at parity with `@ggui-ai/mcp-apps-react` so consumers have a single import
83
+ // point per platform.
84
+ export type {
85
+ ProtocolError,
86
+ ProtocolErrorEmitter,
87
+ BootstrapFailureReason,
88
+ // Bootstrap-failure postMessage envelope shape — the parent receives
89
+ // `{type:'ggui:bootstrap-failed', reason, message}` from the iframe /
90
+ // WebView on any pre-renderer or post-renderer boot failure. RN
91
+ // hosts reading this via WebView `onMessage` pattern-match on the
92
+ // `type` tag to classify iframe-origin failures.
93
+ RendererBootFailedMessage,
94
+ } from '@ggui-ai/iframe-runtime';
95
+
96
+ // Provider
97
+ export { GguiProvider, useGguiContext, useAdapter } from './components/GguiProvider';
98
+ export type { GguiProviderProps } from './components/GguiProvider';
99
+ export type { AdapterRegistry } from './context/GguiContext';
100
+
101
+ // Shared host-role MCP-Apps bridge helpers — exported for composition by
102
+ // callers that want to embed the bridge in a custom WebView wrapper
103
+ // (e.g., custom error overlays, in-app navigation headers). The
104
+ // switch implements the canonical set: ui/initialize, tools/call,
105
+ // ping, ui/open-link, ui/resource-teardown.
106
+ export {
107
+ handleHostBridgeRequest,
108
+ buildInjectedBridgeScript,
109
+ buildDeliveryScript,
110
+ NATIVE_BRIDGE_ENVELOPE_KEY,
111
+ } from './components/mcp-apps-bridge';
112
+ export type { HostBridgeContext } from './components/mcp-apps-bridge';
113
+
114
+ // `<McpAppIframe>` — generic MCP Apps iframe host for React Native.
115
+ // Zero ggui-specific coupling; a mirror of the web host exported from
116
+ // `@ggui-ai/mcp-apps-react`. Any MCP Apps host (Claude Desktop,
117
+ // ChatGPT, VS Code, console, third-party playgrounds) uses this to
118
+ // embed a ggui (or any MCP Apps-conformant) session on RN.
119
+ export { McpAppIframe } from './McpAppIframe/index';
120
+ export type {
121
+ McpAppIframeProps,
122
+ McpAppIframeRef,
123
+ McpAppIframeDimensions,
124
+ McpAppIframePermissions,
125
+ } from './McpAppIframe/index';
126
+
127
+ // Error Boundary
128
+ export { ErrorBoundary } from './components/ErrorBoundary';
129
+ export type { ErrorBoundaryProps } from './components/ErrorBoundary';
130
+
131
+ // UI Feedback affordance — host-side render-shell chrome. Hidden
132
+ // entirely unless the host wires `onUiFeedback`; the payload leaves
133
+ // through that callback only (never the agent ↔ UI wire). An
134
+ // in-iframe twin lives in `@ggui-ai/iframe-runtime` (emitting a
135
+ // `ui-feedback` event on the `ggui:observe` seam) — hosts wire
136
+ // exactly ONE of the two surfaces, never both.
137
+ export { UiFeedback } from './components/UiFeedback';
138
+ export type {
139
+ UiFeedbackProps,
140
+ UiFeedbackPayload,
141
+ UiFeedbackVerdict,
142
+ } from './components/UiFeedback';
143
+
144
+ // Streamable Invoke Protocol (v1.1) hook
145
+ export { useInvoke, parseSseStream } from './invoke/index';
146
+ export type { UseInvokeOptions, UseInvokeReturn, ConversationMessage, InvokeError } from './invoke/index';
147
+
148
+ // Hooks
149
+ export { useAppState } from './hooks/useAppState';
150
+
@@ -0,0 +1,8 @@
1
+ export { useInvoke } from './useInvoke';
2
+ export type {
3
+ UseInvokeOptions,
4
+ UseInvokeReturn,
5
+ ConversationMessage,
6
+ InvokeError,
7
+ } from './useInvoke';
8
+ export { parseSseStream } from './sse-parse';
@@ -0,0 +1,79 @@
1
+ /**
2
+ * SSE frame parser for the streamable invoke protocol (React Native).
3
+ *
4
+ * Reads a `Response.body` chunk by chunk, yields parsed + validated
5
+ * `InvokeEvent`s as they arrive. Buffers partial frames across chunk
6
+ * boundaries — chunks from `fetch` do NOT align with `\n\n` separators.
7
+ *
8
+ * This is a near-exact port of the web implementation
9
+ * (`@ggui-ai/mcp-apps-react/src/invoke/sse-parse.ts`). The only platform concern is
10
+ * `TextDecoder`: modern React Native (Hermes, RN 0.74+) ships it natively,
11
+ * and Expo SDK 50+ polyfills it for older runtimes. If consumers target an
12
+ * older setup they must polyfill `TextDecoder` in their entry file.
13
+ */
14
+ import { invokeEventSchema, type InvokeEvent } from '@ggui-ai/protocol';
15
+
16
+ const DECODER = new TextDecoder();
17
+ const FRAME_SEP = '\n\n';
18
+
19
+ /**
20
+ * Async generator yielding one `InvokeEvent` per SSE frame in the stream.
21
+ *
22
+ * Malformed frames (non-JSON data, schema validation failure) are dropped
23
+ * silently — agents emit a wide variety of frames and a single bad one
24
+ * shouldn't kill the whole turn. Caller handles `event: error` for explicit
25
+ * upstream failures.
26
+ */
27
+ export async function* parseSseStream(
28
+ body: ReadableStream<Uint8Array>,
29
+ signal?: AbortSignal,
30
+ ): AsyncGenerator<InvokeEvent> {
31
+ const reader = body.getReader();
32
+ let buffer = '';
33
+
34
+ try {
35
+ while (true) {
36
+ if (signal?.aborted) break;
37
+ const { value, done } = await reader.read();
38
+ if (done) break;
39
+
40
+ buffer += DECODER.decode(value, { stream: true });
41
+
42
+ let sepIdx: number;
43
+ while ((sepIdx = buffer.indexOf(FRAME_SEP)) !== -1) {
44
+ const frame = buffer.slice(0, sepIdx);
45
+ buffer = buffer.slice(sepIdx + FRAME_SEP.length);
46
+ const event = parseFrame(frame);
47
+ if (event) yield event;
48
+ }
49
+ }
50
+
51
+ // Flush trailing frame (no terminator) — agent that closed cleanly will
52
+ // have sent message_stop with the separator, but be lenient.
53
+ if (buffer.length > 0) {
54
+ const event = parseFrame(buffer);
55
+ if (event) yield event;
56
+ }
57
+ } finally {
58
+ reader.releaseLock();
59
+ }
60
+ }
61
+
62
+ function parseFrame(frame: string): InvokeEvent | null {
63
+ // Frames are `event: NAME\ndata: JSON` (newlines are spec-strict but
64
+ // forgiving here). We only need the data line.
65
+ const dataIdx = frame.indexOf('data: ');
66
+ if (dataIdx === -1) return null;
67
+ const json = frame.slice(dataIdx + 'data: '.length).trim();
68
+ if (!json) return null;
69
+
70
+ let parsed: unknown;
71
+ try {
72
+ parsed = JSON.parse(json);
73
+ } catch {
74
+ return null;
75
+ }
76
+
77
+ const result = invokeEventSchema.safeParse(parsed);
78
+ return result.success ? result.data : null;
79
+ }