@yahoo/uds-create-sidecar 3.0.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/README.md ADDED
@@ -0,0 +1,220 @@
1
+ # UDS Create Sidecar
2
+
3
+ `@yahoo/uds-create-sidecar` lets an Expo development build render Create
4
+ previews inside the customer's own app. The preview therefore uses the same
5
+ native packages, configuration, and package versions as that app.
6
+
7
+ The package contains the existing Sidecar pairing UI, preview-room client,
8
+ authenticated and integrity-checked bundle loader, and renderer host. The only
9
+ required component prop is an explicit module registry:
10
+
11
+ ```text
12
+ Create browser ── signed room token ──> UDSCreateSidecar
13
+
14
+ Customer app ── exact modules + versions ──┘
15
+
16
+ └──> real app dependencies
17
+ ```
18
+
19
+ The standalone app in `apps/create-sidecar` consumes this same package and is
20
+ the reference implementation.
21
+
22
+ ## Requirements
23
+
24
+ This first package targets Expo SDK 57 development builds with React Native
25
+ 0.86. It is not supported in Expo Go because registered packages may contain
26
+ native code.
27
+
28
+ Install the host and the shared JSON renderer as development dependencies:
29
+
30
+ ```sh
31
+ bun add --dev --exact @yahoo/uds-create-sidecar @json-render/react
32
+ bunx expo install react-native-safe-area-context
33
+ ```
34
+
35
+ Install and configure every native package used by the design system in the
36
+ usual way. Sidecar does not replace that package's native setup.
37
+ `UDSCreateSidecar` must be rendered below the app's `SafeAreaProvider`.
38
+
39
+ ### What the package expects from the app
40
+
41
+ Every package Sidecar needs from the app is a declared peer dependency, so a
42
+ missing one is reported at install time:
43
+
44
+ - `react`, `react-native`, `react-native-safe-area-context`, and `expo` are the
45
+ app's own copies. Sidecar renders its pairing UI with them.
46
+ - `@json-render/react` is the renderer every Create preview bundle imports.
47
+ Sidecar never imports it itself, but the registry requires it as a module,
48
+ so the app must install it and register it.
49
+
50
+ ### What the package carries
51
+
52
+ The preview-room client is built on Yjs and the Hocuspocus provider. Both are
53
+ compiled into the package, together with their `lib0` and `y-protocols`
54
+ internals, so the app does not install them and the app's own versions never
55
+ matter. Compiling them in also lets the package replace `lib0`'s Web Crypto
56
+ binding with a small shim over `@noble/hashes`, the package's one runtime
57
+ dependency, so React Native needs no Web Crypto polyfill. Yjs uses those values
58
+ only for client and document identifiers.
59
+
60
+ An app that also uses Yjs sees one console error the first time the Sidecar
61
+ screen loads, `Yjs was already imported`, shown as a LogBox error in a
62
+ development build. It is Yjs noticing a second copy of itself. Sidecar's
63
+ documents never cross into the app's Yjs, so the two copies never meet and the
64
+ message can be dismissed.
65
+
66
+ ## 1. Define the host registry
67
+
68
+ Create one module that statically imports everything a downloaded preview may
69
+ use. Metro must see these imports when it builds the app; downloaded JavaScript
70
+ cannot discover or import an arbitrary installed native package later.
71
+
72
+ This example exposes `react-native-maps`, a package that is intentionally not
73
+ built into the standalone Sidecar:
74
+
75
+ ```tsx
76
+ // src/create-sidecar-registry.ts
77
+ import * as JsonRenderReact from "@json-render/react";
78
+ import { createUDSCreateSidecarRegistry } from "@yahoo/uds-create-sidecar/registry";
79
+ import * as React from "react";
80
+ import * as ReactJsxRuntime from "react/jsx-runtime";
81
+ import * as ReactNative from "react-native";
82
+ import * as ReactNativeMaps from "react-native-maps";
83
+ import appPackage from "../package.json";
84
+
85
+ export const createSidecarRegistry = createUDSCreateSidecarRegistry({
86
+ hostName: "Acme mobile app",
87
+ hostVersion: appPackage.version,
88
+ modules: {
89
+ "@json-render/react": JsonRenderReact,
90
+ react: React,
91
+ "react/jsx-runtime": ReactJsxRuntime,
92
+ "react-native": ReactNative,
93
+ "react-native-maps": ReactNativeMaps,
94
+ },
95
+ packageVersions: {
96
+ ...appPackage.dependencies,
97
+ ...appPackage.devDependencies,
98
+ },
99
+ });
100
+ ```
101
+
102
+ The versions for every exposed package must be exact strings such as `1.2.3`,
103
+ not ranges such as `^1.2.3`. Pin those dependencies in `package.json` or provide
104
+ exact versions from the app's lockfile. The registry rejects a missing or
105
+ non-exact version immediately.
106
+
107
+ Subpath imports must be registered separately, but they use the package root's
108
+ version. For example, registering `react/jsx-dev-runtime` still uses the
109
+ `react` version.
110
+
111
+ ### How Create checks the registry
112
+
113
+ Each preview build lists the packages it imports, the version the design system
114
+ declares for each, and the exact import paths it reads. The connected host
115
+ advertises the registry, and Create and the phone run the same comparison:
116
+
117
+ - A required package or import path that is not registered is missing.
118
+ - A registered version whose major or minor differs from the required version
119
+ is a mismatch. Patch versions may differ.
120
+
121
+ While the phone is connected, Create's phone controls name the packages the
122
+ host cannot provide. The phone repeats the check before evaluating a bundle and
123
+ refuses with an error naming the package and, for a mismatch, both versions.
124
+ Either side can move: install the version the design system declares, or update
125
+ the version the design system declares for that package.
126
+
127
+ ## 2. Add a development-only route
128
+
129
+ The app, rather than the component, owns the production gate. This lets the
130
+ standalone Sidecar remain a normal release build while a customer app can make
131
+ the route impossible to activate in production.
132
+
133
+ For Expo Router, guard the package load as well as the rendered route:
134
+
135
+ ```tsx
136
+ // app/create-preview.tsx
137
+ import { Redirect } from "expo-router";
138
+ import { createSidecarRegistry } from "../src/create-sidecar-registry";
139
+
140
+ declare const require: (
141
+ id: "@yahoo/uds-create-sidecar",
142
+ ) => typeof import("@yahoo/uds-create-sidecar");
143
+
144
+ export default function CreatePreviewRoute() {
145
+ if (!__DEV__) return <Redirect href="/" />;
146
+
147
+ const { UDSCreateSidecar } = require("@yahoo/uds-create-sidecar");
148
+ return <UDSCreateSidecar registry={createSidecarRegistry} />;
149
+ }
150
+ ```
151
+
152
+ Use the equivalent compile-time development constant and production fallback
153
+ with another router. Do not gate only a navigation link: the route itself must
154
+ return before loading or rendering Sidecar. The package test compiles this
155
+ pattern with `__DEV__` set to false and verifies that the Sidecar reference is
156
+ removed from the route's execution path.
157
+
158
+ The two entry points are split for this gate. `@yahoo/uds-create-sidecar/registry`
159
+ holds only the registry builder, imports nothing, and is safe to import at
160
+ module top level in every build, including production. The root entry holds the
161
+ pairing UI, the preview-room client, and the compiled-in Yjs, so it stays behind
162
+ the compile-time check.
163
+
164
+ ## 3. Pair with Create
165
+
166
+ Open the development route. `UDSCreateSidecar` uses production Create by
167
+ default and shows the same origin chooser, short-code pairing, status UI,
168
+ disconnect behavior, and full-screen design preview as the standalone app. A
169
+ custom origin remains selected while the component is mounted; a new launch
170
+ returns to production.
171
+
172
+ It also accepts an existing Sidecar deep link when the host app routes that URL
173
+ to the component; short-code pairing is the portable default for an embedded
174
+ host with its own URL scheme.
175
+
176
+ When the host router mounts this screen from a deep link, pass the route values
177
+ to the component. This avoids relying on a link event that may have fired before
178
+ the route mounted:
179
+
180
+ ```tsx
181
+ import type { UDSCreateSidecarDeepLink } from "@yahoo/uds-create-sidecar";
182
+ import { useLocalSearchParams } from "expo-router";
183
+
184
+ const params = useLocalSearchParams<UDSCreateSidecarDeepLink>();
185
+ const deepLink =
186
+ params.origin && params.room && params.token && params.ws
187
+ ? {
188
+ origin: params.origin,
189
+ room: params.room,
190
+ token: params.token,
191
+ ws: params.ws,
192
+ }
193
+ : undefined;
194
+
195
+ return (
196
+ <UDSCreateSidecar registry={createSidecarRegistry} deepLink={deepLink} />
197
+ );
198
+ ```
199
+
200
+ Create's QR code uses the `uds-create-sidecar` scheme. An embedded app must
201
+ route that scheme to its development-only preview screen to use QR pairing.
202
+ Apps that do not own that route should use the short pairing code.
203
+
204
+ In Create, open **Connect phone**, choose **Connect Sidecar**, and enter the
205
+ code from the app. The embedded host advertises the registry's exact package
206
+ versions through the existing preview protocol; no protocol or token changes
207
+ are required.
208
+
209
+ ## Security boundary
210
+
211
+ The host downloads a bundle only from the selected Create origin, sends the
212
+ room-scoped bearer token, limits the response to 5 MiB, and verifies the
213
+ manifest's SHA-256 hash before evaluation. Module resolution is limited to the
214
+ explicit registry.
215
+
216
+ This is not a JavaScript sandbox. An authorized Create bundle runs inside the
217
+ development app's JavaScript process and can use normal JavaScript globals in
218
+ addition to registered modules. Expose only modules intended for preview, use
219
+ only a trusted Create deployment, and keep the route hard-disabled in
220
+ production.
@@ -0,0 +1,24 @@
1
+ import { UDSCreateSidecarModules, UDSCreateSidecarRegistry, UDSCreateSidecarRegistryOptions, createUDSCreateSidecarRegistry } from "./registry.js";
2
+ import * as _$react from "react";
3
+
4
+ //#region src/deep-link.d.ts
5
+ interface UDSCreateSidecarDeepLink {
6
+ readonly origin: string;
7
+ readonly room: string;
8
+ readonly token: string;
9
+ readonly ws: string;
10
+ }
11
+ declare function parseUDSCreateSidecarDeepLink(url: string): UDSCreateSidecarDeepLink | undefined;
12
+ //#endregion
13
+ //#region src/UDSCreateSidecar.d.ts
14
+ interface UDSCreateSidecarProps {
15
+ readonly registry: UDSCreateSidecarRegistry;
16
+ /** Pairing values read by the host router from its current deep link. */
17
+ readonly deepLink?: UDSCreateSidecarDeepLink;
18
+ }
19
+ declare function UDSCreateSidecar({
20
+ deepLink,
21
+ registry
22
+ }: UDSCreateSidecarProps): _$react.JSX.Element;
23
+ //#endregion
24
+ export { UDSCreateSidecar, type UDSCreateSidecarDeepLink, type UDSCreateSidecarModules, type UDSCreateSidecarProps, type UDSCreateSidecarRegistry, type UDSCreateSidecarRegistryOptions, createUDSCreateSidecarRegistry, parseUDSCreateSidecarDeepLink };