Skip to content

Commit 51955ed

Browse files
author
melike2d
committed
🎉 pogsocket!
0 parents  commit 51955ed

13 files changed

Lines changed: 402 additions & 0 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Editor Settings
2+
.vscode/
3+
.idea/
4+
5+
# Environment Variables
6+
*.env

deps.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from "https://deno.land/std@0.104.0/ws/mod.ts";
2+
export * from "https://deno.land/std@0.104.0/io/bufio.ts";
3+
export * from "https://deno.land/std@0.104.0/async/deferred.ts";

encoding.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export type Encoder = (input?: string) => Uint8Array;
2+
3+
export function encoder(): Encoder {
4+
const _encoder = new TextEncoder();
5+
return input => _encoder.encode(input);
6+
}
7+
8+
export type Decoder = (input?: Uint8Array) => string;
9+
10+
export function decoder(): Decoder {
11+
const _decoder = new TextDecoder();
12+
return input => _decoder.decode(input);
13+
}
14+

helpers/close_socket.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { OpCode } from "../deps.ts";
2+
import { encoder } from "../encoding.ts";
3+
import { sendFrame } from "./send_frame.ts";
4+
5+
import type { PogSocket } from "../socket.ts";
6+
7+
const encode = encoder()
8+
9+
/**
10+
* Closes the provided pogsocket with a code and/or reason.
11+
* @param socket pogsocket to clsoe
12+
* @param code The close code
13+
* @param reason The close reason.
14+
*/
15+
export async function closeSocket(socket: PogSocket, code = 1000, reason?: string) {
16+
try {
17+
const header = [ code >>> 8, code & 0x00ff ];
18+
19+
let payload: Uint8Array;
20+
if (reason) {
21+
const bytes = encode(reason);
22+
payload = new Uint8Array(2 + bytes.byteLength);
23+
payload.set(header);
24+
payload.set(bytes, 2);
25+
} else {
26+
payload = new Uint8Array(header);
27+
}
28+
29+
await sendFrame(socket, OpCode.Close, payload);
30+
} catch (e) {
31+
throw e;
32+
} finally {
33+
closeSocketConnection(socket);
34+
}
35+
}
36+
37+
/**
38+
* Closes the connection of the supplied pogsocket, only for internal use.
39+
* @param socket The pogsocket to close.
40+
*/
41+
export function closeSocketConnection(socket: PogSocket): void {
42+
if (socket.isClosed) {
43+
return;
44+
}
45+
46+
try {
47+
socket.conn.close();
48+
} catch (e) {
49+
throw e;
50+
} finally {
51+
socket.isClosed = true;
52+
while (socket.frameQueue.length > 0) {
53+
const entry = socket.frameQueue.shift();
54+
if (!entry) {
55+
break;
56+
}
57+
58+
const error = new Deno.errors.ConnectionReset("Socket has been closed.");
59+
entry.deferred.reject(error);
60+
}
61+
}
62+
}

helpers/create_mask.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function createMask(length = 4): Uint8Array {
2+
return crypto.getRandomValues(new Uint8Array(length));
3+
}

helpers/event_based.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { readSocket } from "./read_socket.ts";
2+
3+
import type { WebSocketMessage } from "../deps.ts";
4+
import type { PogSocket } from "../socket.ts";
5+
6+
export interface EventHandlers {
7+
message?: (data: WebSocketMessage) => void;
8+
close?: (code: number, reason?: string) => void;
9+
ping?: (data: Uint8Array) => void;
10+
pong?: (data: Uint8Array) => void;
11+
}
12+
13+
export async function useEvents(socket: PogSocket, handlers: EventHandlers) {
14+
for await (const event of readSocket(socket)) {
15+
switch (event.type) {
16+
case "message":
17+
handlers.message?.(event.data);
18+
break;
19+
case "close":
20+
handlers.close?.(event.code, event.reason);
21+
break;
22+
case "ping":
23+
handlers.ping?.(event.data);
24+
break;
25+
case "pong":
26+
handlers.pong?.(event.data);
27+
break;
28+
}
29+
}
30+
}

helpers/is_binary.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { WebSocketMessage } from "../deps.ts";
2+
3+
/**
4+
* Whether a websocket message is binary.
5+
* @param message The websocket isBinaryMessage
6+
* @returns true, if the provided message is binary
7+
*/
8+
export function isBinaryMessage(message: WebSocketMessage): message is Uint8Array {
9+
return typeof message !== "string";
10+
}

helpers/read_frame.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { readFrame as _readFrame, unmask, WebSocketFrame } from "../deps.ts";
2+
3+
import type { PogSocket } from "../socket.ts";
4+
5+
export async function readFrame(socket: PogSocket): Promise<WebSocketFrame|null> {
6+
try {
7+
const frame = await _readFrame(socket.reader);
8+
unmask(frame.payload, frame.mask);
9+
10+
return frame;
11+
} catch {
12+
return null;
13+
}
14+
}

helpers/read_socket.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { OpCode } from "../deps.ts";
2+
import { decoder } from "../encoding.ts";
3+
import { closeSocket, closeSocketConnection } from "./close_socket.ts";
4+
import { readFrame } from "./read_frame.ts";
5+
import { sendFrame } from "./send_frame.ts";
6+
7+
import type { PogSocket } from "../socket.ts";
8+
import type { WebSocketFrame } from "../deps.ts";
9+
10+
function getLength(frames: WebSocketFrame[]): number {
11+
return frames.reduce((length, frame) => length + frame.payload.length, 0);
12+
}
13+
14+
function getCloseEvent(frame: WebSocketFrame, decode = decoder()): CloseEvent {
15+
return {
16+
// [0x12, 0x34] -> 0x1234
17+
code: (frame.payload[0] << 8) | frame.payload[1],
18+
reason: decode(frame.payload.subarray(2, frame.payload.length)),
19+
type: "close"
20+
}
21+
}
22+
23+
export async function* readSocket(socket: PogSocket): AsyncIterableIterator<PogSocketEvent> {
24+
const decode = decoder();
25+
26+
/* handle frames. */
27+
let frames: WebSocketFrame[] = [];
28+
while (!socket.isClosed) {
29+
const frame = await readFrame(socket);
30+
if (!frame) {
31+
closeSocketConnection(socket);
32+
break;
33+
}
34+
35+
switch (frame.opcode) {
36+
case OpCode.TextFrame:
37+
case OpCode.BinaryFrame:
38+
case OpCode.Continue:
39+
frames.push(frame);
40+
41+
/* merge all of the received frames. */
42+
if (frame.isLastFrame) {
43+
const message = new Uint8Array(getLength(frames));
44+
let pos = 0;
45+
for (const frame of frames) {
46+
message.set(frame.payload, pos);
47+
pos += frame.payload.length;
48+
}
49+
50+
yield {
51+
data: frames[0].opcode === OpCode.TextFrame
52+
? decode(message)
53+
: message,
54+
type: "message"
55+
}
56+
57+
frames = [];
58+
}
59+
60+
break;
61+
case OpCode.Close: {
62+
const event = getCloseEvent(frame, decode);
63+
await closeSocket(socket, event.code, event.reason);
64+
yield event;
65+
break;
66+
}
67+
case OpCode.Ping:
68+
await sendFrame(socket, OpCode.Pong, frame.payload);
69+
yield { type: "ping", data: frame.payload }
70+
break;
71+
case OpCode.Pong:
72+
yield { type: "pong", data: frame.payload }
73+
break;
74+
}
75+
}
76+
}
77+
78+
export type PogSocketEvent = MessageEvent | PingEvent | PongEvent | CloseEvent;
79+
export type PogSocketEventType = "message" | "ping" | "pong" | "close";
80+
81+
interface IPogSocketEvent {
82+
type: PogSocketEventType;
83+
}
84+
85+
export interface MessageEvent extends IPogSocketEvent {
86+
type: "message";
87+
data: string | Uint8Array;
88+
}
89+
90+
export interface PingEvent extends IPogSocketEvent {
91+
type: "ping";
92+
data: Uint8Array;
93+
}
94+
95+
export interface PongEvent extends IPogSocketEvent {
96+
type: "pong";
97+
data: Uint8Array;
98+
}
99+
100+
export interface CloseEvent extends IPogSocketEvent {
101+
type: "close";
102+
code: number;
103+
reason?: string;
104+
}
105+

helpers/send_frame.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { OpCode, WebSocketMessage } from "../deps.ts";
2+
import { encoder } from "../encoding.ts";
3+
import { enqueueFrame, PogSocket } from "../socket.ts";
4+
import { isBinaryMessage } from "./is_binary.ts";
5+
6+
const encode = encoder()
7+
8+
export function sendFrame(socket: PogSocket, opCode: OpCode, payload: WebSocketMessage) {
9+
return enqueueFrame(socket, {
10+
mask: socket.mask,
11+
isLastFrame: true,
12+
opcode: opCode,
13+
payload: isBinaryMessage(payload) ? payload : encode(payload)
14+
});
15+
}

0 commit comments

Comments
 (0)