or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/npm-phoenix-websocket

A custom implementation of the Channels API for communicating with an Elixir/Phoenix backend via WebSockets.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/phoenix-websocket@2.0.x

To install, run

npx @tessl/cli install tessl/npm-phoenix-websocket@2.0.0

0

# Phoenix WebSocket

1

2

Phoenix WebSocket is a TypeScript WebSocket client library for communicating with Elixir/Phoenix Channels. It provides an async/await-based API for establishing WebSocket connections, subscribing to channels/topics, sending messages, and handling server responses with automatic reconnection and comprehensive error handling.

3

4

## Package Information

5

6

- **Package Name**: phoenix-websocket

7

- **Package Type**: npm

8

- **Language**: TypeScript

9

- **Installation**: `npm install phoenix-websocket`

10

11

## Core Imports

12

13

```typescript

14

import {

15

PhoenixWebsocket,

16

WebsocketStatuses,

17

PhoenixWebsocketLogLevels,

18

TopicStatuses,

19

TopicMessageHandler,

20

PhoenixError,

21

PhoenixReply

22

} from "phoenix-websocket";

23

```

24

25

For CommonJS:

26

27

```javascript

28

const {

29

PhoenixWebsocket,

30

WebsocketStatuses,

31

PhoenixWebsocketLogLevels,

32

TopicStatuses,

33

TopicMessageHandler,

34

PhoenixError,

35

PhoenixReply

36

} = require("phoenix-websocket");

37

```

38

39

## Basic Usage

40

41

```typescript

42

import { PhoenixWebsocket } from "phoenix-websocket";

43

44

// Create connection instance

45

const socket = new PhoenixWebsocket("wss://example.io/channel-endpoint", {

46

token: "auth-token"

47

});

48

49

// Set up callbacks

50

socket.onConnectedCallback = () => {

51

console.log("Connected to Phoenix server");

52

};

53

54

socket.onDisconnectedCallback = () => {

55

console.log("Disconnected from Phoenix server");

56

};

57

58

// Connect and subscribe to a topic

59

await socket.connect();

60

await socket.subscribeToTopic("lobby", { user_id: "123" }, {

61

user_joined: (payload) => console.log("User joined:", payload),

62

user_left: (payload) => console.log("User left:", payload),

63

});

64

65

// Send a message

66

const reply = await socket.sendMessage("lobby", "new_message", {

67

text: "Hello, world!"

68

});

69

```

70

71

## Architecture

72

73

Phoenix WebSocket is built around several key components:

74

75

- **Connection Management**: Automatic connection handling with configurable retry logic and timeout settings

76

- **Topic Subscription System**: Manage multiple topic subscriptions with individual message handlers

77

- **Message Queue System**: Promise-based message sending with reply handling and timeout management

78

- **Error Handling**: Comprehensive error types for different failure scenarios

79

- **Reconnection Logic**: Automatic reconnection with exponential backoff and online/offline detection

80

81

## Capabilities

82

83

### Connection Management

84

85

Core connection functionality for establishing and maintaining WebSocket connections to Phoenix servers.

86

87

```typescript { .api }

88

class PhoenixWebsocket {

89

constructor(

90

url: string,

91

queryParams?: { [key: string]: string },

92

timeoutInMs?: number

93

);

94

95

connect(): Promise<void>;

96

disconnect(clearTopics?: boolean): void;

97

98

get connectionStatus(): WebsocketStatuses;

99

get subscribedTopics(): string[];

100

101

onConnectedCallback?: (() => void) | undefined;

102

onDisconnectedCallback?: (() => void) | undefined;

103

104

setLogLevel(logLevel: PhoenixWebsocketLogLevels): void;

105

disposeEvents(): void;

106

}

107

```

108

109

**Usage Examples:**

110

111

```typescript

112

// Basic connection

113

const socket = new PhoenixWebsocket("wss://localhost:4000/socket");

114

await socket.connect();

115

116

// With query parameters and timeout

117

const socket = new PhoenixWebsocket(

118

"wss://example.com/socket",

119

{ token: "abc123", user: "alice" },

120

30000 // 30 second timeout

121

);

122

123

// Check connection status

124

if (socket.connectionStatus === WebsocketStatuses.Connected) {

125

console.log("Socket is connected");

126

}

127

128

// Clean disconnect (removes all topics)

129

socket.disconnect(true);

130

```

131

132

### Topic Subscription

133

134

Topic subscription system for joining Phoenix channels and handling server messages.

135

136

```typescript { .api }

137

type TopicMessageHandler = (data: { [key: string]: any } | undefined) => void;

138

139

// Object-based message handlers

140

subscribeToTopic(

141

topic: string,

142

payload?: { [key: string]: any },

143

messageHandlers?: { [message: string]: TopicMessageHandler },

144

reconnectHandler?: (reconnectPromise: Promise<void>) => void

145

): Promise<void>;

146

147

// Map-based message handlers

148

subscribeToTopic(

149

topic: string,

150

payload?: { [key: string]: any },

151

messageHandlers?: Map<string, TopicMessageHandler>,

152

reconnectHandler?: (reconnectPromise: Promise<void>) => void

153

): Promise<void>;

154

155

unsubscribeToTopic(topic: string): void;

156

```

157

158

**Usage Examples:**

159

160

```typescript

161

// Simple subscription without message handlers

162

await socket.subscribeToTopic("lobby");

163

164

// With join payload

165

await socket.subscribeToTopic("room:123", { user_id: "alice" });

166

167

// With message handlers

168

await socket.subscribeToTopic("chat", { user: "alice" }, {

169

user_joined: (payload) => {

170

console.log(`${payload.username} joined the chat`);

171

},

172

user_left: (payload) => {

173

console.log(`${payload.username} left the chat`);

174

},

175

new_message: (payload) => {

176

console.log(`Message: ${payload.text}`);

177

}

178

});

179

180

// With Map-based handlers

181

const handlers = new Map([

182

["user_joined", (data) => console.log("User joined:", data)],

183

["user_left", (data) => console.log("User left:", data)]

184

]);

185

await socket.subscribeToTopic("lobby", {}, handlers);

186

187

// With reconnect handler for error recovery

188

await socket.subscribeToTopic("critical_topic", { userId: "123" }, {

189

error: (payload) => console.error("Topic error:", payload)

190

}, (reconnectPromise) => {

191

// Called on rejoin attempts (NOT initial join)

192

console.log("Topic reconnecting...");

193

reconnectPromise.catch((error) => {

194

if (error instanceof PhoenixRespondedWithError) {

195

if (error.reply?.response?.reason === "Invalid User") {

196

console.error("Invalid user on reconnect");

197

}

198

}

199

});

200

});

201

202

// Handle join errors

203

try {

204

await socket.subscribeToTopic("restricted_topic", { userId: "123" });

205

} catch (error) {

206

if (error instanceof PhoenixRespondedWithError) {

207

if (error.reply?.response?.reason === "Invalid User") {

208

console.error("Access denied: Invalid user");

209

}

210

}

211

}

212

213

// Unsubscribe from topic

214

socket.unsubscribeToTopic("lobby");

215

```

216

217

### Message Sending

218

219

Message sending functionality for communicating with Phoenix channels.

220

221

```typescript { .api }

222

sendMessage(

223

topic: string,

224

message: string,

225

payload?: { [key: string]: any }

226

): Promise<PhoenixReply>;

227

```

228

229

**Usage Examples:**

230

231

```typescript

232

// Send message without payload

233

const reply = await socket.sendMessage("lobby", "ping");

234

235

// Send message with payload

236

const reply = await socket.sendMessage("chat", "new_message", {

237

text: "Hello everyone!",

238

user_id: "alice"

239

});

240

241

// Handle reply

242

if (reply.status === "ok") {

243

console.log("Message sent successfully:", reply.response);

244

} else {

245

console.error("Message failed:", reply.response);

246

}

247

248

// Error handling

249

try {

250

const reply = await socket.sendMessage("room:123", "join_request");

251

} catch (error) {

252

if (error instanceof PhoenixInvalidTopicError) {

253

console.error("Not subscribed to topic");

254

} else if (error instanceof PhoenixTimeoutError) {

255

console.error("Message timed out");

256

}

257

}

258

```

259

260

## Types

261

262

### Message Handler

263

264

```typescript { .api }

265

type TopicMessageHandler = (data: { [key: string]: any } | undefined) => void;

266

```

267

268

### Connection Status

269

270

```typescript { .api }

271

enum WebsocketStatuses {

272

Disconnected = 0,

273

Connected = 1,

274

Disconnecting = 2,

275

Reconnecting = 3,

276

}

277

```

278

279

### Topic Status

280

281

```typescript { .api }

282

enum TopicStatuses {

283

Unsubscribed = 0,

284

Leaving = 1,

285

Joining = 2,

286

Subscribed = 3,

287

}

288

```

289

290

### Logging Levels

291

292

```typescript { .api }

293

enum PhoenixWebsocketLogLevels {

294

Informative = 1,

295

Warnings = 2,

296

Errors = 3,

297

Quiet = 4,

298

}

299

```

300

301

### Server Replies

302

303

```typescript { .api }

304

type PhoenixReply = PhoenixOkReply | PhoenixErrorReply;

305

306

type PhoenixOkReply = {

307

response: { [key: string]: any } | string;

308

status: "ok";

309

};

310

311

type PhoenixErrorReply = {

312

response: { [key: string]: any } | string;

313

status: "error";

314

};

315

```

316

317

### Error Types

318

319

```typescript { .api }

320

abstract class PhoenixError extends Error {}

321

322

class PhoenixInvalidTopicError extends PhoenixError {

323

constructor(topic?: string);

324

}

325

326

class PhoenixInvalidStateError extends PhoenixError {

327

constructor();

328

}

329

330

class PhoenixConnectionError extends PhoenixError {

331

constructor(topic?: string);

332

}

333

334

class PhoenixInternalServerError extends PhoenixError {

335

constructor();

336

}

337

338

class PhoenixRespondedWithError extends PhoenixError {

339

constructor(reply?: PhoenixReply);

340

reply?: PhoenixReply;

341

}

342

343

class PhoenixDisconnectedError extends PhoenixError {

344

constructor();

345

}

346

347

class PhoenixTimeoutError extends PhoenixError {

348

constructor();

349

}

350

```

351

352

## Error Handling

353

354

The library provides comprehensive error handling with specific error types:

355

356

```typescript

357

import {

358

PhoenixInvalidTopicError,

359

PhoenixInvalidStateError,

360

PhoenixConnectionError,

361

PhoenixTimeoutError

362

} from "phoenix-websocket";

363

364

try {

365

await socket.sendMessage("nonexistent_topic", "hello");

366

} catch (error) {

367

if (error instanceof PhoenixInvalidTopicError) {

368

console.error("Topic not subscribed");

369

} else if (error instanceof PhoenixInvalidStateError) {

370

console.error("WebSocket not connected");

371

} else if (error instanceof PhoenixConnectionError) {

372

console.error("Connection error occurred");

373

} else if (error instanceof PhoenixTimeoutError) {

374

console.error("Request timed out");

375

}

376

}

377

```

378

379

## Advanced Configuration

380

381

### Logging Configuration

382

383

```typescript

384

socket.setLogLevel(PhoenixWebsocketLogLevels.Errors); // Only show errors

385

socket.setLogLevel(PhoenixWebsocketLogLevels.Quiet); // No logging

386

```

387

388

### Connection Callbacks

389

390

```typescript

391

socket.onConnectedCallback = () => {

392

// Called on initial connection and reconnections

393

console.log("WebSocket connected");

394

};

395

396

socket.onDisconnectedCallback = () => {

397

// Called when WebSocket disconnects

398

console.log("WebSocket disconnected");

399

};

400

```

401

402

### Reconnection Handling

403

404

```typescript

405

// Subscribe with reconnection handler

406

await socket.subscribeToTopic("important_topic", {}, {

407

data_update: (payload) => console.log("Update:", payload)

408

}, async (reconnectPromise) => {

409

console.log("Topic is reconnecting...");

410

try {

411

await reconnectPromise;

412

console.log("Topic successfully reconnected");

413

} catch (error) {

414

console.error("Topic reconnection failed:", error);

415

}

416

});

417

```

418

419

### Resource Cleanup

420

421

```typescript

422

// Clean up event listeners

423

socket.disposeEvents();

424

425

// Disconnect and clear all topics

426

socket.disconnect(true);

427

428

// Disconnect but keep topics for reconnection

429

socket.disconnect(false);

430

```