@callstack/repack-dev-server 5.0.0-rc.9 → 5.0.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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021 Callstack
3
+ Copyright (c) 2025 Callstack
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,32 +1,27 @@
1
- <p align="center">
2
- <img src="https://raw.githubusercontent.com/callstack/repack/HEAD/logo.png">
3
- </p>
4
- <p align="center">
5
- A toolkit to build your React Native application with Rspack or Webpack.
6
- </p>
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/callstack/repack/HEAD/logo.png" width="650" alt="Re.Pack logo" />
3
+ <h3>A toolkit to build your React Native application with Rspack or Webpack.</h3>
4
+ </div>
5
+ <div align="center">
7
6
 
8
- ---
9
-
10
- [![Build Status][build-badge]][build]
11
- [![Version][version-badge]][version]
12
- [![MIT License][license-badge]][license]
7
+ [![mit licence][license-badge]][license]
8
+ [![npm downloads][npm-downloads-badge]][npm-downloads]
9
+ [![Chat][chat-badge]][chat]
13
10
  [![PRs Welcome][prs-welcome-badge]][prs-welcome]
14
- [![Code of Conduct][coc-badge]][coc]
15
11
 
16
- `@callstack/repack-dev-sever` is bundler-agnostic development server for React Native applications as part of `@callstack/repack`.
12
+ </div>
13
+
14
+ `@callstack/repack-dev-server` is a bundler-agnostic development server for React Native applications as part of [`@callstack/repack`](https://github.com/callstack/repack).
17
15
 
18
16
  Check out our website at https://re-pack.dev for more info and documentation or out GitHub: https://github.com/callstack/repack.
19
17
 
20
18
  <!-- badges -->
21
19
 
22
- [callstack-readme-with-love]: https://callstack.com/?utm_source=github.com&utm_medium=referral&utm_campaign=react-native-paper&utm_term=readme-with-love
23
- [build-badge]: https://img.shields.io/github/workflow/status/callstack/repack/CI/main?style=flat-square
24
- [build]: https://github.com/callstack/repack/actions/workflows/main.yml
25
- [version-badge]: https://img.shields.io/npm/v/@callstack/repack-dev-server?style=flat-square
26
- [version]: https://www.npmjs.com/package/@callstack/repack-dev-server
27
- [license-badge]: https://img.shields.io/npm/l/@callstack/repack-debugger-app?style=flat-square
28
- [license]: https://github.com/callstack/repack/blob/master/LICENSE
29
- [prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
20
+ [license-badge]: https://img.shields.io/npm/l/@callstack/repack?style=for-the-badge
21
+ [license]: https://github.com/callstack/repack/blob/main/LICENSE
22
+ [npm-downloads-badge]: https://img.shields.io/npm/dm/@callstack/repack?style=for-the-badge
23
+ [npm-downloads]: https://www.npmjs.com/package/@callstack/repack
24
+ [prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
30
25
  [prs-welcome]: ./CONTRIBUTING.md
31
- [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
32
- [coc]: https://github.com/callstack/repack/blob/master/CODE_OF_CONDUCT.md
26
+ [chat-badge]: https://img.shields.io/discord/426714625279524876.svg?style=for-the-badge
27
+ [chat]: https://discord.gg/Q4yr2rTWYF
@@ -11,6 +11,7 @@ import multipartPlugin from './plugins/multipart/multipartPlugin.js';
11
11
  import symbolicatePlugin from './plugins/symbolicate/sybmolicatePlugin.js';
12
12
  import wssPlugin from './plugins/wss/wssPlugin.js';
13
13
  import { Internal } from './types.js';
14
+ import { normalizeOptions } from './utils/normalizeOptions.js';
14
15
  /**
15
16
  * Create instance of development server, powered by Fastify.
16
17
  *
@@ -20,9 +21,10 @@ import { Internal } from './types.js';
20
21
  export async function createServer(config) {
21
22
  // biome-ignore lint/style/useConst: needed in fastify constructor
22
23
  let delegate;
24
+ const options = normalizeOptions(config.options);
23
25
  /** Fastify instance powering the development server. */
24
26
  const instance = Fastify({
25
- disableRequestLogging: !config.options.logRequests,
27
+ disableRequestLogging: options.disableRequestLogging,
26
28
  logger: {
27
29
  level: 'trace',
28
30
  stream: new Writable({
@@ -34,9 +36,10 @@ export async function createServer(config) {
34
36
  },
35
37
  }),
36
38
  },
37
- ...(config.options.https ? { https: config.options.https } : undefined),
39
+ ...(options.https ? { https: options.https } : {}),
38
40
  });
39
- delegate = await config.delegate({
41
+ delegate = config.delegate({
42
+ options,
40
43
  log: instance.log,
41
44
  notifyBuildStart: (platform) => {
42
45
  instance.wss.apiServer.send({
@@ -50,16 +53,16 @@ export async function createServer(config) {
50
53
  platform,
51
54
  });
52
55
  },
53
- broadcastToHmrClients: (event, platform, clientIds) => {
54
- instance.wss.hmrServer.send(event, platform, clientIds);
56
+ broadcastToHmrClients: (event) => {
57
+ instance.wss.hmrServer.send(event);
55
58
  },
56
59
  broadcastToMessageClients: ({ method, params }) => {
57
60
  instance.wss.messageServer.broadcast(method, params);
58
61
  },
59
62
  });
60
63
  const devMiddleware = createDevMiddleware({
61
- projectRoot: config.options.rootDir,
62
- serverBaseUrl: `http://${config.options.host}:${config.options.port}`,
64
+ projectRoot: options.rootDir,
65
+ serverBaseUrl: options.url,
63
66
  logger: instance.log,
64
67
  unstable_experiments: {
65
68
  // @ts-expect-error removed in 0.76, keep this for backkwards compatibility
@@ -70,11 +73,8 @@ export async function createServer(config) {
70
73
  await instance.register(fastifySensible);
71
74
  await instance.register(middie);
72
75
  await instance.register(wssPlugin, {
73
- options: {
74
- ...config.options,
75
- endpoints: devMiddleware.websocketEndpoints,
76
- },
77
76
  delegate,
77
+ endpoints: devMiddleware.websocketEndpoints,
78
78
  });
79
79
  await instance.register(multipartPlugin);
80
80
  await instance.register(apiPlugin, {
@@ -85,7 +85,7 @@ export async function createServer(config) {
85
85
  delegate,
86
86
  });
87
87
  await instance.register(devtoolsPlugin, {
88
- options: config.options,
88
+ rootDir: options.rootDir,
89
89
  });
90
90
  await instance.register(symbolicatePlugin, {
91
91
  delegate,
@@ -95,7 +95,7 @@ export async function createServer(config) {
95
95
  await instance.register(faviconPlugin);
96
96
  instance.addHook('onSend', async (request, reply, payload) => {
97
97
  reply.header('X-Content-Type-Options', 'nosniff');
98
- reply.header('X-React-Native-Project-Root', config.options.rootDir);
98
+ reply.header('X-React-Native-Project-Root', options.rootDir);
99
99
  const [pathname] = request.url.split('?');
100
100
  if (pathname.endsWith('.map')) {
101
101
  reply.header('Access-Control-Allow-Origin', 'devtools://devtools');
@@ -110,8 +110,8 @@ export async function createServer(config) {
110
110
  /** Start the development server. */
111
111
  async function start() {
112
112
  await instance.listen({
113
- port: config.options.port,
114
- host: config.options.host,
113
+ port: options.port,
114
+ host: options.host,
115
115
  });
116
116
  }
117
117
  /** Stop the development server. */
@@ -18,8 +18,8 @@ async function compilerPlugin(instance, { delegate }) {
18
18
  let { platform } = request.query;
19
19
  if (!filename) {
20
20
  // This technically should never happen - this route should not be called if file is missing.
21
- request.log.error('File was not provided');
22
- return reply.notFound();
21
+ request.log.debug('File was not provided');
22
+ return reply.notFound('File was not provided');
23
23
  }
24
24
  // Let consumer infer the platform. If function is not provided fallback
25
25
  // to platform query param.
@@ -29,6 +29,7 @@ async function compilerPlugin(instance, { delegate }) {
29
29
  multipart?.writeChunk({ 'Content-Type': 'application/json' }, JSON.stringify({
30
30
  done: completed,
31
31
  total,
32
+ status: 'Bundling with Re.Pack',
32
33
  }));
33
34
  };
34
35
  try {
@@ -45,7 +46,7 @@ async function compilerPlugin(instance, { delegate }) {
45
46
  }
46
47
  }
47
48
  catch (error) {
48
- request.log.error(error);
49
+ request.log.debug(error);
49
50
  return reply.notFound(error.message);
50
51
  }
51
52
  },
@@ -1,7 +1,6 @@
1
1
  import type { FastifyInstance } from 'fastify';
2
- import type { Server } from '../../types.js';
3
- declare function devtoolsPlugin(instance: FastifyInstance, { options }: {
4
- options: Server.Options;
2
+ declare function devtoolsPlugin(instance: FastifyInstance, { rootDir }: {
3
+ rootDir: string;
5
4
  }): Promise<void>;
6
5
  declare const _default: typeof devtoolsPlugin;
7
6
  export default _default;
@@ -1,9 +1,9 @@
1
1
  import { openStackFrameInEditorMiddleware, openURLMiddleware, } from '@react-native-community/cli-server-api';
2
2
  import fastifyPlugin from 'fastify-plugin';
3
- async function devtoolsPlugin(instance, { options }) {
3
+ async function devtoolsPlugin(instance, { rootDir }) {
4
4
  instance.use('/open-url', openURLMiddleware);
5
5
  instance.use('/open-stack-frame', openStackFrameInEditorMiddleware({
6
- watchFolders: [options.rootDir],
6
+ watchFolders: [rootDir],
7
7
  }));
8
8
  instance.route({
9
9
  method: ['GET', 'POST', 'PUT'],
@@ -2,7 +2,6 @@ import path from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import fastifyFavicon from 'fastify-favicon';
4
4
  import fastifyPlugin from 'fastify-plugin';
5
- // @ts-ignore
6
5
  const dirname = path.dirname(fileURLToPath(import.meta.url));
7
6
  const pathToImgDir = path.join(dirname, '../../../static');
8
7
  async function faviconPlugin(instance) {
@@ -1,35 +1,33 @@
1
1
  import type { IncomingMessage } from 'node:http';
2
2
  import type { Socket } from 'node:net';
3
3
  import type { FastifyInstance } from 'fastify';
4
- import { type ServerOptions, type WebSocket, WebSocketServer as WebSocketServerImpl } from 'ws';
5
- import type { WebSocketServerInterface } from './types.js';
4
+ import { type WebSocket, WebSocketServer as WebSocketServerImpl } from 'ws';
5
+ import type { WebSocketServerInterface, WebSocketServerOptions } from './types.js';
6
6
  /**
7
7
  * Abstract class for providing common logic (eg routing) for all WebSocket servers.
8
8
  *
9
9
  * @category Development server
10
10
  */
11
- export declare abstract class WebSocketServer implements WebSocketServerInterface {
11
+ export declare abstract class WebSocketServer<T extends WebSocket = WebSocket> implements WebSocketServerInterface {
12
12
  /** An instance of the underlying WebSocket server. */
13
13
  protected server: WebSocketServerImpl;
14
14
  /** Fastify instance from which {@link server} will receive upgrade connections. */
15
15
  protected fastify: FastifyInstance;
16
+ protected name: string;
16
17
  protected paths: string[];
18
+ protected clients: Map<string, T>;
19
+ protected nextClientId: number;
20
+ private timer;
17
21
  /**
18
22
  * Create a new instance of the WebSocketServer.
19
23
  * Any logging information, will be passed through standard `fastify.log` API.
20
24
  *
21
25
  * @param fastify Fastify instance to which the WebSocket will be attached to.
22
26
  * @param path Path on which this WebSocketServer will be accepting connections.
23
- * @param wssOptions WebSocket Server options.
27
+ * @param options WebSocketServer options.
24
28
  */
25
- constructor(fastify: FastifyInstance, path: string | string[], wssOptions?: Omit<ServerOptions, 'noServer' | 'server' | 'host' | 'port' | 'path'>);
29
+ constructor(fastify: FastifyInstance, options: WebSocketServerOptions);
26
30
  shouldUpgrade(pathname: string): boolean;
27
31
  upgrade(request: IncomingMessage, socket: Socket, head: Buffer): void;
28
- /**
29
- * Process incoming WebSocket connection.
30
- *
31
- * @param socket Incoming WebSocket connection.
32
- * @param request Upgrade request for the connection.
33
- */
34
- abstract onConnection(socket: WebSocket, request: IncomingMessage): void;
32
+ onConnection(socket: T, _request: IncomingMessage): string;
35
33
  }
@@ -1,4 +1,4 @@
1
- import { WebSocketServer as WebSocketServerImpl, } from 'ws';
1
+ import { WebSocketServer as WebSocketServerImpl } from 'ws';
2
2
  /**
3
3
  * Abstract class for providing common logic (eg routing) for all WebSocket servers.
4
4
  *
@@ -11,13 +11,29 @@ export class WebSocketServer {
11
11
  *
12
12
  * @param fastify Fastify instance to which the WebSocket will be attached to.
13
13
  * @param path Path on which this WebSocketServer will be accepting connections.
14
- * @param wssOptions WebSocket Server options.
14
+ * @param options WebSocketServer options.
15
15
  */
16
- constructor(fastify, path, wssOptions = {}) {
16
+ constructor(fastify, options) {
17
+ this.nextClientId = 0;
18
+ this.timer = null;
17
19
  this.fastify = fastify;
18
- this.server = new WebSocketServerImpl({ noServer: true, ...wssOptions });
20
+ this.name = options.name;
21
+ this.paths = Array.isArray(options.path) ? options.path : [options.path];
22
+ this.server = new WebSocketServerImpl({ noServer: true, ...options.wss });
19
23
  this.server.on('connection', this.onConnection.bind(this));
20
- this.paths = Array.isArray(path) ? path : [path];
24
+ this.clients = new Map();
25
+ // setup heartbeat timer
26
+ this.timer = setInterval(() => {
27
+ this.clients.forEach((socket) => {
28
+ if (!socket.isAlive) {
29
+ socket.terminate();
30
+ }
31
+ else {
32
+ socket.isAlive = false;
33
+ socket.ping(() => { });
34
+ }
35
+ });
36
+ }, 30000);
21
37
  }
22
38
  shouldUpgrade(pathname) {
23
39
  return this.paths.includes(pathname);
@@ -27,4 +43,25 @@ export class WebSocketServer {
27
43
  this.server.emit('connection', webSocket, request);
28
44
  });
29
45
  }
46
+ onConnection(socket, _request) {
47
+ const clientId = `client#${this.nextClientId++}`;
48
+ this.clients.set(clientId, socket);
49
+ this.fastify.log.debug({ msg: `${this.name} client connected`, clientId });
50
+ const errorHandler = () => {
51
+ this.fastify.log.debug({
52
+ msg: `${this.name} client disconnected`,
53
+ clientId,
54
+ });
55
+ socket.removeAllListeners(); // should we do this?
56
+ this.clients.delete(clientId);
57
+ };
58
+ socket.addEventListener('error', errorHandler);
59
+ socket.addEventListener('close', errorHandler);
60
+ // heartbeat
61
+ socket.isAlive = true;
62
+ socket.on('pong', () => {
63
+ socket.isAlive = true;
64
+ });
65
+ return clientId;
66
+ }
30
67
  }
@@ -1,5 +1,4 @@
1
1
  import type { FastifyInstance } from 'fastify';
2
- import type WebSocket from 'ws';
3
2
  import { WebSocketServer } from '../WebSocketServer.js';
4
3
  /**
5
4
  * Class for creating a WebSocket server for API clients.
@@ -8,8 +7,6 @@ import { WebSocketServer } from '../WebSocketServer.js';
8
7
  * @category Development server
9
8
  */
10
9
  export declare class WebSocketApiServer extends WebSocketServer {
11
- private clients;
12
- private nextClientId;
13
10
  /**
14
11
  * Create new instance of WebSocketApiServer and attach it to the given Fastify instance.
15
12
  * Any logging information, will be passed through standard `fastify.log` API.
@@ -23,10 +20,4 @@ export declare class WebSocketApiServer extends WebSocketServer {
23
20
  * @param event Event string or object to send.
24
21
  */
25
22
  send(event: any): void;
26
- /**
27
- * Process new WebSocket connection from client application.
28
- *
29
- * @param socket Incoming client's WebSocket connection.
30
- */
31
- onConnection(socket: WebSocket): void;
32
23
  }
@@ -13,9 +13,7 @@ export class WebSocketApiServer extends WebSocketServer {
13
13
  * @param fastify Fastify instance to attach the WebSocket server to.
14
14
  */
15
15
  constructor(fastify) {
16
- super(fastify, '/api');
17
- this.clients = new Map();
18
- this.nextClientId = 0;
16
+ super(fastify, { name: 'API', path: '/api' });
19
17
  }
20
18
  /**
21
19
  * Send message to all connected API clients.
@@ -33,24 +31,4 @@ export class WebSocketApiServer extends WebSocketServer {
33
31
  }
34
32
  }
35
33
  }
36
- /**
37
- * Process new WebSocket connection from client application.
38
- *
39
- * @param socket Incoming client's WebSocket connection.
40
- */
41
- onConnection(socket) {
42
- const clientId = `client#${this.nextClientId++}`;
43
- this.clients.set(clientId, socket);
44
- this.fastify.log.debug({ msg: 'API client connected', clientId });
45
- this.clients.set(clientId, socket);
46
- const onClose = () => {
47
- this.fastify.log.debug({
48
- msg: 'API client disconnected',
49
- clientId,
50
- });
51
- this.clients.delete(clientId);
52
- };
53
- socket.addEventListener('error', onClose);
54
- socket.addEventListener('close', onClose);
55
- }
56
34
  }
@@ -1,3 +1,4 @@
1
+ import type { IncomingMessage } from 'node:http';
1
2
  import type { FastifyInstance } from 'fastify';
2
3
  import type WebSocket from 'ws';
3
4
  import { WebSocketServer } from '../WebSocketServer.js';
@@ -8,8 +9,6 @@ import { WebSocketServer } from '../WebSocketServer.js';
8
9
  * @category Development server
9
10
  */
10
11
  export declare class WebSocketDevClientServer extends WebSocketServer {
11
- private clients;
12
- private nextClientId;
13
12
  /**
14
13
  * Create new instance of WebSocketDevClientServer and attach it to the given Fastify instance.
15
14
  * Any logging information, will be passed through standard `fastify.log` API.
@@ -23,10 +22,5 @@ export declare class WebSocketDevClientServer extends WebSocketServer {
23
22
  * @param message Stringified client message.
24
23
  */
25
24
  processMessage(message: string): void;
26
- /**
27
- * Process new WebSocket connection from client application.
28
- *
29
- * @param socket Incoming client's WebSocket connection.
30
- */
31
- onConnection(socket: WebSocket): void;
25
+ onConnection(socket: WebSocket, request: IncomingMessage): string;
32
26
  }
@@ -13,9 +13,7 @@ export class WebSocketDevClientServer extends WebSocketServer {
13
13
  * @param fastify Fastify instance to attach the WebSocket server to.
14
14
  */
15
15
  constructor(fastify) {
16
- super(fastify, '/__client');
17
- this.clients = new Map();
18
- this.nextClientId = 0;
16
+ super(fastify, { name: 'React Native', path: '/__client' });
19
17
  }
20
18
  /**
21
19
  * Process client message.
@@ -44,26 +42,11 @@ export class WebSocketDevClientServer extends WebSocketServer {
44
42
  this.fastify.log.warn({ msg: 'Unknown client message', message });
45
43
  }
46
44
  }
47
- /**
48
- * Process new WebSocket connection from client application.
49
- *
50
- * @param socket Incoming client's WebSocket connection.
51
- */
52
- onConnection(socket) {
53
- const clientId = `client#${this.nextClientId++}`;
54
- this.clients.set(clientId, socket);
55
- this.fastify.log.debug({ msg: 'React Native client connected', clientId });
56
- const onClose = () => {
57
- this.fastify.log.debug({
58
- msg: 'React Native client disconnected',
59
- clientId,
60
- });
61
- this.clients.delete(clientId);
62
- };
63
- socket.addEventListener('error', onClose);
64
- socket.addEventListener('close', onClose);
45
+ onConnection(socket, request) {
46
+ const clientId = super.onConnection(socket, request);
65
47
  socket.addEventListener('message', (event) => {
66
48
  this.processMessage(event.data.toString());
67
49
  });
50
+ return clientId;
68
51
  }
69
52
  }
@@ -1,3 +1,4 @@
1
+ import type { IncomingMessage } from 'node:http';
1
2
  import type { FastifyInstance } from 'fastify';
2
3
  import type WebSocket from 'ws';
3
4
  import { WebSocketServer } from '../WebSocketServer.js';
@@ -36,8 +37,6 @@ export interface EventMessage {
36
37
  export declare class WebSocketEventsServer extends WebSocketServer {
37
38
  private config;
38
39
  static readonly PROTOCOL_VERSION = 2;
39
- private clients;
40
- private nextClientId;
41
40
  /**
42
41
  * Create new instance of WebSocketHMRServer and attach it to the given Fastify instance.
43
42
  * Any logging information, will be passed through standard `fastify.log` API.
@@ -66,10 +65,5 @@ export declare class WebSocketEventsServer extends WebSocketServer {
66
65
  * @param event Event message to broadcast.
67
66
  */
68
67
  broadcastEvent(event: EventMessage): void;
69
- /**
70
- * Process new client's WebSocket connection.
71
- *
72
- * @param socket Incoming WebSocket connection.
73
- */
74
- onConnection(socket: WebSocket): void;
68
+ onConnection(socket: WebSocket, request: IncomingMessage): string;
75
69
  }
@@ -16,14 +16,16 @@ export class WebSocketEventsServer extends WebSocketServer {
16
16
  * @param config Configuration object.
17
17
  */
18
18
  constructor(fastify, config) {
19
- super(fastify, '/events', {
20
- verifyClient: (({ origin }) => {
21
- return /^(https?:\/\/localhost|file:\/\/)/.test(origin);
22
- }),
19
+ super(fastify, {
20
+ name: 'Events',
21
+ path: '/events',
22
+ wss: {
23
+ verifyClient: (({ origin }) => {
24
+ return /^(https?:\/\/localhost|file:\/\/)/.test(origin);
25
+ }),
26
+ },
23
27
  });
24
28
  this.config = config;
25
- this.clients = new Map();
26
- this.nextClientId = 0;
27
29
  }
28
30
  /**
29
31
  * Parse received command message from connected client.
@@ -118,22 +120,8 @@ export class WebSocketEventsServer extends WebSocketServer {
118
120
  }
119
121
  }
120
122
  }
121
- /**
122
- * Process new client's WebSocket connection.
123
- *
124
- * @param socket Incoming WebSocket connection.
125
- */
126
- onConnection(socket) {
127
- const clientId = `client#${this.nextClientId++}`;
128
- this.clients.set(clientId, socket);
129
- this.fastify.log.debug({ msg: 'Events client connected', clientId });
130
- const onClose = () => {
131
- this.fastify.log.debug({ msg: 'Events client disconnected', clientId });
132
- socket.removeAllListeners();
133
- this.clients.delete(clientId);
134
- };
135
- socket.addEventListener('error', onClose);
136
- socket.addEventListener('close', onClose);
123
+ onConnection(socket, request) {
124
+ const clientId = super.onConnection(socket, request);
137
125
  socket.addEventListener('message', (event) => {
138
126
  const message = this.parseMessage(event.data.toString());
139
127
  if (!message) {
@@ -157,6 +145,7 @@ export class WebSocketEventsServer extends WebSocketServer {
157
145
  });
158
146
  }
159
147
  });
148
+ return clientId;
160
149
  }
161
150
  }
162
151
  WebSocketEventsServer.PROTOCOL_VERSION = 2;
@@ -1,37 +1,22 @@
1
- import type { IncomingMessage } from 'node:http';
2
1
  import type { FastifyInstance } from 'fastify';
3
- import type WebSocket from 'ws';
4
2
  import { WebSocketServer } from '../WebSocketServer.js';
5
- import type { HmrDelegate } from '../types.js';
6
3
  /**
7
4
  * Class for creating a WebSocket server for Hot Module Replacement.
8
5
  *
9
6
  * @category Development server
10
7
  */
11
8
  export declare class WebSocketHMRServer extends WebSocketServer {
12
- private delegate;
13
- private clients;
14
- private nextClientId;
15
9
  /**
16
10
  * Create new instance of WebSocketHMRServer and attach it to the given Fastify instance.
17
11
  * Any logging information, will be passed through standard `fastify.log` API.
18
12
  *
19
13
  * @param fastify Fastify instance to attach the WebSocket server to.
20
- * @param delegate HMR delegate instance.
21
14
  */
22
- constructor(fastify: FastifyInstance, delegate: HmrDelegate);
15
+ constructor(fastify: FastifyInstance);
23
16
  /**
24
17
  * Send action to all connected HMR clients.
25
18
  *
26
19
  * @param event Event to send to the clients.
27
- * @param platform Platform of clients to send the event to.
28
- * @param clientIds Ids of clients who should receive the event.
29
20
  */
30
- send(event: any, platform: string, clientIds?: string[]): void;
31
- /**
32
- * Process new WebSocket connection from HMR client.
33
- *
34
- * @param socket Incoming HMR client's WebSocket connection.
35
- */
36
- onConnection(socket: WebSocket, request: IncomingMessage): void;
21
+ send(event: any): void;
37
22
  }
@@ -1,4 +1,3 @@
1
- import { URL } from 'node:url';
2
1
  import { WebSocketServer } from '../WebSocketServer.js';
3
2
  /**
4
3
  * Class for creating a WebSocket server for Hot Module Replacement.
@@ -11,28 +10,21 @@ export class WebSocketHMRServer extends WebSocketServer {
11
10
  * Any logging information, will be passed through standard `fastify.log` API.
12
11
  *
13
12
  * @param fastify Fastify instance to attach the WebSocket server to.
14
- * @param delegate HMR delegate instance.
15
13
  */
16
- constructor(fastify, delegate) {
17
- super(fastify, delegate.getUriPath());
18
- this.delegate = delegate;
19
- this.clients = new Map();
20
- this.nextClientId = 0;
14
+ constructor(fastify) {
15
+ super(fastify, {
16
+ name: 'HMR',
17
+ path: '/__hmr',
18
+ });
21
19
  }
22
20
  /**
23
21
  * Send action to all connected HMR clients.
24
22
  *
25
23
  * @param event Event to send to the clients.
26
- * @param platform Platform of clients to send the event to.
27
- * @param clientIds Ids of clients who should receive the event.
28
24
  */
29
- send(event, platform, clientIds) {
25
+ send(event) {
30
26
  const data = typeof event === 'string' ? event : JSON.stringify(event);
31
- for (const [key, socket] of this.clients) {
32
- if (key.platform !== platform ||
33
- !(clientIds ?? [key.clientId]).includes(key.clientId)) {
34
- continue;
35
- }
27
+ this.clients.forEach((socket) => {
36
28
  try {
37
29
  socket.send(data);
38
30
  }
@@ -41,42 +33,8 @@ export class WebSocketHMRServer extends WebSocketServer {
41
33
  msg: 'Cannot send action to client',
42
34
  event,
43
35
  error,
44
- ...key,
45
36
  });
46
37
  }
47
- }
48
- }
49
- /**
50
- * Process new WebSocket connection from HMR client.
51
- *
52
- * @param socket Incoming HMR client's WebSocket connection.
53
- */
54
- onConnection(socket, request) {
55
- const { searchParams } = new URL(request.url || '', 'http://localhost');
56
- const platform = searchParams.get('platform');
57
- if (!platform) {
58
- this.fastify.log.debug({
59
- msg: 'HMR connection disconnected - missing platform',
60
- });
61
- socket.close();
62
- return;
63
- }
64
- const clientId = `client#${this.nextClientId++}`;
65
- const client = {
66
- clientId,
67
- platform,
68
- };
69
- this.clients.set(client, socket);
70
- this.fastify.log.debug({ msg: 'HMR client connected', ...client });
71
- const onClose = () => {
72
- this.fastify.log.debug({
73
- msg: 'HMR client disconnected',
74
- ...client,
75
- });
76
- this.clients.delete(client);
77
- };
78
- socket.addEventListener('error', onClose);
79
- socket.addEventListener('close', onClose);
80
- this.delegate.onClientConnected(platform, clientId);
38
+ });
81
39
  }
82
40
  }
@@ -21,9 +21,6 @@ export interface ReactNativeMessage {
21
21
  error?: Error;
22
22
  params?: Record<string, any>;
23
23
  }
24
- type WebSocketWithUpgradeReq = WebSocket & {
25
- upgradeReq?: IncomingMessage;
26
- };
27
24
  /**
28
25
  * Class for creating a WebSocket server and sending messages between development server
29
26
  * and the React Native applications.
@@ -56,8 +53,7 @@ export declare class WebSocketMessageServer extends WebSocketServer {
56
53
  * @returns True if message is a response.
57
54
  */
58
55
  static isResponse(message: Partial<ReactNativeMessage>): boolean;
59
- private clients;
60
- private nextClientId;
56
+ private upgradeRequests;
61
57
  /**
62
58
  * Create new instance of WebSocketMessageServer and attach it to the given Fastify instance.
63
59
  * Any logging information, will be passed through standard `fastify.log` API.
@@ -80,7 +76,7 @@ export declare class WebSocketMessageServer extends WebSocketServer {
80
76
  * @param clientId Id of the client.
81
77
  * @returns WebSocket connection.
82
78
  */
83
- getClientSocket(clientId: string): WebSocketWithUpgradeReq;
79
+ getClientSocket(clientId: string): WebSocket.WebSocket;
84
80
  /**
85
81
  * Process error by sending an error message to the client whose message caused the error
86
82
  * to occur.
@@ -128,12 +124,5 @@ export declare class WebSocketMessageServer extends WebSocketServer {
128
124
  * @param params Method parameters.
129
125
  */
130
126
  broadcast(method: string, params?: Record<string, any>): void;
131
- /**
132
- * Process new client's WebSocket connection.
133
- *
134
- * @param socket Incoming WebSocket connection.
135
- * @param request Upgrade request for the connection.
136
- */
137
- onConnection(socket: WebSocket, request: IncomingMessage): void;
127
+ onConnection(socket: WebSocket, request: IncomingMessage): string;
138
128
  }
139
- export {};
@@ -49,9 +49,8 @@ export class WebSocketMessageServer extends WebSocketServer {
49
49
  * @param fastify Fastify instance to attach the WebSocket server to.
50
50
  */
51
51
  constructor(fastify) {
52
- super(fastify, '/message');
53
- this.clients = new Map();
54
- this.nextClientId = 0;
52
+ super(fastify, { name: 'Message', path: '/message' });
53
+ this.upgradeRequests = {};
55
54
  }
56
55
  /**
57
56
  * Parse stringified message into a {@link ReactNativeMessage}.
@@ -202,9 +201,9 @@ export class WebSocketMessageServer extends WebSocketServer {
202
201
  break;
203
202
  case 'getpeers': {
204
203
  const output = {};
205
- this.clients.forEach((peerSocket, peerId) => {
204
+ this.clients.forEach((_, peerId) => {
206
205
  if (clientId !== peerId) {
207
- const { searchParams } = new URL(peerSocket.upgradeReq?.url || '');
206
+ const { searchParams } = new URL(this.upgradeRequests[peerId]?.url || '');
208
207
  output[peerId] = [...searchParams.entries()].reduce((acc, [key, value]) => {
209
208
  acc[key] = value;
210
209
  return acc;
@@ -271,25 +270,9 @@ export class WebSocketMessageServer extends WebSocketServer {
271
270
  broadcast(method, params) {
272
271
  this.sendBroadcast(undefined, { method, params });
273
272
  }
274
- /**
275
- * Process new client's WebSocket connection.
276
- *
277
- * @param socket Incoming WebSocket connection.
278
- * @param request Upgrade request for the connection.
279
- */
280
273
  onConnection(socket, request) {
281
- const clientId = `client#${this.nextClientId++}`;
282
- const client = socket;
283
- client.upgradeReq = request;
284
- this.clients.set(clientId, client);
285
- this.fastify.log.debug({ msg: 'Message client connected', clientId });
286
- const onClose = () => {
287
- this.fastify.log.debug({ msg: 'Message client disconnected', clientId });
288
- socket.removeAllListeners();
289
- this.clients.delete(clientId);
290
- };
291
- socket.addEventListener('error', onClose);
292
- socket.addEventListener('close', onClose);
274
+ const clientId = super.onConnection(socket, request);
275
+ this.upgradeRequests[clientId] = request;
293
276
  socket.addEventListener('message', (event) => {
294
277
  const message = this.parseMessage(event.data.toString(),
295
278
  // @ts-ignore
@@ -328,6 +311,7 @@ export class WebSocketMessageServer extends WebSocketServer {
328
311
  this.handleError(clientId, message, error);
329
312
  }
330
313
  });
314
+ return clientId;
331
315
  }
332
316
  }
333
317
  WebSocketMessageServer.PROTOCOL_VERSION = 2;
@@ -1,22 +1,12 @@
1
1
  import type { IncomingMessage } from 'node:http';
2
2
  import type { Socket } from 'node:net';
3
- /**
4
- * Delegate with implementation for HMR-specific functions.
5
- */
6
- export interface HmrDelegate {
7
- /** Get URI under which HMR server will be running, e.g: `/hmr` */
8
- getUriPath: () => string;
9
- /**
10
- * Callback for when the new HMR client is connected.
11
- *
12
- * Useful for running initial synchronization or any other side effect.
13
- *
14
- * @param platform Platform of the connected client.
15
- * @param clientId Id of the connected client.
16
- */
17
- onClientConnected: (platform: string, clientId: string) => void;
18
- }
3
+ import type { ServerOptions } from 'ws';
19
4
  export interface WebSocketServerInterface {
20
5
  shouldUpgrade(pathname: string): boolean;
21
6
  upgrade(request: IncomingMessage, socket: Socket, head: Buffer): void;
22
7
  }
8
+ export type WebSocketServerOptions = {
9
+ name: string;
10
+ path: string | string[];
11
+ wss?: Omit<ServerOptions, 'noServer' | 'server' | 'host' | 'port' | 'path'>;
12
+ };
@@ -1,4 +1,5 @@
1
1
  import type { FastifyInstance } from 'fastify';
2
+ import type { WebSocketServer } from 'ws';
2
3
  import type { Server } from '../../types.js';
3
4
  import { WebSocketRouter } from './WebSocketRouter.js';
4
5
  import { WebSocketServerAdapter } from './WebSocketServerAdapter.js';
@@ -21,9 +22,14 @@ declare module 'fastify' {
21
22
  };
22
23
  }
23
24
  }
24
- declare function wssPlugin(instance: FastifyInstance, { options, delegate, }: {
25
- options: Server.Options;
25
+ declare module 'ws' {
26
+ interface WebSocket {
27
+ isAlive?: boolean;
28
+ }
29
+ }
30
+ declare function wssPlugin(instance: FastifyInstance, { endpoints, }: {
26
31
  delegate: Server.Delegate;
32
+ endpoints?: Record<string, WebSocketServer>;
27
33
  }): Promise<void>;
28
34
  declare const _default: typeof wssPlugin;
29
35
  export default _default;
@@ -12,7 +12,7 @@ import { WebSocketMessageServer } from './servers/WebSocketMessageServer.js';
12
12
  */
13
13
  const WS_DEVICE_URL = '/inspector/device';
14
14
  const WS_DEBUGGER_URL = '/inspector/debug';
15
- async function wssPlugin(instance, { options, delegate, }) {
15
+ async function wssPlugin(instance, { endpoints, }) {
16
16
  const router = new WebSocketRouter(instance);
17
17
  const devClientServer = new WebSocketDevClientServer(instance);
18
18
  const messageServer = new WebSocketMessageServer(instance);
@@ -20,10 +20,10 @@ async function wssPlugin(instance, { options, delegate, }) {
20
20
  webSocketMessageServer: messageServer,
21
21
  });
22
22
  const apiServer = new WebSocketApiServer(instance);
23
- const hmrServer = new WebSocketHMRServer(instance, delegate.hmr);
23
+ const hmrServer = new WebSocketHMRServer(instance);
24
24
  // @react-native/dev-middleware servers
25
- const deviceConnectionServer = new WebSocketServerAdapter(instance, WS_DEVICE_URL, options.endpoints?.[WS_DEVICE_URL]);
26
- const debuggerConnectionServer = new WebSocketServerAdapter(instance, WS_DEBUGGER_URL, options.endpoints?.[WS_DEBUGGER_URL]);
25
+ const deviceConnectionServer = new WebSocketServerAdapter(instance, WS_DEVICE_URL, endpoints?.[WS_DEVICE_URL]);
26
+ const debuggerConnectionServer = new WebSocketServerAdapter(instance, WS_DEBUGGER_URL, endpoints?.[WS_DEBUGGER_URL]);
27
27
  router.registerServer(devClientServer);
28
28
  router.registerServer(messageServer);
29
29
  router.registerServer(eventsServer);
package/dist/types.d.ts CHANGED
@@ -1,11 +1,29 @@
1
+ import type { ServerOptions as HttpsServerOptions } from 'node:https';
1
2
  import type { FastifyBaseLogger } from 'fastify';
2
- import type { WebSocketServer } from 'ws';
3
3
  import type { CompilerDelegate } from './plugins/compiler/types.js';
4
4
  import type { CodeFrame, InputStackFrame, ReactNativeStackFrame, StackFrame, SymbolicatorDelegate, SymbolicatorResults } from './plugins/symbolicate/types.js';
5
- import type { HmrDelegate } from './plugins/wss/types.js';
5
+ import type { NormalizedOptions } from './utils/normalizeOptions.js';
6
6
  export type { CompilerDelegate };
7
7
  export type { CodeFrame, InputStackFrame, ReactNativeStackFrame, StackFrame, SymbolicatorDelegate, SymbolicatorResults, };
8
- export type { HmrDelegate };
8
+ export interface DevServerOptions {
9
+ /**
10
+ * Hostname or IP address under which to run the development server.
11
+ * Can be 'local-ip', 'local-ipv4', 'local-ipv6' or a custom string.
12
+ * When left unspecified, it will listen on all available network interfaces.
13
+ */
14
+ host?: 'local-ip' | 'local-ipv4' | 'local-ipv6' | string;
15
+ /** Port under which to run the development server. */
16
+ port?: number;
17
+ /** Whether to enable Hot Module Replacement. */
18
+ hot?: boolean;
19
+ /** Options for running the server as HTTPS. If `undefined`, the server will run as HTTP. */
20
+ server?: 'http' | 'https' | {
21
+ type: 'http';
22
+ } | {
23
+ type: 'https';
24
+ options?: HttpsServerOptions;
25
+ };
26
+ }
9
27
  export declare namespace Server {
10
28
  /** Development server configuration. */
11
29
  interface Config {
@@ -15,25 +33,9 @@ export declare namespace Server {
15
33
  delegate: (context: DelegateContext) => Delegate;
16
34
  }
17
35
  /** Development server options. */
18
- interface Options {
36
+ interface Options extends DevServerOptions {
19
37
  /** Root directory of the project. */
20
38
  rootDir: string;
21
- /** Port under which to run the development server. */
22
- port: number;
23
- /**
24
- * Hostname or IP address under which to run the development server.
25
- * When left unspecified, it will listen on all available network interfaces, similarly to listening on '0.0.0.0'.
26
- */
27
- host?: string;
28
- /** Options for running the server as HTTPS. If `undefined`, the server will run as HTTP. */
29
- https?: {
30
- /** Path to certificate when running server as HTTPS. */
31
- cert?: string;
32
- /** Path to certificate key when running server as HTTPS. */
33
- key?: string;
34
- };
35
- /** Additional endpoints with pre-configured servers */
36
- endpoints?: Record<string, WebSocketServer>;
37
39
  /** Whether to enable logging requests. */
38
40
  logRequests?: boolean;
39
41
  }
@@ -47,8 +49,6 @@ export declare namespace Server {
47
49
  symbolicator: SymbolicatorDelegate;
48
50
  /** A logger delegate. */
49
51
  logger: LoggerDelegate;
50
- /** An HMR delegate. */
51
- hmr: HmrDelegate;
52
52
  /** An messages delegate. */
53
53
  messages: MessagesDelegate;
54
54
  /** An API delegate. */
@@ -60,6 +60,8 @@ export declare namespace Server {
60
60
  * Allows to emit logs, notify about compilation events and broadcast events to connected clients.
61
61
  */
62
62
  interface DelegateContext {
63
+ /** Normalized development server options. */
64
+ options: NormalizedOptions;
63
65
  /** A logger instance, useful for emitting logs from the delegate. */
64
66
  log: FastifyBaseLogger;
65
67
  /** Send notification about compilation start for given `platform`. */
@@ -70,11 +72,8 @@ export declare namespace Server {
70
72
  * Broadcast arbitrary event to all connected HMR clients for given `platform`.
71
73
  *
72
74
  * @param event Arbitrary event to broadcast.
73
- * @param platform Platform of the clients to which broadcast should be sent.
74
- * @param clientIds Ids of the client to which broadcast should be sent.
75
- * If `undefined` the broadcast will be sent to all connected clients for the given `platform`.
76
75
  */
77
- broadcastToHmrClients: <E = any>(event: E, platform: string, clientIds?: string[]) => void;
76
+ broadcastToHmrClients: <E = any>(event: E) => void;
78
77
  /**
79
78
  * Broadcast arbitrary method-like event to all connected message clients.
80
79
  *
@@ -0,0 +1,12 @@
1
+ import type { ServerOptions as HttpsServerOptions } from 'node:https';
2
+ import type { Server } from '../types.js';
3
+ export interface NormalizedOptions {
4
+ host: string;
5
+ port: number;
6
+ https: HttpsServerOptions | undefined;
7
+ hot: boolean;
8
+ url: string;
9
+ disableRequestLogging: boolean;
10
+ rootDir: string;
11
+ }
12
+ export declare function normalizeOptions(options: Server.Options): NormalizedOptions;
@@ -0,0 +1,28 @@
1
+ function normalizeHttpsOptions(serverOptions) {
2
+ if (serverOptions &&
3
+ typeof serverOptions === 'object' &&
4
+ serverOptions.type === 'https') {
5
+ return serverOptions.options;
6
+ }
7
+ return undefined;
8
+ }
9
+ export function normalizeOptions(options) {
10
+ const host = options.host ?? 'localhost';
11
+ const port = options.port ?? 8081;
12
+ const https = normalizeHttpsOptions(options.server);
13
+ const hot = options.hot ?? false;
14
+ const protocol = https ? 'https' : 'http';
15
+ const url = `${protocol}://${host}:${options.port}`;
16
+ return {
17
+ // webpack dev server compatible options
18
+ host,
19
+ port,
20
+ https,
21
+ hot,
22
+ url,
23
+ // fastify options
24
+ disableRequestLogging: !options.logRequests,
25
+ // project options
26
+ rootDir: options.rootDir,
27
+ };
28
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@callstack/repack-dev-server",
3
3
  "description": "A bundler-agnostic development server for React Native applications as part of @callstack/repack.",
4
4
  "license": "MIT",
5
- "version": "5.0.0-rc.9",
5
+ "version": "5.0.1",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -48,7 +48,7 @@
48
48
  "@babel/core": "^7.23.9",
49
49
  "@react-native-community/cli-server-api": "15.0.1",
50
50
  "@types/babel__code-frame": "^7.0.3",
51
- "@types/node": "18",
51
+ "@types/node": "^18",
52
52
  "@types/ws": "^8.5.3",
53
53
  "babel-plugin-add-import-extension": "^1.6.0",
54
54
  "typescript": "^5.7.2"