or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

channel.mdindex.mdpresence.mdpush.mdsocket.mdutilities.md

utilities.mddocs/

0

# Transport and Utilities

1

2

Supporting utilities including Timer for reconnection backoff, LongPoll fallback transport, and Ajax helper for HTTP requests when WebSocket is unavailable.

3

4

## Capabilities

5

6

### Timer

7

8

Utility class for managing reconnection timing with configurable backoff strategies.

9

10

```typescript { .api }

11

/**

12

* Creates a Timer for managing reconnection backoff

13

* @param callback - Function to call when timer fires

14

* @param timerCalc - Function to calculate timeout based on attempt count

15

*/

16

constructor(callback: () => void | Promise<void>, timerCalc: (tries: number) => number);

17

18

/**

19

* Reset timer state (clears current timeout and resets attempt counter)

20

*/

21

reset(): void;

22

23

/**

24

* Schedule the next timeout based on current attempt count

25

*/

26

scheduleTimeout(): void;

27

```

28

29

**Usage Example:**

30

31

```typescript

32

import { Timer } from "phoenix";

33

34

// Basic exponential backoff

35

const reconnectTimer = new Timer(

36

() => {

37

console.log("Attempting to reconnect...");

38

socket.connect();

39

},

40

(tries) => {

41

// Exponential backoff: 1s, 2s, 4s, 8s, max 30s

42

return Math.min(1000 * Math.pow(2, tries - 1), 30000);

43

}

44

);

45

46

// Start the timer

47

reconnectTimer.scheduleTimeout();

48

49

// Reset on successful connection

50

socket.onOpen(() => {

51

reconnectTimer.reset();

52

});

53

54

// Advanced backoff with jitter

55

const advancedTimer = new Timer(

56

async () => {

57

try {

58

await attemptReconnection();

59

} catch (error) {

60

console.error("Reconnection failed:", error);

61

}

62

},

63

(tries) => {

64

const baseDelay = [1000, 2000, 5000, 10000, 30000][tries - 1] || 30000;

65

const jitter = Math.random() * 1000; // Add up to 1s jitter

66

return baseDelay + jitter;

67

}

68

);

69

```

70

71

### LongPoll

72

73

Fallback transport mechanism for long polling when WebSocket connections are unavailable.

74

75

```typescript { .api }

76

/**

77

* Creates a LongPoll transport instance

78

* @param endPoint - Long polling endpoint URL

79

*/

80

constructor(endPoint: string);

81

82

/**

83

* Normalize endpoint URL for long polling

84

* @param endPoint - Raw endpoint URL

85

* @returns Normalized endpoint URL

86

*/

87

normalizeEndpoint(endPoint: string): string;

88

89

/**

90

* Get the formatted endpoint URL

91

* @returns Complete long polling URL

92

*/

93

endpointURL(): string;

94

95

/**

96

* Close current connection and retry

97

*/

98

closeAndRetry(): void;

99

100

/**

101

* Handle timeout events

102

*/

103

ontimeout(): void;

104

105

/**

106

* Perform long polling request

107

*/

108

poll(): void;

109

110

/**

111

* Send data via long polling

112

* @param body - Data to send

113

*/

114

send(body: any): void;

115

116

/**

117

* Close the long polling connection

118

* @param code - Close code (optional)

119

* @param reason - Close reason (optional)

120

*/

121

close(code?: any, reason?: any): void;

122

```

123

124

**Usage Example:**

125

126

```typescript

127

import { LongPoll } from "phoenix";

128

129

// Create long poll transport

130

const longPoll = new LongPoll("/longpoll");

131

132

console.log("LongPoll URL:", longPoll.endpointURL());

133

134

// Send data

135

longPoll.send({

136

event: "heartbeat",

137

payload: {},

138

ref: "unique-ref"

139

});

140

141

// Start polling

142

longPoll.poll();

143

144

// Handle timeout

145

longPoll.ontimeout = () => {

146

console.log("Long poll timed out, retrying...");

147

longPoll.closeAndRetry();

148

};

149

150

// Close when done

151

longPoll.close(1000, "Normal closure");

152

153

// Custom transport implementation

154

class CustomLongPoll extends LongPoll {

155

constructor(endpoint: string) {

156

super(endpoint);

157

}

158

159

poll() {

160

console.log("Custom polling logic");

161

super.poll();

162

}

163

164

send(body: any) {

165

console.log("Sending via custom long poll:", body);

166

super.send(body);

167

}

168

}

169

```

170

171

### Ajax

172

173

HTTP request utility class for Phoenix communication with various request methods.

174

175

```typescript { .api }

176

/**

177

* Request state constants

178

*/

179

static states: { [state: string]: number };

180

181

/**

182

* Make HTTP request

183

* @param method - HTTP method (GET, POST, PUT, DELETE, etc.)

184

* @param endPoint - Request endpoint URL

185

* @param accept - Accept header value

186

* @param body - Request body

187

* @param timeout - Request timeout in milliseconds (optional)

188

* @param ontimeout - Timeout callback (optional)

189

* @param callback - Response callback (optional)

190

*/

191

static request(

192

method: string,

193

endPoint: string,

194

accept: string,

195

body: any,

196

timeout?: number,

197

ontimeout?: any,

198

callback?: (response?: any) => void | Promise<void>

199

): void;

200

201

/**

202

* Cross-domain request handler

203

* @param req - Request object

204

* @param method - HTTP method

205

* @param endPoint - Request endpoint

206

* @param body - Request body

207

* @param timeout - Request timeout (optional)

208

* @param ontimeout - Timeout callback (optional)

209

* @param callback - Response callback (optional)

210

*/

211

static xdomainRequest(

212

req: any,

213

method: string,

214

endPoint: string,

215

body: any,

216

timeout?: number,

217

ontimeout?: any,

218

callback?: (response?: any) => void | Promise<void>

219

): void;

220

221

/**

222

* XMLHttpRequest wrapper

223

* @param req - XMLHttpRequest object

224

* @param method - HTTP method

225

* @param endPoint - Request endpoint

226

* @param accept - Accept header value

227

* @param body - Request body

228

* @param timeout - Request timeout (optional)

229

* @param ontimeout - Timeout callback (optional)

230

* @param callback - Response callback (optional)

231

*/

232

static xhrRequest(

233

req: any,

234

method: string,

235

endPoint: string,

236

accept: string,

237

body: any,

238

timeout?: number,

239

ontimeout?: any,

240

callback?: (response?: any) => void | Promise<void>

241

): void;

242

243

/**

244

* Parse JSON response safely

245

* @param resp - Response string

246

* @returns Parsed JSON object

247

*/

248

static parseJSON(resp: string): JSON;

249

250

/**

251

* Serialize object to query string

252

* @param obj - Object to serialize

253

* @param parentKey - Parent key for nested serialization

254

* @returns URL-encoded query string

255

*/

256

static serialize(obj: any, parentKey: string): string;

257

258

/**

259

* Append parameters to URL

260

* @param url - Base URL

261

* @param params - Parameters to append

262

* @returns URL with appended parameters

263

*/

264

static appendParams(url: string, params: any): string;

265

```

266

267

**Usage Example:**

268

269

```typescript

270

import { Ajax } from "phoenix";

271

272

// Check request states

273

console.log("Ajax states:", Ajax.states);

274

275

// Basic GET request

276

Ajax.request(

277

"GET",

278

"/api/users",

279

"application/json",

280

null,

281

5000,

282

() => console.log("Request timed out"),

283

(response) => {

284

console.log("Users loaded:", response);

285

}

286

);

287

288

// POST request with JSON body

289

Ajax.request(

290

"POST",

291

"/api/users",

292

"application/json",

293

{ name: "John Doe", email: "john@example.com" },

294

10000,

295

() => console.log("Create user timed out"),

296

(response) => {

297

if (response.status === 201) {

298

console.log("User created:", response.data);

299

} else {

300

console.error("Failed to create user:", response.errors);

301

}

302

}

303

);

304

305

// Serialize object for query parameters

306

const params = { filter: "active", sort: "name", limit: 10 };

307

const queryString = Ajax.serialize(params, "");

308

console.log("Query string:", queryString); // "filter=active&sort=name&limit=10"

309

310

// Append parameters to URL

311

const baseUrl = "/api/posts";

312

const fullUrl = Ajax.appendParams(baseUrl, params);

313

console.log("Full URL:", fullUrl); // "/api/posts?filter=active&sort=name&limit=10"

314

315

// Parse JSON response

316

try {

317

const data = Ajax.parseJSON('{"success": true, "data": [1,2,3]}');

318

console.log("Parsed data:", data);

319

} catch (error) {

320

console.error("JSON parse error:", error);

321

}

322

```

323

324

## Advanced Usage Patterns

325

326

### Connection Recovery with Timer

327

328

```typescript

329

class ConnectionRecovery {

330

private reconnectTimer: Timer;

331

private maxAttempts: number = 10;

332

private attempts: number = 0;

333

334

constructor(private socket: Socket) {

335

this.reconnectTimer = new Timer(

336

() => this.attemptReconnection(),

337

(tries) => this.calculateBackoff(tries)

338

);

339

340

this.setupSocketHandlers();

341

}

342

343

private setupSocketHandlers() {

344

this.socket.onOpen(() => {

345

console.log("Connected successfully");

346

this.attempts = 0;

347

this.reconnectTimer.reset();

348

});

349

350

this.socket.onClose(() => {

351

console.log("Connection lost, starting recovery");

352

this.startRecovery();

353

});

354

355

this.socket.onError((error) => {

356

console.error("Socket error:", error);

357

this.startRecovery();

358

});

359

}

360

361

private startRecovery() {

362

if (this.attempts < this.maxAttempts) {

363

this.attempts++;

364

console.log(`Recovery attempt ${this.attempts}/${this.maxAttempts}`);

365

this.reconnectTimer.scheduleTimeout();

366

} else {

367

console.error("Max reconnection attempts exceeded");

368

this.onMaxAttemptsExceeded();

369

}

370

}

371

372

private attemptReconnection() {

373

console.log("Attempting to reconnect...");

374

this.socket.connect();

375

}

376

377

private calculateBackoff(tries: number): number {

378

// Progressive backoff: 1s, 2s, 5s, 10s, 30s, then 30s

379

const delays = [1000, 2000, 5000, 10000, 30000];

380

return delays[tries - 1] || 30000;

381

}

382

383

private onMaxAttemptsExceeded() {

384

// Show user notification or redirect

385

console.log("Connection could not be recovered");

386

387

// Optional: Show user a retry button

388

this.showRetryDialog();

389

}

390

391

private showRetryDialog() {

392

const retry = confirm("Connection lost. Would you like to try reconnecting?");

393

if (retry) {

394

this.attempts = 0;

395

this.startRecovery();

396

}

397

}

398

399

forceReconnect() {

400

this.attempts = 0;

401

this.reconnectTimer.reset();

402

this.attemptReconnection();

403

}

404

}

405

406

// Usage

407

const recovery = new ConnectionRecovery(socket);

408

```

409

410

### HTTP Fallback Service

411

412

```typescript

413

class HttpFallbackService {

414

private isUsingFallback = false;

415

416

constructor(private baseUrl: string) {}

417

418

async request(method: string, endpoint: string, data?: any, timeout = 5000): Promise<any> {

419

return new Promise((resolve, reject) => {

420

const fullUrl = `${this.baseUrl}${endpoint}`;

421

422

Ajax.request(

423

method,

424

fullUrl,

425

"application/json",

426

data ? JSON.stringify(data) : null,

427

timeout,

428

() => {

429

reject(new Error(`Request to ${endpoint} timed out`));

430

},

431

(response) => {

432

try {

433

const parsed = typeof response === 'string'

434

? Ajax.parseJSON(response)

435

: response;

436

resolve(parsed);

437

} catch (error) {

438

reject(new Error(`Failed to parse response: ${error}`));

439

}

440

}

441

);

442

});

443

}

444

445

async get(endpoint: string, params?: any, timeout?: number): Promise<any> {

446

const url = params ? Ajax.appendParams(endpoint, params) : endpoint;

447

return this.request("GET", url, null, timeout);

448

}

449

450

async post(endpoint: string, data: any, timeout?: number): Promise<any> {

451

return this.request("POST", endpoint, data, timeout);

452

}

453

454

async put(endpoint: string, data: any, timeout?: number): Promise<any> {

455

return this.request("PUT", endpoint, data, timeout);

456

}

457

458

async delete(endpoint: string, timeout?: number): Promise<any> {

459

return this.request("DELETE", endpoint, null, timeout);

460

}

461

462

enableFallback() {

463

this.isUsingFallback = true;

464

console.log("HTTP fallback enabled");

465

}

466

467

disableFallback() {

468

this.isUsingFallback = false;

469

console.log("HTTP fallback disabled");

470

}

471

}

472

473

// Usage

474

const httpService = new HttpFallbackService("/api");

475

476

// Use when WebSocket is unavailable

477

socket.onError(() => {

478

httpService.enableFallback();

479

});

480

481

socket.onOpen(() => {

482

httpService.disableFallback();

483

});

484

485

// Make HTTP requests

486

httpService.get("/users", { active: true })

487

.then(users => console.log("Users:", users))

488

.catch(error => console.error("Failed to fetch users:", error));

489

490

httpService.post("/messages", {

491

channel: "general",

492

content: "Hello via HTTP"

493

})

494

.then(message => console.log("Message sent:", message))

495

.catch(error => console.error("Failed to send message:", error));

496

```

497

498

### Hybrid Transport Manager

499

500

```typescript

501

class HybridTransportManager {

502

private socket: Socket;

503

private longPoll: LongPoll;

504

private httpService: HttpFallbackService;

505

private currentTransport: 'websocket' | 'longpoll' | 'http' = 'websocket';

506

507

constructor(wsEndpoint: string, httpBaseUrl: string) {

508

this.socket = new Socket(wsEndpoint, {

509

longPollFallbackMs: 2000,

510

transport: (endpoint) => new LongPoll(endpoint)

511

});

512

513

this.longPoll = new LongPoll(wsEndpoint + "/longpoll");

514

this.httpService = new HttpFallbackService(httpBaseUrl);

515

516

this.setupTransportFallback();

517

}

518

519

private setupTransportFallback() {

520

// Try WebSocket first

521

this.socket.onOpen(() => {

522

this.currentTransport = 'websocket';

523

console.log("Using WebSocket transport");

524

});

525

526

// Fallback to long polling

527

this.socket.onError(() => {

528

if (this.currentTransport === 'websocket') {

529

console.log("WebSocket failed, trying long polling");

530

this.currentTransport = 'longpoll';

531

this.startLongPolling();

532

} else if (this.currentTransport === 'longpoll') {

533

console.log("Long polling failed, using HTTP");

534

this.currentTransport = 'http';

535

}

536

});

537

}

538

539

private startLongPolling() {

540

this.longPoll.poll();

541

542

this.longPoll.ontimeout = () => {

543

console.log("Long poll timeout, falling back to HTTP");

544

this.currentTransport = 'http';

545

};

546

}

547

548

async sendMessage(channel: string, event: string, payload: any): Promise<any> {

549

switch (this.currentTransport) {

550

case 'websocket':

551

return this.sendViaWebSocket(channel, event, payload);

552

553

case 'longpoll':

554

return this.sendViaLongPoll(event, payload);

555

556

case 'http':

557

return this.sendViaHttp(channel, event, payload);

558

}

559

}

560

561

private sendViaWebSocket(channel: string, event: string, payload: any): Promise<any> {

562

return new Promise((resolve, reject) => {

563

const ch = this.socket.channel(channel);

564

ch.push(event, payload)

565

.receive("ok", resolve)

566

.receive("error", reject);

567

});

568

}

569

570

private sendViaLongPoll(event: string, payload: any): Promise<any> {

571

return new Promise((resolve, reject) => {

572

this.longPoll.send({

573

event,

574

payload,

575

ref: Date.now().toString()

576

});

577

578

// Simplified - in reality you'd handle the response

579

setTimeout(() => resolve({ status: "sent" }), 100);

580

});

581

}

582

583

private sendViaHttp(channel: string, event: string, payload: any): Promise<any> {

584

return this.httpService.post(`/channels/${channel}/messages`, {

585

event,

586

payload

587

});

588

}

589

590

getCurrentTransport(): string {

591

return this.currentTransport;

592

}

593

594

connect() {

595

this.socket.connect();

596

}

597

598

disconnect() {

599

this.socket.disconnect();

600

this.longPoll.close();

601

}

602

}

603

604

// Usage

605

const transportManager = new HybridTransportManager("/socket", "/api");

606

607

transportManager.connect();

608

609

// Send message - automatically uses best available transport

610

transportManager.sendMessage("room:lobby", "new_message", {

611

body: "Hello from hybrid transport!"

612

})

613

.then(response => console.log("Message sent:", response))

614

.catch(error => console.error("Send failed:", error));

615

616

console.log("Current transport:", transportManager.getCurrentTransport());

617

```