@storybook/react-native 10.3.0-next.6 → 10.3.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.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Options for configuring WebSockets used for syncing storybook instances or sending events to storybook.
3
+ */
4
+ interface WebsocketsOptions {
5
+ /**
6
+ * The port WebSocket server will listen on. Defaults to 7007.
7
+ */
8
+ port?: number;
9
+ /**
10
+ * The host WebSocket server will bind to. Defaults to 'localhost'.
11
+ */
12
+ host?: string;
13
+ /**
14
+ * Whether to use WSS/HTTPS for the channel server.
15
+ */
16
+ secured?: boolean;
17
+ /**
18
+ * TLS private key used when `secured` is true.
19
+ */
20
+ key?: string | Buffer;
21
+ /**
22
+ * TLS certificate used when `secured` is true.
23
+ */
24
+ cert?: string | Buffer;
25
+ /**
26
+ * Optional certificate authority chain used when `secured` is true.
27
+ */
28
+ ca?: string | Buffer | Array<string | Buffer>;
29
+ /**
30
+ * Optional TLS passphrase used when `secured` is true.
31
+ */
32
+ passphrase?: string;
33
+ }
34
+
35
+ export type { WebsocketsOptions as W };
package/dist/index.d.ts CHANGED
@@ -78,6 +78,7 @@ declare class View {
78
78
  }>;
79
79
  _getHost: (params?: Partial<Params>) => any;
80
80
  __getPort: (params?: Partial<Params>) => any;
81
+ _isSecureConnection: (params?: Partial<Params>) => any;
81
82
  _getServerChannel: (params?: Partial<Params>) => Channel;
82
83
  createPreparedStoryMapping: () => Promise<void>;
83
84
  getStorybookUI: (params?: Partial<Params>) => () => react_jsx_runtime.JSX.Element;
package/dist/index.js CHANGED
@@ -471,6 +471,7 @@ var BUILDING_BLOCK_recomputeInvalidatedAtoms = (store2) => {
471
471
  }
472
472
  }
473
473
  if (hasChangedDeps) {
474
+ invalidatedAtoms.set(a, aState.n);
474
475
  readAtomState(store2, a);
475
476
  mountDependencies(store2, a);
476
477
  }
@@ -494,9 +495,19 @@ var BUILDING_BLOCK_readAtomState = (store2, atom2) => {
494
495
  const mountDependencies = buildingBlocks[17];
495
496
  const setAtomStateValueOrPromise = buildingBlocks[20];
496
497
  const registerAbortHandler = buildingBlocks[26];
498
+ const storeEpochHolder = buildingBlocks[28];
497
499
  const atomState = ensureAtomState(store2, atom2);
500
+ const storeEpochNumber = storeEpochHolder[0];
498
501
  if (isAtomStateInitialized(atomState)) {
499
- if (mountedMap.has(atom2) && invalidatedAtoms.get(atom2) !== atomState.n) {
502
+ if (
503
+ // If the atom is mounted, we can use cached atom state,
504
+ // because it should have been updated by dependencies.
505
+ // We can't use the cache if the atom is invalidated.
506
+ mountedMap.has(atom2) && invalidatedAtoms.get(atom2) !== atomState.n || // If atom is not mounted, we can use cached atom state,
507
+ // only if store hasn't been mutated.
508
+ atomState.m === storeEpochNumber
509
+ ) {
510
+ atomState.m = storeEpochNumber;
500
511
  return atomState;
501
512
  }
502
513
  let hasChangedDeps = false;
@@ -507,6 +518,7 @@ var BUILDING_BLOCK_readAtomState = (store2, atom2) => {
507
518
  }
508
519
  }
509
520
  if (!hasChangedDeps) {
521
+ atomState.m = storeEpochNumber;
510
522
  return atomState;
511
523
  }
512
524
  }
@@ -597,6 +609,7 @@ var BUILDING_BLOCK_readAtomState = (store2, atom2) => {
597
609
  }
598
610
  };
599
611
  const prevEpochNumber = atomState.n;
612
+ const prevInvalidated = invalidatedAtoms.get(atom2) === prevEpochNumber;
600
613
  try {
601
614
  if ((import_meta.env ? import_meta.env.MODE : void 0) !== "production") {
602
615
  storeMutationSet.delete(store2);
@@ -619,15 +632,17 @@ var BUILDING_BLOCK_readAtomState = (store2, atom2) => {
619
632
  pruneDependencies();
620
633
  }
621
634
  (_a = storeHooks.r) == null ? void 0 : _a.call(storeHooks, atom2);
635
+ atomState.m = storeEpochNumber;
622
636
  return atomState;
623
637
  } catch (error) {
624
638
  delete atomState.v;
625
639
  atomState.e = error;
626
640
  ++atomState.n;
641
+ atomState.m = storeEpochNumber;
627
642
  return atomState;
628
643
  } finally {
629
644
  isSync = false;
630
- if (prevEpochNumber !== atomState.n && invalidatedAtoms.get(atom2) === prevEpochNumber) {
645
+ if (atomState.n !== prevEpochNumber && prevInvalidated) {
631
646
  invalidatedAtoms.set(atom2, atomState.n);
632
647
  changedAtoms.add(atom2);
633
648
  (_b = storeHooks.c) == null ? void 0 : _b.call(storeHooks, atom2);
@@ -665,6 +680,7 @@ var BUILDING_BLOCK_writeAtomState = (store2, atom2, ...args) => {
665
680
  const writeAtomState = buildingBlocks[16];
666
681
  const mountDependencies = buildingBlocks[17];
667
682
  const setAtomStateValueOrPromise = buildingBlocks[20];
683
+ const storeEpochHolder = buildingBlocks[28];
668
684
  let isSync = true;
669
685
  const getter = (a) => returnAtomValue(readAtomState(store2, a));
670
686
  const setter = (a, ...args2) => {
@@ -683,6 +699,7 @@ var BUILDING_BLOCK_writeAtomState = (store2, atom2, ...args) => {
683
699
  setAtomStateValueOrPromise(store2, a, v);
684
700
  mountDependencies(store2, a);
685
701
  if (prevEpochNumber !== aState.n) {
702
+ ++storeEpochHolder[0];
686
703
  changedAtoms.add(a);
687
704
  invalidateDependents(store2, a);
688
705
  (_a = storeHooks.c) == null ? void 0 : _a.call(storeHooks, a);
@@ -867,14 +884,18 @@ var BUILDING_BLOCK_storeGet = (store2, atom2) => {
867
884
  };
868
885
  var BUILDING_BLOCK_storeSet = (store2, atom2, ...args) => {
869
886
  const buildingBlocks = getInternalBuildingBlocks(store2);
887
+ const changedAtoms = buildingBlocks[3];
870
888
  const flushCallbacks = buildingBlocks[12];
871
889
  const recomputeInvalidatedAtoms = buildingBlocks[13];
872
890
  const writeAtomState = buildingBlocks[16];
891
+ const prevChangedAtomsSize = changedAtoms.size;
873
892
  try {
874
893
  return writeAtomState(store2, atom2, ...args);
875
894
  } finally {
876
- recomputeInvalidatedAtoms(store2);
877
- flushCallbacks(store2);
895
+ if (changedAtoms.size !== prevChangedAtomsSize) {
896
+ recomputeInvalidatedAtoms(store2);
897
+ flushCallbacks(store2);
898
+ }
878
899
  }
879
900
  };
880
901
  var BUILDING_BLOCK_storeSub = (store2, atom2, listener) => {
@@ -983,7 +1004,9 @@ function buildStore(...buildArgs) {
983
1004
  /* @__PURE__ */ new WeakMap(),
984
1005
  // abortHandlersMap
985
1006
  BUILDING_BLOCK_registerAbortHandler,
986
- BUILDING_BLOCK_abortPromise
1007
+ BUILDING_BLOCK_abortPromise,
1008
+ // store epoch
1009
+ [0]
987
1010
  ].map((fn, i) => buildArgs[i] || fn);
988
1011
  buildingBlockMap.set(store2, Object.freeze(buildingBlocks));
989
1012
  return store2;
@@ -1155,6 +1178,28 @@ function useAtomValue(atom2, options) {
1155
1178
  }
1156
1179
  }
1157
1180
  if (typeof delay === "number") {
1181
+ console.warn(`[DEPRECATED] delay option is deprecated and will be removed in v3.
1182
+
1183
+ Migration guide:
1184
+
1185
+ Create a custom hook like the following.
1186
+
1187
+ function useAtomValueWithDelay<Value>(
1188
+ atom: Atom<Value>,
1189
+ options: { delay: number },
1190
+ ): Value {
1191
+ const { delay } = options
1192
+ const store = useStore(options)
1193
+ const [value, setValue] = useState(() => store.get(atom))
1194
+ useEffect(() => {
1195
+ const unsub = store.sub(atom, () => {
1196
+ setTimeout(() => setValue(store.get(atom)), delay)
1197
+ })
1198
+ return unsub
1199
+ }, [store, atom, delay])
1200
+ return value
1201
+ }
1202
+ `);
1158
1203
  setTimeout(rerender, delay);
1159
1204
  return;
1160
1205
  }
@@ -1389,11 +1434,17 @@ var View3 = class {
1389
1434
  }
1390
1435
  return 7007;
1391
1436
  };
1437
+ _isSecureConnection = (params = {}) => {
1438
+ if (typeof params.secured === "boolean") {
1439
+ return params.secured;
1440
+ }
1441
+ return globalThis.STORYBOOK_WEBSOCKET?.secured ?? false;
1442
+ };
1392
1443
  _getServerChannel = (params = {}) => {
1393
1444
  const host = this._getHost(params);
1394
1445
  const port = `:${this.__getPort(params)}`;
1395
1446
  const query = params.query || "";
1396
- const websocketType = params.secured ? "wss" : "ws";
1447
+ const websocketType = this._isSecureConnection(params) ? "wss" : "ws";
1397
1448
  const url = `${websocketType}://${host}${port}/${query}`;
1398
1449
  const channel = new import_channels.Channel({
1399
1450
  async: true,
@@ -1,18 +1,6 @@
1
1
  import { MetroConfig } from 'metro-config';
2
+ import { W as WebsocketsOptions } from '../index-6iAzVvXp.js';
2
3
 
3
- /**
4
- * Options for configuring WebSockets used for syncing storybook instances or sending events to storybook.
5
- */
6
- interface WebsocketsOptions {
7
- /**
8
- * The port WebSocket server will listen on. Defaults to 7007.
9
- */
10
- port?: number;
11
- /**
12
- * The host WebSocket server will bind to. Defaults to 'localhost'.
13
- */
14
- host?: string;
15
- }
16
4
  /**
17
5
  * Options for configuring Storybook with React Native.
18
6
  */
@@ -64,6 +52,9 @@ interface WithStorybookOptions {
64
52
  * When provided, creates a WebSocket server for real-time communication.
65
53
  * @param options.websockets.port - The port WebSocket server will listen on. Defaults to 7007.
66
54
  * @param options.websockets.host - The host WebSocket server will bind to. Defaults to 'localhost'.
55
+ * @param options.websockets.secured - Whether to use WSS/HTTPS for the channel server.
56
+ * @param options.websockets.key - TLS private key used when `secured` is true.
57
+ * @param options.websockets.cert - TLS certificate used when `secured` is true.
67
58
  * @param options.useJs - Whether to use JavaScript files for Storybook configuration instead of TypeScript.
68
59
  * When true, generates storybook.requires.js instead of storybook.requires.ts.
69
60
  * Defaults to false.
@@ -201,7 +201,8 @@ var require_generate = __commonJS({
201
201
  useJs = false,
202
202
  docTools = true,
203
203
  host = void 0,
204
- port = 7007
204
+ port = void 0,
205
+ secured = false
205
206
  }) {
206
207
  const channelHost = host === "auto" ? getLocalIPAddress() : host;
207
208
  const storybookRequiresLocation = path3.resolve(
@@ -284,11 +285,22 @@ var require_generate = __commonJS({
284
285
  const annotations = `[
285
286
  ${enhancers.join(",\n ")}
286
287
  ]`;
288
+ const hasWebsocketConfig = host !== void 0 || port !== void 0 || secured;
289
+ const websocketAssignmentLines = [];
290
+ if (channelHost) {
291
+ websocketAssignmentLines.push(`host: '${channelHost}',`);
292
+ }
293
+ if (hasWebsocketConfig) {
294
+ websocketAssignmentLines.push(`port: ${port ?? 7007},`);
295
+ websocketAssignmentLines.push(`secured: ${Boolean(secured)},`);
296
+ }
287
297
  const globalTypes = `
288
298
  declare global {
289
299
  var view: View;
290
300
  var STORIES: typeof normalizedStories;
291
- var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;
301
+ var STORYBOOK_WEBSOCKET:
302
+ | { host?: string; port?: number; secured?: boolean }
303
+ | undefined;
292
304
  var FEATURES: Features;
293
305
  }
294
306
  `;
@@ -306,7 +318,9 @@ ${useJs ? "" : globalTypes}
306
318
  const annotations = ${annotations};
307
319
 
308
320
  globalThis.STORIES = normalizedStories;
309
- ${channelHost ? `globalThis.STORYBOOK_WEBSOCKET = { host: '${channelHost}', port: ${port ?? 7007} };` : ""}
321
+ ${hasWebsocketConfig ? `globalThis.STORYBOOK_WEBSOCKET = {
322
+ ${websocketAssignmentLines.join("\n ")}
323
+ };` : ""}
310
324
 
311
325
  module?.hot?.accept?.();
312
326
  ${featuresAssignment ? `
@@ -574,6 +588,7 @@ var import_telemetry = require("storybook/internal/telemetry");
574
588
  // src/metro/channelServer.ts
575
589
  var import_ws2 = require("ws");
576
590
  var import_node_http = require("http");
591
+ var import_node_https = require("https");
577
592
  init_buildIndex();
578
593
 
579
594
  // src/metro/mcpServer.ts
@@ -632,11 +647,7 @@ function createMcpHandler(configPath, wss) {
632
647
  { McpServer },
633
648
  { ValibotJsonSchemaAdapter },
634
649
  { HttpTransport },
635
- {
636
- addListAllDocumentationTool,
637
- addGetDocumentationTool,
638
- addGetComponentStoryDocumentationTool
639
- },
650
+ { addListAllDocumentationTool, addGetDocumentationTool, addGetStoryDocumentationTool },
640
651
  { storyInstructions: storyInstructions2 },
641
652
  { buildIndex: buildIndex2 },
642
653
  valibot,
@@ -675,7 +686,7 @@ function createMcpHandler(configPath, wss) {
675
686
  ).withContext();
676
687
  addListAllDocumentationTool(server);
677
688
  addGetDocumentationTool(server);
678
- addGetComponentStoryDocumentationTool(server);
689
+ addGetStoryDocumentationTool(server);
679
690
  server.tool(
680
691
  {
681
692
  name: "get-storybook-story-instructions",
@@ -951,14 +962,20 @@ function createChannelServer({
951
962
  host = void 0,
952
963
  configPath,
953
964
  experimental_mcp = false,
954
- websockets = true
965
+ websockets = true,
966
+ secured = false,
967
+ ssl
955
968
  }) {
956
- const httpServer = (0, import_node_http.createServer)();
969
+ if (secured && (!ssl?.key || !ssl?.cert)) {
970
+ throw new Error("[Storybook] Secure channel server requires both `ssl.key` and `ssl.cert`.");
971
+ }
972
+ const httpServer = secured ? (0, import_node_https.createServer)(ssl) : (0, import_node_http.createServer)();
957
973
  const wss = websockets ? new import_ws2.WebSocketServer({ server: httpServer }) : null;
958
974
  const mcpServer = experimental_mcp ? createMcpHandler(configPath, wss ?? void 0) : null;
959
975
  const selectStorySyncEndpoint = wss ? createSelectStorySyncEndpoint(wss) : null;
960
976
  httpServer.on("request", async (req, res) => {
961
- const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
977
+ const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
978
+ const requestUrl = new URL(req.url ?? "/", `${protocol}://${req.headers.host ?? "localhost"}`);
962
979
  if (req.method === "OPTIONS") {
963
980
  res.writeHead(204);
964
981
  res.end();
@@ -1059,7 +1076,7 @@ function createChannelServer({
1059
1076
  }
1060
1077
  });
1061
1078
  httpServer.listen(port, host, () => {
1062
- const protocol = wss ? "WebSocket" : "HTTP";
1079
+ const protocol = wss ? secured ? "WSS" : "WebSocket" : secured ? "HTTPS" : "HTTP";
1063
1080
  console.log(`${protocol} server listening on ${host ?? "localhost"}:${port}`);
1064
1081
  });
1065
1082
  mcpServer?.preInit();
@@ -1125,12 +1142,20 @@ function withStorybook(config, options = {
1125
1142
  if (websockets || experimental_mcp) {
1126
1143
  const port = websockets === "auto" ? 7007 : websockets?.port ?? 7007;
1127
1144
  const host = websockets === "auto" ? "auto" : websockets?.host;
1145
+ const secured = Boolean(websockets && websockets !== "auto" && websockets.secured);
1128
1146
  createChannelServer({
1129
1147
  port,
1130
1148
  host: host === "auto" ? void 0 : host,
1131
1149
  configPath,
1132
1150
  experimental_mcp,
1133
- websockets: Boolean(websockets)
1151
+ websockets: Boolean(websockets),
1152
+ secured,
1153
+ ssl: websockets && websockets !== "auto" ? {
1154
+ key: websockets.key,
1155
+ cert: websockets.cert,
1156
+ ca: websockets.ca,
1157
+ passphrase: websockets.passphrase
1158
+ } : void 0
1134
1159
  });
1135
1160
  if (websockets) {
1136
1161
  (0, import_generate.generate)({
@@ -1138,7 +1163,8 @@ function withStorybook(config, options = {
1138
1163
  useJs,
1139
1164
  docTools,
1140
1165
  host,
1141
- port
1166
+ port,
1167
+ secured
1142
1168
  });
1143
1169
  } else {
1144
1170
  (0, import_generate.generate)({
package/dist/node.d.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import { WebSocketServer } from 'ws';
2
2
  import { StoryIndex } from 'storybook/internal/types';
3
3
 
4
+ interface ChannelServerSecureOptions {
5
+ ca?: string | Buffer | Array<string | Buffer>;
6
+ cert?: string | Buffer | Array<string | Buffer>;
7
+ key?: string | Buffer | Array<string | Buffer>;
8
+ passphrase?: string;
9
+ }
4
10
  /**
5
11
  * Options for creating a channel server.
6
12
  */
@@ -27,6 +33,15 @@ interface ChannelServerOptions {
27
33
  * When false, starts only the HTTP server endpoints.
28
34
  */
29
35
  websockets?: boolean;
36
+ /**
37
+ * Whether to use HTTPS/WSS for the channel server.
38
+ * When true, valid TLS credentials must be provided via `ssl`.
39
+ */
40
+ secured?: boolean;
41
+ /**
42
+ * TLS credentials used when `secured` is true.
43
+ */
44
+ ssl?: ChannelServerSecureOptions;
30
45
  }
31
46
  /**
32
47
  * Creates a channel server for syncing storybook instances and sending events.
@@ -43,9 +58,11 @@ interface ChannelServerOptions {
43
58
  * @param options.configPath - The path to the Storybook config folder.
44
59
  * @param options.experimental_mcp - Whether to enable MCP server support.
45
60
  * @param options.websockets - Whether to enable WebSocket server support.
61
+ * @param options.secured - Whether to use HTTPS/WSS for the channel server.
62
+ * @param options.ssl - TLS credentials used when `secured` is true.
46
63
  * @returns The created WebSocketServer instance, or null when websockets are disabled.
47
64
  */
48
- declare function createChannelServer({ port, host, configPath, experimental_mcp, websockets, }: ChannelServerOptions): WebSocketServer | null;
65
+ declare function createChannelServer({ port, host, configPath, experimental_mcp, websockets, secured, ssl, }: ChannelServerOptions): WebSocketServer | null;
49
66
 
50
67
  declare function buildIndex({ configPath }: {
51
68
  configPath: string;
package/dist/node.js CHANGED
@@ -353,6 +353,7 @@ module.exports = __toCommonJS(node_exports);
353
353
  // src/metro/channelServer.ts
354
354
  var import_ws2 = require("ws");
355
355
  var import_node_http = require("http");
356
+ var import_node_https = require("https");
356
357
  init_buildIndex();
357
358
 
358
359
  // src/metro/mcpServer.ts
@@ -411,11 +412,7 @@ function createMcpHandler(configPath, wss) {
411
412
  { McpServer },
412
413
  { ValibotJsonSchemaAdapter },
413
414
  { HttpTransport },
414
- {
415
- addListAllDocumentationTool,
416
- addGetDocumentationTool,
417
- addGetComponentStoryDocumentationTool
418
- },
415
+ { addListAllDocumentationTool, addGetDocumentationTool, addGetStoryDocumentationTool },
419
416
  { storyInstructions: storyInstructions2 },
420
417
  { buildIndex: buildIndex2 },
421
418
  valibot,
@@ -454,7 +451,7 @@ function createMcpHandler(configPath, wss) {
454
451
  ).withContext();
455
452
  addListAllDocumentationTool(server);
456
453
  addGetDocumentationTool(server);
457
- addGetComponentStoryDocumentationTool(server);
454
+ addGetStoryDocumentationTool(server);
458
455
  server.tool(
459
456
  {
460
457
  name: "get-storybook-story-instructions",
@@ -730,14 +727,20 @@ function createChannelServer({
730
727
  host = void 0,
731
728
  configPath,
732
729
  experimental_mcp = false,
733
- websockets = true
730
+ websockets = true,
731
+ secured = false,
732
+ ssl
734
733
  }) {
735
- const httpServer = (0, import_node_http.createServer)();
734
+ if (secured && (!ssl?.key || !ssl?.cert)) {
735
+ throw new Error("[Storybook] Secure channel server requires both `ssl.key` and `ssl.cert`.");
736
+ }
737
+ const httpServer = secured ? (0, import_node_https.createServer)(ssl) : (0, import_node_http.createServer)();
736
738
  const wss = websockets ? new import_ws2.WebSocketServer({ server: httpServer }) : null;
737
739
  const mcpServer = experimental_mcp ? createMcpHandler(configPath, wss ?? void 0) : null;
738
740
  const selectStorySyncEndpoint = wss ? createSelectStorySyncEndpoint(wss) : null;
739
741
  httpServer.on("request", async (req, res) => {
740
- const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
742
+ const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
743
+ const requestUrl = new URL(req.url ?? "/", `${protocol}://${req.headers.host ?? "localhost"}`);
741
744
  if (req.method === "OPTIONS") {
742
745
  res.writeHead(204);
743
746
  res.end();
@@ -838,7 +841,7 @@ function createChannelServer({
838
841
  }
839
842
  });
840
843
  httpServer.listen(port, host, () => {
841
- const protocol = wss ? "WebSocket" : "HTTP";
844
+ const protocol = wss ? secured ? "WSS" : "WebSocket" : secured ? "HTTPS" : "HTTP";
842
845
  console.log(`${protocol} server listening on ${host ?? "localhost"}:${port}`);
843
846
  });
844
847
  mcpServer?.preInit();
@@ -1,3 +1,5 @@
1
+ import { W as WebsocketsOptions } from '../index-6iAzVvXp.js';
2
+
1
3
  /**
2
4
  * Minimal compiler types for webpack/rspack compatibility.
3
5
  * We define these inline to avoid requiring @rspack/core or webpack as dependencies.
@@ -21,19 +23,6 @@ interface Compiler {
21
23
  };
22
24
  };
23
25
  }
24
- /**
25
- * Options for configuring WebSockets used for syncing storybook instances or sending events to storybook.
26
- */
27
- interface WebsocketsOptions {
28
- /**
29
- * The port WebSocket server will listen on. Defaults to 7007.
30
- */
31
- port?: number;
32
- /**
33
- * The host WebSocket server will bind to. Defaults to 'localhost'.
34
- */
35
- host?: string;
36
- }
37
26
  /**
38
27
  * Options for configuring the Storybook Repack plugin.
39
28
  */
@@ -201,7 +201,8 @@ var require_generate = __commonJS({
201
201
  useJs = false,
202
202
  docTools = true,
203
203
  host = void 0,
204
- port = 7007
204
+ port = void 0,
205
+ secured = false
205
206
  }) {
206
207
  const channelHost = host === "auto" ? getLocalIPAddress() : host;
207
208
  const storybookRequiresLocation = path3.resolve(
@@ -284,11 +285,22 @@ var require_generate = __commonJS({
284
285
  const annotations = `[
285
286
  ${enhancers.join(",\n ")}
286
287
  ]`;
288
+ const hasWebsocketConfig = host !== void 0 || port !== void 0 || secured;
289
+ const websocketAssignmentLines = [];
290
+ if (channelHost) {
291
+ websocketAssignmentLines.push(`host: '${channelHost}',`);
292
+ }
293
+ if (hasWebsocketConfig) {
294
+ websocketAssignmentLines.push(`port: ${port ?? 7007},`);
295
+ websocketAssignmentLines.push(`secured: ${Boolean(secured)},`);
296
+ }
287
297
  const globalTypes = `
288
298
  declare global {
289
299
  var view: View;
290
300
  var STORIES: typeof normalizedStories;
291
- var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;
301
+ var STORYBOOK_WEBSOCKET:
302
+ | { host?: string; port?: number; secured?: boolean }
303
+ | undefined;
292
304
  var FEATURES: Features;
293
305
  }
294
306
  `;
@@ -306,7 +318,9 @@ ${useJs ? "" : globalTypes}
306
318
  const annotations = ${annotations};
307
319
 
308
320
  globalThis.STORIES = normalizedStories;
309
- ${channelHost ? `globalThis.STORYBOOK_WEBSOCKET = { host: '${channelHost}', port: ${port ?? 7007} };` : ""}
321
+ ${hasWebsocketConfig ? `globalThis.STORYBOOK_WEBSOCKET = {
322
+ ${websocketAssignmentLines.join("\n ")}
323
+ };` : ""}
310
324
 
311
325
  module?.hot?.accept?.();
312
326
  ${featuresAssignment ? `
@@ -572,6 +586,7 @@ var import_generate = __toESM(require_generate());
572
586
  // src/metro/channelServer.ts
573
587
  var import_ws2 = require("ws");
574
588
  var import_node_http = require("http");
589
+ var import_node_https = require("https");
575
590
  init_buildIndex();
576
591
 
577
592
  // src/metro/mcpServer.ts
@@ -630,11 +645,7 @@ function createMcpHandler(configPath, wss) {
630
645
  { McpServer },
631
646
  { ValibotJsonSchemaAdapter },
632
647
  { HttpTransport },
633
- {
634
- addListAllDocumentationTool,
635
- addGetDocumentationTool,
636
- addGetComponentStoryDocumentationTool
637
- },
648
+ { addListAllDocumentationTool, addGetDocumentationTool, addGetStoryDocumentationTool },
638
649
  { storyInstructions: storyInstructions2 },
639
650
  { buildIndex: buildIndex2 },
640
651
  valibot,
@@ -673,7 +684,7 @@ function createMcpHandler(configPath, wss) {
673
684
  ).withContext();
674
685
  addListAllDocumentationTool(server);
675
686
  addGetDocumentationTool(server);
676
- addGetComponentStoryDocumentationTool(server);
687
+ addGetStoryDocumentationTool(server);
677
688
  server.tool(
678
689
  {
679
690
  name: "get-storybook-story-instructions",
@@ -949,14 +960,20 @@ function createChannelServer({
949
960
  host = void 0,
950
961
  configPath,
951
962
  experimental_mcp = false,
952
- websockets = true
963
+ websockets = true,
964
+ secured = false,
965
+ ssl
953
966
  }) {
954
- const httpServer = (0, import_node_http.createServer)();
967
+ if (secured && (!ssl?.key || !ssl?.cert)) {
968
+ throw new Error("[Storybook] Secure channel server requires both `ssl.key` and `ssl.cert`.");
969
+ }
970
+ const httpServer = secured ? (0, import_node_https.createServer)(ssl) : (0, import_node_http.createServer)();
955
971
  const wss = websockets ? new import_ws2.WebSocketServer({ server: httpServer }) : null;
956
972
  const mcpServer = experimental_mcp ? createMcpHandler(configPath, wss ?? void 0) : null;
957
973
  const selectStorySyncEndpoint = wss ? createSelectStorySyncEndpoint(wss) : null;
958
974
  httpServer.on("request", async (req, res) => {
959
- const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
975
+ const protocol = "encrypted" in req.socket && req.socket.encrypted ? "https" : "http";
976
+ const requestUrl = new URL(req.url ?? "/", `${protocol}://${req.headers.host ?? "localhost"}`);
960
977
  if (req.method === "OPTIONS") {
961
978
  res.writeHead(204);
962
979
  res.end();
@@ -1057,7 +1074,7 @@ function createChannelServer({
1057
1074
  }
1058
1075
  });
1059
1076
  httpServer.listen(port, host, () => {
1060
- const protocol = wss ? "WebSocket" : "HTTP";
1077
+ const protocol = wss ? secured ? "WSS" : "WebSocket" : secured ? "HTTPS" : "HTTP";
1061
1078
  console.log(`${protocol} server listening on ${host ?? "localhost"}:${port}`);
1062
1079
  });
1063
1080
  mcpServer?.preInit();
@@ -1109,6 +1126,7 @@ var StorybookPlugin = class {
1109
1126
  }) {
1110
1127
  const port = websockets === "auto" ? 7007 : websockets?.port ?? 7007;
1111
1128
  const host = websockets === "auto" ? "auto" : websockets?.host;
1129
+ const secured = Boolean(websockets && websockets !== "auto" && websockets.secured);
1112
1130
  if ((websockets || experimental_mcp) && !this.serverStarted) {
1113
1131
  this.serverStarted = true;
1114
1132
  createChannelServer({
@@ -1116,7 +1134,14 @@ var StorybookPlugin = class {
1116
1134
  host: host === "auto" ? void 0 : host,
1117
1135
  configPath,
1118
1136
  experimental_mcp,
1119
- websockets: Boolean(websockets)
1137
+ websockets: Boolean(websockets),
1138
+ secured,
1139
+ ssl: websockets && websockets !== "auto" ? {
1140
+ key: websockets.key,
1141
+ cert: websockets.cert,
1142
+ ca: websockets.ca,
1143
+ passphrase: websockets.passphrase
1144
+ } : void 0
1120
1145
  });
1121
1146
  }
1122
1147
  compiler.hooks.beforeCompile.tapPromise("StorybookPlugin", async () => {
@@ -1126,7 +1151,7 @@ var StorybookPlugin = class {
1126
1151
  configPath,
1127
1152
  useJs,
1128
1153
  docTools,
1129
- ...websockets ? { host, port } : {}
1154
+ ...websockets ? { host, port, secured } : {}
1130
1155
  });
1131
1156
  console.log("[StorybookPlugin] Generated storybook.requires");
1132
1157
  });
@@ -1141,7 +1166,10 @@ var StorybookPlugin = class {
1141
1166
  * and replace the config folder index with a stub component.
1142
1167
  */
1143
1168
  applyDisabled(compiler, configPath) {
1144
- const stubPath = require.resolve("@storybook/react-native/stub");
1169
+ const stubPath = path2.resolve(
1170
+ __dirname,
1171
+ __dirname.includes(`${path2.sep}src${path2.sep}`) ? "../stub.tsx" : "../stub.js"
1172
+ );
1145
1173
  const normalizedConfigPath = path2.resolve(configPath);
1146
1174
  new compiler.webpack.NormalModuleReplacementPlugin(/./, (resource) => {
1147
1175
  const request = resource.request;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/react-native",
3
- "version": "10.3.0-next.6",
3
+ "version": "10.3.1",
4
4
  "description": "A better way to develop React Native Components for your app",
5
5
  "keywords": [
6
6
  "react",
@@ -43,36 +43,36 @@
43
43
  "metro/**/*"
44
44
  ],
45
45
  "dependencies": {
46
- "@storybook/mcp": "^0.4.1",
47
- "@storybook/react": "10.3.0-alpha.14",
48
- "@storybook/react-native-theming": "^10.3.0-next.6",
49
- "@storybook/react-native-ui": "^10.3.0-next.6",
50
- "@storybook/react-native-ui-common": "^10.3.0-next.6",
46
+ "@storybook/mcp": "^0.6.1",
47
+ "@storybook/react": "^10.3.1",
48
+ "@storybook/react-native-theming": "^10.3.1",
49
+ "@storybook/react-native-ui": "^10.3.1",
50
+ "@storybook/react-native-ui-common": "^10.3.1",
51
51
  "@tmcp/adapter-valibot": "^0.1.4",
52
- "@tmcp/transport-http": "^0.8.0",
52
+ "@tmcp/transport-http": "^0.8.5",
53
53
  "commander": "^14.0.2",
54
- "dedent": "^1.7.0",
54
+ "dedent": "^1.7.2",
55
55
  "deepmerge": "^4.3.1",
56
56
  "esbuild-register": "^3.6.0",
57
57
  "glob": "^13.0.0",
58
58
  "react-native-url-polyfill": "^3.0.0",
59
59
  "setimmediate": "^1.0.5",
60
- "tmcp": "^1.16.0",
61
- "valibot": "^1.2.0",
62
- "ws": "^8.18.3"
60
+ "tmcp": "^1.19.3",
61
+ "valibot": "^1.3.1",
62
+ "ws": "^8.20.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/jest": "^29.4.3",
66
66
  "@types/react": "~19.2.14",
67
67
  "babel-jest": "^29.7.0",
68
- "babel-preset-expo": "^54.0.6",
68
+ "babel-preset-expo": "^55.0.13",
69
69
  "jest": "^29.7.0",
70
- "jest-expo": "~55.0.9",
71
- "jotai": "^2.17.1",
70
+ "jest-expo": "~55.0.11",
71
+ "jotai": "^2.19.0",
72
72
  "react": "19.2.0",
73
- "react-native": "0.83.2",
74
- "storybook": "10.3.0-alpha.14",
75
- "test-renderer": "^0.14.0",
73
+ "react-native": "0.83.4",
74
+ "storybook": "^10.3.1",
75
+ "test-renderer": "^0.15.0",
76
76
  "tsup": "^8.5.0",
77
77
  "typescript": "~5.9.3"
78
78
  },
package/readme.md CHANGED
@@ -461,7 +461,7 @@ You can enable MCP with or without websockets:
461
461
 
462
462
  ### websockets
463
463
 
464
- Type: `'auto' | { host: string?, port: number? }`, default: `undefined`
464
+ Type: `'auto' | { host?: string, port?: number, secured?: boolean, key?: string | Buffer, cert?: string | Buffer, ca?: string | Buffer | Array<string | Buffer>, passphrase?: string }`, default: `undefined`
465
465
 
466
466
  If specified, create a WebSocket server on startup. This allows you to sync up multiple devices to show the same story and [arg](https://storybook.js.org/docs/writing-stories/args) values connected to the story in the UI.
467
467
 
@@ -479,6 +479,18 @@ Type: `number`, default: `7007`
479
479
 
480
480
  The port on which to run the WebSocket, if specified.
481
481
 
482
+ ### websockets.secured
483
+
484
+ Type: `boolean`, default: `false`
485
+
486
+ When `true`, the channel server starts on `https` and upgrades WebSocket clients over `wss`.
487
+
488
+ ### websockets.key / websockets.cert
489
+
490
+ Type: `string | Buffer`, default: `undefined`
491
+
492
+ TLS private key and certificate used when `secured` is `true`.
493
+
482
494
  ## getStorybookUI options
483
495
 
484
496
  You can pass these parameters to getStorybookUI call in your storybook entry point:
@@ -55,7 +55,8 @@ async function generate({
55
55
  useJs = false,
56
56
  docTools = true,
57
57
  host = undefined,
58
- port = 7007,
58
+ port = undefined,
59
+ secured = false,
59
60
  }) {
60
61
  // here we want to get the ip address and pass it to rn storybook so that devices can connect over lan easily
61
62
  const channelHost = host === 'auto' ? getLocalIPAddress() : host;
@@ -165,11 +166,25 @@ async function generate({
165
166
  ${enhancers.join(',\n ')}
166
167
  ]`;
167
168
 
169
+ const hasWebsocketConfig = host !== undefined || port !== undefined || secured;
170
+ const websocketAssignmentLines = [];
171
+
172
+ if (channelHost) {
173
+ websocketAssignmentLines.push(`host: '${channelHost}',`);
174
+ }
175
+
176
+ if (hasWebsocketConfig) {
177
+ websocketAssignmentLines.push(`port: ${port ?? 7007},`);
178
+ websocketAssignmentLines.push(`secured: ${Boolean(secured)},`);
179
+ }
180
+
168
181
  const globalTypes = `
169
182
  declare global {
170
183
  var view: View;
171
184
  var STORIES: typeof normalizedStories;
172
- var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;
185
+ var STORYBOOK_WEBSOCKET:
186
+ | { host?: string; port?: number; secured?: boolean }
187
+ | undefined;
173
188
  var FEATURES: Features;
174
189
  }
175
190
  `;
@@ -188,7 +203,13 @@ ${useJs ? '' : globalTypes}
188
203
  const annotations = ${annotations};
189
204
 
190
205
  globalThis.STORIES = normalizedStories;
191
- ${channelHost ? `globalThis.STORYBOOK_WEBSOCKET = { host: '${channelHost}', port: ${port ?? 7007} };` : ''}
206
+ ${
207
+ hasWebsocketConfig
208
+ ? `globalThis.STORYBOOK_WEBSOCKET = {
209
+ ${websocketAssignmentLines.join('\n ')}
210
+ };`
211
+ : ''
212
+ }
192
213
 
193
214
  module?.hot?.accept?.();
194
215
  ${featuresAssignment ? `\n${featuresAssignment}\n` : ''}