Skip to content

Commit 9a539db

Browse files
committed
inital commit
1 parent 224519d commit 9a539db

File tree

14 files changed

+707
-0
lines changed

14 files changed

+707
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.vscode/
2+
.env

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# NetSocket
2+
a socket solution for deno.
3+
> currently you can inspect test files to understand how it works

browser/client.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// deno-fmt-ignore-file
2+
// deno-lint-ignore-file
3+
// This code was bundled using `deno bundle` and it's not recommended to edit it manually
4+
5+
function getDataFromMessage(message, key) {
6+
return message.split('&').find((a)=>a.startsWith(key)
7+
)?.replace(`${key}={`, '').slice(0, -1);
8+
}
9+
function parseMessage(event, data) {
10+
return `EventName={${event}}&Data={${encodeURIComponent(JSON.stringify(data))}}`;
11+
}
12+
function parseEvent(message) {
13+
return {
14+
event: getDataFromMessage(message, 'EventName'),
15+
data: JSON.parse(decodeURIComponent(getDataFromMessage(message, 'Data')))
16+
};
17+
}
18+
class NetSocket {
19+
socket;
20+
get _socket() {
21+
return this._socket;
22+
}
23+
events;
24+
get _events() {
25+
return this.events;
26+
}
27+
_uuid;
28+
get uuid() {
29+
return this._uuid;
30+
}
31+
#FirstConnected;
32+
_status;
33+
get status() {
34+
return this._status;
35+
}
36+
_listeners;
37+
Timeout;
38+
#options;
39+
constructor(options){
40+
this.#FirstConnected = false;
41+
this._listeners = new Map();
42+
this._status = 'connecting';
43+
this.events = {
44+
open: ()=>{
45+
this._status = 'connected';
46+
if (this.#FirstConnected) {
47+
this.#emit('reconnect');
48+
this.#emit('connect');
49+
} else {
50+
const pong = ({ uuid })=>{
51+
this.#emit('firstconnect', this);
52+
this.#emit('connect');
53+
this._uuid = uuid;
54+
this.#FirstConnected = true;
55+
this.off('pong', pong);
56+
};
57+
this.on('pong', pong);
58+
this.emit('ping');
59+
}
60+
},
61+
close: ()=>{
62+
this._status = 'connecting';
63+
const Interval = setInterval(()=>{
64+
this.bindSocket(this.generateSocket(this.#options, this.uuid));
65+
}, 2000);
66+
const Timeout = setTimeout(()=>{
67+
clearInterval(Interval);
68+
this.#emit('disconnect', this);
69+
}, this.Timeout);
70+
const connect = ()=>{
71+
clearInterval(Interval);
72+
clearTimeout(Timeout);
73+
this.off('connect', connect);
74+
};
75+
this.on('connect', connect);
76+
},
77+
message: (ev)=>{
78+
const event = parseEvent(ev.data);
79+
this.#emit(event.event, ...event.data);
80+
},
81+
error: ()=>{
82+
this.#emit('error', this);
83+
}
84+
};
85+
const _options = {
86+
Timeout: 10000,
87+
connection: {
88+
hostname: 'localhost',
89+
tls: false,
90+
path: '/NetSocket',
91+
port: false
92+
},
93+
...options
94+
};
95+
this.on('disconnect', ()=>{
96+
this._status = 'disconnected';
97+
});
98+
this.#options = _options;
99+
this.Timeout = _options.Timeout;
100+
this.bindSocket(this.generateSocket(_options));
101+
}
102+
generateSocket(options, uuid) {
103+
if (typeof options.connection === 'string') {
104+
const url = new URL(options.connection);
105+
return new WebSocket(`${url.origin}${url.pathname}`, options.protocols);
106+
} else {
107+
const host = options.connection;
108+
const connection = `${host.tls ? 'wss' : 'ws'}://${host.hostname}:${host.port ? host.port : host.port === false ? '' : host.tls ? 443 : 80}${host.path ? host.path : ''}/${uuid ? `?_id=${uuid}` : ''}`;
109+
return new WebSocket(connection, options.protocols);
110+
}
111+
}
112+
bindSocket(socket) {
113+
if (this.socket) {
114+
this.socket.removeEventListener('open', this.events.open);
115+
this.socket.removeEventListener('close', this.events.close);
116+
this.socket.removeEventListener('message', this.events.message);
117+
this.socket.removeEventListener('error', this.events.error);
118+
this.socket.close();
119+
}
120+
this.socket = socket;
121+
this.socket.addEventListener('open', this.events.open);
122+
this.socket.addEventListener('close', this.events.close);
123+
this.socket.addEventListener('message', this.events.message);
124+
this.socket.addEventListener('error', this.events.error);
125+
}
126+
emit(event, ...data) {
127+
let Interval;
128+
new Promise((res, rej)=>{
129+
if (this._status === 'connected' && this.socket.readyState === WebSocket.OPEN) {
130+
res();
131+
} else if (this._status === 'disconnected') {
132+
rej();
133+
} else {
134+
Interval = setInterval(()=>{
135+
if (this._status === 'connected' && this.socket.readyState === WebSocket.OPEN) {
136+
res();
137+
} else if (this._status === 'disconnected') {
138+
rej();
139+
}
140+
}, 500);
141+
}
142+
}).then(()=>this.socket.send(parseMessage(event, data))
143+
).catch(()=>console.error('cannot use emit while disconnected.', {
144+
event,
145+
data
146+
})
147+
).finally(()=>Interval && clearInterval(Interval)
148+
);
149+
}
150+
#emit(event, ...data) {
151+
const listeners = this._listeners.get(event);
152+
listeners?.forEach((callback)=>callback(...data)
153+
);
154+
const anyListeners = this._listeners.get('any');
155+
if (anyListeners) {
156+
anyListeners.forEach((callback)=>callback(event, ...data)
157+
);
158+
}
159+
}
160+
on(event1, callback) {
161+
let Events = this._listeners.get(event1);
162+
if (!Events) {
163+
Events = new Set();
164+
this._listeners.set(event1, Events);
165+
}
166+
Events.add(callback);
167+
}
168+
off(event2, callback) {
169+
const Events = this._listeners.get(event2);
170+
Events?.delete(callback);
171+
if (Events.size === 0) {
172+
this._listeners.delete(event2);
173+
}
174+
}
175+
}
176+
export { NetSocket as NetSocket };

depts.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { serve, serveTls } from 'https://deno.land/[email protected]/http/server.ts'
2+
export { v4 } from 'https://deno.land/[email protected]/uuid/mod.ts'
3+
export type { ServeInit } from 'https://deno.land/[email protected]/http/server.ts'

mod.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { SocketInstance, NetSocket } from './src/server.ts'
2+
export { getUUIDfromUrl, parseEvent, parseMessage } from './src/methods.ts'
3+
export { NetSocket as NetSocketClient } from './src/client.ts'

src/client.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { events, NetSocketClientOptions, callback, HostOptions } from './types.ts'
2+
import { getUUIDfromUrl, parseEvent, parseMessage } from './methods.ts'
3+
4+
export class NetSocket {
5+
private socket!: WebSocket
6+
get _socket(): WebSocket {
7+
return this._socket
8+
}
9+
private events: events
10+
get _events(): events {
11+
return this.events
12+
}
13+
private _uuid!: string
14+
get uuid() {
15+
return this._uuid
16+
}
17+
#FirstConnected: boolean
18+
private _status: 'connecting' | 'connected' | 'disconnected'
19+
get status(): 'connecting' | 'connected' | 'disconnected' {
20+
return this._status
21+
}
22+
public readonly _listeners: Map<string, Set<callback>>
23+
public readonly Timeout: number
24+
#options: NetSocketClientOptions
25+
constructor(options?: NetSocketClientOptions) {
26+
this.#FirstConnected = false
27+
this._listeners = new Map()
28+
this._status = 'connecting'
29+
this.events = {
30+
open: () => {
31+
this._status = 'connected'
32+
if (this.#FirstConnected) {
33+
this.#emit('reconnect')
34+
this.#emit('connect')
35+
} else {
36+
const pong = ({ uuid }: { uuid: string }) => {
37+
this.#emit('firstconnect', this)
38+
this.#emit('connect')
39+
this._uuid = uuid
40+
this.#FirstConnected = true
41+
this.off('pong', pong)
42+
}
43+
this.on('pong', pong)
44+
this.emit('ping')
45+
}
46+
},
47+
close: () => {
48+
this._status = 'connecting'
49+
const Interval = setInterval(() => {
50+
this.bindSocket(this.generateSocket(this.#options, this.uuid))
51+
}, 2000)
52+
const Timeout = setTimeout(() => {
53+
clearInterval(Interval)
54+
this.#emit('disconnect', this)
55+
}, this.Timeout)
56+
const connect = () => {
57+
clearInterval(Interval)
58+
clearTimeout(Timeout)
59+
this.off('connect', connect)
60+
}
61+
this.on('connect', connect)
62+
},
63+
message: (ev) => {
64+
const event = parseEvent(ev.data)
65+
this.#emit(event.event, ...event.data)
66+
},
67+
error: () => {
68+
this.#emit('error', this)
69+
}
70+
}
71+
const _options: NetSocketClientOptions = {
72+
Timeout: 10000,
73+
connection: {
74+
hostname: 'localhost',
75+
tls: false,
76+
path: '/NetSocket',
77+
port: false
78+
},
79+
...options
80+
}
81+
this.on('disconnect', () => {
82+
this._status = 'disconnected'
83+
})
84+
this.#options = _options
85+
this.Timeout = _options.Timeout!
86+
this.bindSocket(this.generateSocket(_options))
87+
}
88+
private generateSocket(options: NetSocketClientOptions, uuid?: string): WebSocket {
89+
if (typeof options.connection === 'string') {
90+
const url = new URL(options.connection)
91+
return new WebSocket(`${ url.origin }${ url.pathname }`, options.protocols)
92+
} else {
93+
const host: HostOptions = options.connection!
94+
const connection = `${ host.tls ? 'wss' : 'ws' }://${ host.hostname }:${ host.port ? host.port : host.port === false ? '' : host.tls ? 443 : 80 }${ host.path ? host.path : '' }/${ uuid ? `?_id=${uuid}` : '' }`
95+
return new WebSocket(connection, options.protocols)
96+
}
97+
}
98+
bindSocket(socket: WebSocket) {
99+
if (this.socket) {
100+
this.socket.removeEventListener('open', this.events.open)
101+
this.socket.removeEventListener('close', this.events.close)
102+
this.socket.removeEventListener('message', this.events.message)
103+
this.socket.removeEventListener('error', this.events.error)
104+
this.socket.close()
105+
}
106+
this.socket = socket
107+
this.socket.addEventListener('open', this.events.open)
108+
this.socket.addEventListener('close', this.events.close)
109+
this.socket.addEventListener('message', this.events.message)
110+
this.socket.addEventListener('error', this.events.error)
111+
}
112+
emit(event: string, ...data: unknown[]) {
113+
let Interval: number
114+
new Promise<void>((res, rej) => {
115+
if (this._status === 'connected' && this.socket.readyState === WebSocket.OPEN) {
116+
res()
117+
} else if (this._status === 'disconnected') {
118+
rej()
119+
} else {
120+
Interval = setInterval(() => {
121+
if (this._status === 'connected' && this.socket.readyState === WebSocket.OPEN) {
122+
res()
123+
} else if (this._status === 'disconnected') {
124+
rej()
125+
}
126+
}, 500)
127+
}
128+
})
129+
.then(() => this.socket.send(parseMessage(event, data)))
130+
.catch(() => console.error('cannot use emit while disconnected.', { event, data }))
131+
.finally(() => Interval && clearInterval(Interval))
132+
}
133+
#emit(event: string, ...data: unknown[]) {
134+
const listeners = this._listeners.get(event)
135+
listeners?.forEach((callback) => callback(...data))
136+
137+
const anyListeners = this._listeners.get('any')
138+
if (anyListeners) {
139+
anyListeners.forEach((callback) => callback(event, ...data))
140+
}
141+
}
142+
on(event: string | 'any', callback: callback) {
143+
let Events: Set<callback> = this._listeners.get(event)!
144+
if (!Events) { Events = new Set(); this._listeners.set(event, Events) }
145+
Events.add(callback)
146+
}
147+
off(event: string, callback: callback) {
148+
const Events: Set<callback> = this._listeners.get(event)!
149+
Events?.delete(callback)
150+
if (Events.size === 0) {
151+
this._listeners.delete(event)
152+
}
153+
}
154+
}

src/methods.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export function getUUIDfromUrl(url: string) {
2+
return ((a) => a[a.length - 1]?.split('&')?.find(a=>a.trim().startsWith('_id'))?.split('=')[1]?.trim())(url.split('/?')) || undefined
3+
}
4+
export function getDataFromMessage(message: string, key: string) {
5+
return message.split('&').find((a)=>a.startsWith(key))?.replace(`${key}={`,'').slice(0, -1)
6+
}
7+
export function parseMessage(event: string, data: unknown[]): string {
8+
return `EventName={${event}}&Data={${encodeURIComponent(JSON.stringify(data))}}`
9+
}
10+
export function parseEvent(message: string): { event: string, data: unknown[] } {
11+
return { event: getDataFromMessage(message, 'EventName')!, data: JSON.parse(decodeURIComponent(getDataFromMessage(message, 'Data')!)) }
12+
}

0 commit comments

Comments
 (0)