itty-sockets 0.7.0 → 0.7.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/connect.d.ts +64 -0
- package/connect.js +1 -0
- package/connect.mjs +1 -0
- package/package.json +4 -4
- package/.github/FUNDING.yml +0 -12
- package/.github/workflows/coverage.yml +0 -25
- package/.github/workflows/lint.yml +0 -19
- package/.github/workflows/verify.yml +0 -19
- package/CHANGELOG.md +0 -29
- package/bun.lockb +0 -0
- package/src/connect.spec.ts +0 -570
- package/src/connect.ts +0 -133
- package/src/connect.ts.backup +0 -183
- package/tsconfig.json +0 -25
package/connect.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
type IttySocketEvent<BaseFormat> = BaseFormat extends UseItty ? 'open' | 'close' | 'message' | 'join' | 'leave' : 'open' | 'close' | 'message';
|
|
2
|
+
type Timestamp = {
|
|
3
|
+
date: number;
|
|
4
|
+
};
|
|
5
|
+
type UserDetails = {
|
|
6
|
+
uid: string;
|
|
7
|
+
alias?: string;
|
|
8
|
+
};
|
|
9
|
+
type OptionalUserDetails = {
|
|
10
|
+
uid?: string;
|
|
11
|
+
alias?: string;
|
|
12
|
+
};
|
|
13
|
+
export type UseItty<MessageType = any> = {
|
|
14
|
+
message: MessageType;
|
|
15
|
+
} & UserDetails & Timestamp;
|
|
16
|
+
export type MessageEvent<MessageType = any> = {
|
|
17
|
+
message: MessageType;
|
|
18
|
+
} & Timestamp & OptionalUserDetails;
|
|
19
|
+
export type JoinEvent = {
|
|
20
|
+
type: 'join';
|
|
21
|
+
users: number;
|
|
22
|
+
} & Timestamp & OptionalUserDetails;
|
|
23
|
+
export type LeaveEvent = {
|
|
24
|
+
type: 'leave';
|
|
25
|
+
users: number;
|
|
26
|
+
} & Timestamp & OptionalUserDetails;
|
|
27
|
+
export type ErrorEvent = {
|
|
28
|
+
type: 'error';
|
|
29
|
+
message: string;
|
|
30
|
+
} & Timestamp;
|
|
31
|
+
export type IttySocketOptions = {
|
|
32
|
+
as?: string;
|
|
33
|
+
alias?: string;
|
|
34
|
+
echo?: true;
|
|
35
|
+
announce?: true;
|
|
36
|
+
};
|
|
37
|
+
export interface IttySocketConnect {
|
|
38
|
+
<BaseFormat = object>(...args: BaseFormat extends UseItty ? [channelID: string, options?: IttySocketOptions] : [url: string, queryParams?: any]): IttySocket<BaseFormat>;
|
|
39
|
+
}
|
|
40
|
+
type UseIttyEvents<BaseFormat> = {
|
|
41
|
+
on(type: 'join', listener: (event: JoinEvent) => any): IttySocket<BaseFormat>;
|
|
42
|
+
on(type: 'leave', listener: (event: LeaveEvent) => any): IttySocket<BaseFormat>;
|
|
43
|
+
on(type: 'error', listener: (event: ErrorEvent) => any): IttySocket<BaseFormat>;
|
|
44
|
+
};
|
|
45
|
+
type SendMessage<BaseFormat> = BaseFormat extends UseItty ? <MessageFormat = any>(message: MessageFormat, uid?: string) => IttySocket<BaseFormat> : <MessageFormat = any>(message: MessageFormat) => IttySocket<BaseFormat>;
|
|
46
|
+
export type IttySocket<BaseFormat = object> = {
|
|
47
|
+
open: () => IttySocket<BaseFormat>;
|
|
48
|
+
close: () => IttySocket<BaseFormat>;
|
|
49
|
+
send: SendMessage<BaseFormat>;
|
|
50
|
+
push: SendMessage<BaseFormat>;
|
|
51
|
+
remove(type: IttySocketEvent<BaseFormat>, listener: () => any): IttySocket<BaseFormat>;
|
|
52
|
+
remove(type: string, listener: () => any): IttySocket<BaseFormat>;
|
|
53
|
+
on(type: 'open', listener: () => any): IttySocket<BaseFormat>;
|
|
54
|
+
on(type: 'close', listener: () => any): IttySocket<BaseFormat>;
|
|
55
|
+
on<MessageFormat = BaseFormat>(type: 'message', listener: (event: BaseFormat & MessageFormat) => any): IttySocket<BaseFormat>;
|
|
56
|
+
on<MessageFormat = BaseFormat>(type: string, listener: (event: BaseFormat & MessageFormat & {
|
|
57
|
+
type: string;
|
|
58
|
+
}) => any): IttySocket<BaseFormat>;
|
|
59
|
+
on<MessageFormat = BaseFormat>(type: (event?: any) => any, listener: (event: BaseFormat & MessageFormat & {
|
|
60
|
+
type: string;
|
|
61
|
+
}) => any): IttySocket<BaseFormat>;
|
|
62
|
+
} & (BaseFormat extends UseItty ? UseIttyEvents<BaseFormat> : object);
|
|
63
|
+
export declare let connect: IttySocketConnect;
|
|
64
|
+
export {};
|
package/connect.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";exports.connect=(e,s={})=>{let t,n,a=[],o={},p=()=>(t||(t=new WebSocket((/^wss?:/.test(e)?e:"wss://itty.ws/c/"+e)+"?"+new URLSearchParams(s)),t.onmessage=(e,s=JSON.parse(e.data),t=s?.message,n={...null==t?.[0]&&t,...s})=>[n.type,s.type?0:"message","*"].map(e=>o[e]?.map(e=>e(n))),t.onopen=()=>(a.splice(0).map(e=>t.send(e)),o.open?.map(e=>e(n)),n&&t?.close()),t.onclose=()=>(n=t=null,o.close?.map(e=>e(n)))),c),c={open:p,send:(e,s)=>(e=(s?`${s}`:"")+JSON.stringify(e),1&t?.readyState?t.send(e):a.push(e),p()),on:(e,s)=>((o[e?.[0]?e:"*"]??=[]).push(e?.[0]?s:t=>e?.(t)&&s(t)),p()),remove:(e,s)=>(o[e]=o[e]?.filter(e=>e!=s),c),close:()=>(1&t?.readyState?t.close():n=1,c),push:(e,s)=>(n=1,c.send(e,s))};return c};
|
package/connect.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let e=(e,s={})=>{let a,t,n=[],p={},o=()=>(a||(a=new WebSocket((/^wss?:/.test(e)?e:"wss://itty.ws/c/"+e)+"?"+new URLSearchParams(s)),a.onmessage=(e,s=JSON.parse(e.data),a=s?.message,t={...null==a?.[0]&&a,...s})=>[t.type,s.type?0:"message","*"].map(e=>p[e]?.map(e=>e(t))),a.onopen=()=>(n.splice(0).map(e=>a.send(e)),p.open?.map(e=>e(t)),t&&a?.close()),a.onclose=()=>(t=a=null,p.close?.map(e=>e(t)))),l),l={open:o,send:(e,s)=>(e=(s?`${s}`:"")+JSON.stringify(e),1&a?.readyState?a.send(e):n.push(e),o()),on:(e,s)=>((p[e?.[0]?e:"*"]??=[]).push(e?.[0]?s:a=>e?.(a)&&s(a)),o()),remove:(e,s)=>(p[e]=p[e]?.filter(e=>e!=s),l),close:()=>(1&a?.readyState?a.close():t=1,l),push:(e,s)=>(t=1,l.send(e,s))};return l};export{e as connect};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "itty-sockets",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "WebSockets : simplified and minified.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"test": "bun test --coverage",
|
|
16
16
|
"lint": "itty lint",
|
|
17
17
|
"build": "itty lint && itty build --snippet=connect --hybrid",
|
|
18
|
-
"release": "itty release --prepare --tag --push",
|
|
19
|
-
"release:next": "itty release --prepare --tag --push --type=next"
|
|
18
|
+
"release": "itty release --prepare --tag --push --otp",
|
|
19
|
+
"release:next": "itty release --prepare --tag --push --type=next --otp"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"websockets",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/bun": "^1.2.3",
|
|
37
|
-
"itty-packager": "^1.
|
|
37
|
+
"itty-packager": "^1.7.0"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/.github/FUNDING.yml
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# These are supported funding model platforms
|
|
2
|
-
|
|
3
|
-
github: kwhitley
|
|
4
|
-
open_collective: kevinrwhitley
|
|
5
|
-
# patreon: # Replace with a single Patreon username
|
|
6
|
-
# ko_fi: # Replace with a single Ko-fi username
|
|
7
|
-
# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
8
|
-
# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
9
|
-
# liberapay: # Replace with a single Liberapay username
|
|
10
|
-
# issuehunt: # Replace with a single IssueHunt username
|
|
11
|
-
# otechie: # Replace with a single Otechie username
|
|
12
|
-
# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
on: [push, pull_request]
|
|
2
|
-
|
|
3
|
-
name: test
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
build:
|
|
7
|
-
name: Build
|
|
8
|
-
runs-on: ubuntu-latest
|
|
9
|
-
steps:
|
|
10
|
-
- uses: actions/checkout@v3
|
|
11
|
-
with:
|
|
12
|
-
fetch-depth: 0
|
|
13
|
-
|
|
14
|
-
- uses: oven-sh/setup-bun@v2.0.1
|
|
15
|
-
|
|
16
|
-
- name: Clean workspace and run tests
|
|
17
|
-
run: |
|
|
18
|
-
git clean -xfd # Remove all untracked files, including stale tests
|
|
19
|
-
bun install
|
|
20
|
-
bun test --coverage --coverage-reporter=text --coverage-reporter=lcov
|
|
21
|
-
|
|
22
|
-
- name: Coveralls
|
|
23
|
-
uses: coverallsapp/github-action@master
|
|
24
|
-
with:
|
|
25
|
-
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
name: lint
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [v0.x]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [v0.x]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
build:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v2
|
|
15
|
-
- uses: oven-sh/setup-bun@v2.0.1
|
|
16
|
-
- name: install dependencies
|
|
17
|
-
run: bun install
|
|
18
|
-
- name: lint
|
|
19
|
-
run: bun run lint
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
name: verify
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [v0.x]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [v0.x]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
build:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v2
|
|
15
|
-
- uses: oven-sh/setup-bun@v2.0.1
|
|
16
|
-
- name: Install dependencies
|
|
17
|
-
run: bun install
|
|
18
|
-
- name: Build
|
|
19
|
-
run: bun run build
|
package/CHANGELOG.md
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
## Changelog
|
|
2
|
-
CAUTION: Pre v1.0.0, this should be considered an alpha release, with minor updates allowing for breaking changes to the interface.
|
|
3
|
-
#### v0.7.0
|
|
4
|
-
- added: .on('*', listener) to respond to *any* messages (e.g. normal, custom, join, leave, error, etc)
|
|
5
|
-
#### v0.6.0
|
|
6
|
-
- added: .on('custom-type', listener) support, keying off payload.type
|
|
7
|
-
- added: .on(filterFunction, listener) support to route listeners based on custom payload rules
|
|
8
|
-
- added: .on('message', listener) still works, catching *all* user-sent messages
|
|
9
|
-
- added: full TypeScript generics support for .on and .send functions
|
|
10
|
-
- added: for convenience, we now destructure the message payload into the top-level event (before the event props)
|
|
11
|
-
- breaking: removed Date casting of event.date, now left as numeric timestamp
|
|
12
|
-
#### v0.5.0
|
|
13
|
-
- BREAKING: removed base (url) option - instead, simply pass a full wss:// path as the channelId to use an external compatible server.
|
|
14
|
-
#### v0.4.0
|
|
15
|
-
- added: base (url) option
|
|
16
|
-
#### v0.3.1
|
|
17
|
-
- fixes: module export
|
|
18
|
-
- removes extra NPM files
|
|
19
|
-
#### v0.3.0
|
|
20
|
-
- breaking: every event has multiple listeners (previously only on('message') allowed multiple)
|
|
21
|
-
- added: join/leave/error event types
|
|
22
|
-
- added: .remove('event-name', listener) to remove listeners
|
|
23
|
-
#### v0.2.3
|
|
24
|
-
- fix type hinting on listeners
|
|
25
|
-
- improve type hinting on send/push
|
|
26
|
-
#### v0.2.0
|
|
27
|
-
- alpha release 2
|
|
28
|
-
#### v0.1.0
|
|
29
|
-
- alpha release 1
|
package/bun.lockb
DELETED
|
Binary file
|
package/src/connect.spec.ts
DELETED
|
@@ -1,570 +0,0 @@
|
|
|
1
|
-
import { describe, afterAll, expect, it, mock } from 'bun:test'
|
|
2
|
-
import { connect, type IttySocket, type UseItty } from './connect'
|
|
3
|
-
|
|
4
|
-
type TestLeaf = (args: {
|
|
5
|
-
channel: IttySocket<UseItty>,
|
|
6
|
-
resolve: () => void,
|
|
7
|
-
spy: () => void,
|
|
8
|
-
getChannel: (options?: any) => IttySocket<UseItty>
|
|
9
|
-
}) => void
|
|
10
|
-
|
|
11
|
-
type TestTree = {
|
|
12
|
-
[key: string]: TestTree | TestLeaf
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type ChatMessage = {
|
|
16
|
-
type: 'chat',
|
|
17
|
-
user: string,
|
|
18
|
-
text: string,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const EXPOSED_METHODS = ['send', 'push', 'on', 'remove', 'close', 'open']
|
|
22
|
-
const OPEN_CHANNELS: IttySocket[] = []
|
|
23
|
-
|
|
24
|
-
const tests: TestTree = {
|
|
25
|
-
'NAMED EXPORTS': {
|
|
26
|
-
'import { connect } from "itty-sockets"': {
|
|
27
|
-
'is a function': () => expect(typeof connect).toBe('function'),
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
'connect(id, options?)': {
|
|
31
|
-
'constructs correct WebSocket URL': () => {
|
|
32
|
-
const originalWebSocket = global.WebSocket
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
34
|
-
const mockFn = mock((url: string) => ({}))
|
|
35
|
-
|
|
36
|
-
// @ts-ignore
|
|
37
|
-
global.WebSocket = mockFn
|
|
38
|
-
|
|
39
|
-
connect('my-channel', { a: 'b', c: 'd' } as any).open()
|
|
40
|
-
expect(mockFn.mock.calls[0][0]).toBe('wss://itty.ws/c/my-channel?a=b&c=d')
|
|
41
|
-
|
|
42
|
-
connect('ws://custom.server/path').open()
|
|
43
|
-
expect(mockFn.mock.calls[1][0]).toBe('ws://custom.server/path?')
|
|
44
|
-
|
|
45
|
-
connect('ws://custom.server/path', { echo: true, alias: 'test-user' }).open()
|
|
46
|
-
expect(mockFn.mock.calls[2][0]).toBe('ws://custom.server/path?echo=true&alias=test-user')
|
|
47
|
-
|
|
48
|
-
global.WebSocket = originalWebSocket
|
|
49
|
-
},
|
|
50
|
-
'exposes chainable method': EXPOSED_METHODS.reduce((acc, method) => {
|
|
51
|
-
acc[`.${method}()`] = ({ channel }) => {
|
|
52
|
-
expect(typeof channel[method]).toBe('function')
|
|
53
|
-
expect(channel[method]()).toBe(channel)
|
|
54
|
-
}
|
|
55
|
-
return acc
|
|
56
|
-
}, {} as Record<string, TestLeaf>),
|
|
57
|
-
'OPTIONS': {
|
|
58
|
-
'{ echo: true }': {
|
|
59
|
-
'sends messages back to sender': async ({ getChannel, resolve }) =>
|
|
60
|
-
getChannel({ echo: true })
|
|
61
|
-
.on('message', msg => {
|
|
62
|
-
expect(msg.message).toBe('test')
|
|
63
|
-
resolve()
|
|
64
|
-
})
|
|
65
|
-
.send('test'),
|
|
66
|
-
},
|
|
67
|
-
'{ alias: string }': {
|
|
68
|
-
'sets alias for messages': async ({ getChannel, resolve }) =>
|
|
69
|
-
getChannel({ echo: true, alias: 'test-user' })
|
|
70
|
-
.on('message', msg => {
|
|
71
|
-
expect(msg.alias).toBe('test-user')
|
|
72
|
-
resolve()
|
|
73
|
-
})
|
|
74
|
-
.send('test'),
|
|
75
|
-
'sets alias for join events if { announce: true } is set': async ({ getChannel, resolve }) =>
|
|
76
|
-
getChannel({ echo: true, alias: 'test-user', announce: true })
|
|
77
|
-
.on('join', ({ alias }) => {
|
|
78
|
-
expect(alias).toBe('test-user')
|
|
79
|
-
resolve()
|
|
80
|
-
}),
|
|
81
|
-
},
|
|
82
|
-
'{ as: string }': {
|
|
83
|
-
'sets alias for messages': async ({ getChannel, resolve }) =>
|
|
84
|
-
getChannel({ echo: true, as: 'test-user' })
|
|
85
|
-
.on('message', msg => {
|
|
86
|
-
expect(msg.alias).toBe('test-user')
|
|
87
|
-
resolve()
|
|
88
|
-
})
|
|
89
|
-
.send('test')
|
|
90
|
-
},
|
|
91
|
-
'{ announce: true }': {
|
|
92
|
-
'announces self to channel upon joining/leaving': async ({ getChannel, resolve }) =>
|
|
93
|
-
getChannel({ announce: true, as: 'test-user' })
|
|
94
|
-
.on('join', ({ alias }) => {
|
|
95
|
-
expect(alias).toBe('test-user')
|
|
96
|
-
resolve()
|
|
97
|
-
})
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
'METHODS': {
|
|
101
|
-
'.open()': {
|
|
102
|
-
'opens the socket': async ({ channel, resolve }) =>
|
|
103
|
-
channel
|
|
104
|
-
.on('open', resolve)
|
|
105
|
-
.open(),
|
|
106
|
-
'calling multiple times is fine': async ({ channel, resolve }) =>
|
|
107
|
-
channel
|
|
108
|
-
.on('open', () => {
|
|
109
|
-
channel.open() // one more time for good measure
|
|
110
|
-
resolve()
|
|
111
|
-
})
|
|
112
|
-
.open(),
|
|
113
|
-
},
|
|
114
|
-
'.close()': {
|
|
115
|
-
'closes the socket': async ({ channel, resolve }) =>
|
|
116
|
-
channel
|
|
117
|
-
.on('close', resolve)
|
|
118
|
-
.on('open', channel.close),
|
|
119
|
-
'calling multiple times is fine': async ({ channel, resolve }) =>
|
|
120
|
-
channel
|
|
121
|
-
.on('close',resolve)
|
|
122
|
-
.on('open', () => {
|
|
123
|
-
channel.close()
|
|
124
|
-
channel.close()
|
|
125
|
-
}),
|
|
126
|
-
},
|
|
127
|
-
'.on(\'open\', listener)': {
|
|
128
|
-
'registers an event listener that is called when the socket is opened': async ({ channel, resolve }) =>
|
|
129
|
-
channel
|
|
130
|
-
.on('open', resolve)
|
|
131
|
-
.open(),
|
|
132
|
-
'allows multiple listeners': async ({ channel, resolve, spy }) =>
|
|
133
|
-
channel
|
|
134
|
-
.on('open', spy)
|
|
135
|
-
.on('open', () => {
|
|
136
|
-
expect(spy).toHaveBeenCalled()
|
|
137
|
-
resolve()
|
|
138
|
-
}),
|
|
139
|
-
},
|
|
140
|
-
'.on(\'close\', listener)': {
|
|
141
|
-
'registers an event listener that is called when the socket is closed': async ({ channel, resolve }) =>
|
|
142
|
-
channel
|
|
143
|
-
.on('close', resolve)
|
|
144
|
-
.on('open', channel.close),
|
|
145
|
-
'allows multiple listeners': async ({ channel, resolve, spy }) =>
|
|
146
|
-
channel
|
|
147
|
-
.on('close', spy)
|
|
148
|
-
.on('close', () => {
|
|
149
|
-
expect(spy).toHaveBeenCalled()
|
|
150
|
-
resolve()
|
|
151
|
-
})
|
|
152
|
-
.on('open', channel.close),
|
|
153
|
-
},
|
|
154
|
-
'.on(\'message\', listener)': {
|
|
155
|
-
'registers an event listener that is called when a message is received': async ({ getChannel, resolve }) => {
|
|
156
|
-
getChannel({ echo: true })
|
|
157
|
-
.on('message', e => {
|
|
158
|
-
expect(e.message).toBe('test')
|
|
159
|
-
resolve()
|
|
160
|
-
})
|
|
161
|
-
.send('test')
|
|
162
|
-
},
|
|
163
|
-
'allows multiple message listeners': async ({ getChannel, resolve, spy }) =>
|
|
164
|
-
getChannel({ echo: true })
|
|
165
|
-
.on('message', spy)
|
|
166
|
-
.on('message', (e) => {
|
|
167
|
-
expect(e.message).toBe('test')
|
|
168
|
-
expect(spy).toHaveBeenCalled()
|
|
169
|
-
resolve()
|
|
170
|
-
})
|
|
171
|
-
.send('test'),
|
|
172
|
-
'receives message props on base message object': async ({ getChannel, resolve }) =>
|
|
173
|
-
getChannel({ echo: true })
|
|
174
|
-
.on<{ foo: string }>('message', (e) => {
|
|
175
|
-
expect(e.foo).toBe('bar')
|
|
176
|
-
expect(e.message.foo).toBe('bar')
|
|
177
|
-
resolve()
|
|
178
|
-
})
|
|
179
|
-
.send({ foo: 'bar' }),
|
|
180
|
-
'message props do not override event base props': async ({ getChannel, resolve }, date = new Date()) =>
|
|
181
|
-
getChannel({ echo: true, alias: 'test-user' })
|
|
182
|
-
.on<{ foo: string }>('message', (e) => {
|
|
183
|
-
expect(e.foo).toBe('bar')
|
|
184
|
-
// confirm types are correct
|
|
185
|
-
expect(e.uid).toBeTypeOf('string')
|
|
186
|
-
expect(e.alias).toBeTypeOf('string')
|
|
187
|
-
expect(e.date).toBeTypeOf('number')
|
|
188
|
-
// confirm props are not overridden
|
|
189
|
-
expect(e.uid).not.toBe('foo')
|
|
190
|
-
expect(e.alias).not.toBe('bar')
|
|
191
|
-
expect(e.date).not.toBe(+date)
|
|
192
|
-
resolve()
|
|
193
|
-
})
|
|
194
|
-
.send({ foo: 'bar', uid: 'foo', alias: 'bar', date }),
|
|
195
|
-
'base props not polluted by string messages': async ({ getChannel, resolve, spy }) =>
|
|
196
|
-
getChannel({ echo: true })
|
|
197
|
-
.on('message', spy)
|
|
198
|
-
.on('message', (e) => {
|
|
199
|
-
expect(e[0]).toBeUndefined() // "h" if polluted
|
|
200
|
-
expect(spy).toHaveBeenCalled()
|
|
201
|
-
resolve()
|
|
202
|
-
})
|
|
203
|
-
.send('hello'),
|
|
204
|
-
'base props not polluted by array messages': async ({ getChannel, resolve, spy }) =>
|
|
205
|
-
getChannel({ echo: true })
|
|
206
|
-
.on('message', spy)
|
|
207
|
-
.on('message', (e) => {
|
|
208
|
-
expect(e[0]).toBeUndefined() // "1" if polluted
|
|
209
|
-
expect(spy).toHaveBeenCalled()
|
|
210
|
-
resolve()
|
|
211
|
-
})
|
|
212
|
-
.send([1, 2, 3]),
|
|
213
|
-
'base props not polluted by numeric messages': async ({ getChannel, resolve, spy }) =>
|
|
214
|
-
getChannel({ echo: true })
|
|
215
|
-
.on('message', spy)
|
|
216
|
-
.on('message', (e) => {
|
|
217
|
-
expect(e[0]).toBeUndefined() // "?" if polluted
|
|
218
|
-
expect(spy).toHaveBeenCalled()
|
|
219
|
-
resolve()
|
|
220
|
-
})
|
|
221
|
-
.send(13),
|
|
222
|
-
},
|
|
223
|
-
'.on(\'join\', listener)': {
|
|
224
|
-
'registers an event listener that is called when a user (or self) joins the channel': async ({ channel, resolve }) =>
|
|
225
|
-
channel
|
|
226
|
-
.on('join', e => {
|
|
227
|
-
expect(e.users).toBe(1)
|
|
228
|
-
resolve()
|
|
229
|
-
}),
|
|
230
|
-
'does NOT include user details when { announce: true } is not set': async ({ channel, resolve }) =>
|
|
231
|
-
channel
|
|
232
|
-
.on('join', e => {
|
|
233
|
-
expect(e.uid).toBeUndefined()
|
|
234
|
-
expect(e.alias).toBeUndefined()
|
|
235
|
-
resolve()
|
|
236
|
-
}),
|
|
237
|
-
'DOES include user details when { announce: true } is set': async ({ getChannel, resolve }) =>
|
|
238
|
-
getChannel({ announce: true, alias: 'test-user' })
|
|
239
|
-
.on('join', e => {
|
|
240
|
-
expect(e.uid).not.toBeUndefined()
|
|
241
|
-
expect(e.alias).toBe('test-user')
|
|
242
|
-
resolve()
|
|
243
|
-
})
|
|
244
|
-
},
|
|
245
|
-
'.on(\'error\', listener)': {
|
|
246
|
-
'registers an event listener that is called when an error occurs': async ({ channel, resolve }) =>
|
|
247
|
-
channel
|
|
248
|
-
.on('error', e => {
|
|
249
|
-
expect(e.message).toContain('non-existent-user')
|
|
250
|
-
resolve()
|
|
251
|
-
})
|
|
252
|
-
.send('test', 'non-existent-user')
|
|
253
|
-
},
|
|
254
|
-
'.on(\'leave\', listener)': {
|
|
255
|
-
'registers an event listener that is called when a user leaves the channel': async ({ channel, getChannel,resolve }) => {
|
|
256
|
-
channel
|
|
257
|
-
.on('leave', e => {
|
|
258
|
-
expect(e.users).toBe(1)
|
|
259
|
-
resolve()
|
|
260
|
-
})
|
|
261
|
-
.on('open', () => {
|
|
262
|
-
getChannel().push('test') // trigger a join + leave
|
|
263
|
-
})
|
|
264
|
-
},
|
|
265
|
-
'does NOT include user details when { announce: true } is not set': async ({ channel, getChannel, resolve }) => {
|
|
266
|
-
channel
|
|
267
|
-
.on('leave', e => {
|
|
268
|
-
expect(e.uid).toBeUndefined()
|
|
269
|
-
expect(e.alias).toBeUndefined()
|
|
270
|
-
resolve()
|
|
271
|
-
})
|
|
272
|
-
.on('open', () => {
|
|
273
|
-
getChannel().push('test') // trigger a join + leave
|
|
274
|
-
})
|
|
275
|
-
},
|
|
276
|
-
'DOES include user details when { announce: true } is set': async ({ getChannel, resolve }) => {
|
|
277
|
-
getChannel()
|
|
278
|
-
.on('leave', e => {
|
|
279
|
-
expect(e.uid).not.toBeUndefined()
|
|
280
|
-
expect(e.alias).toBe('test-user')
|
|
281
|
-
resolve()
|
|
282
|
-
})
|
|
283
|
-
.on('open', () => {
|
|
284
|
-
getChannel({ announce: true, alias: 'test-user' }).push('test') // trigger a join + leave
|
|
285
|
-
})
|
|
286
|
-
}
|
|
287
|
-
},
|
|
288
|
-
'.on(\'{custom-type}\', listener)': {
|
|
289
|
-
'catches when message.type matches the custom type': async ({ getChannel, resolve }) => {
|
|
290
|
-
getChannel()
|
|
291
|
-
.on<{ user: string, text: string }>('chat', (e) => {
|
|
292
|
-
const { user, text } = e
|
|
293
|
-
expect(user).toBe('test-user')
|
|
294
|
-
expect(text).toBe('test')
|
|
295
|
-
expect(e.type).toBe('chat') // currently giving a TS error (incorrect)
|
|
296
|
-
expect(e.uid).toBeTypeOf('string')
|
|
297
|
-
expect(e.date).toBeTypeOf('number')
|
|
298
|
-
expect(e.user).toBe(e.message.user)
|
|
299
|
-
resolve()
|
|
300
|
-
})
|
|
301
|
-
.on('open', () => {
|
|
302
|
-
getChannel().send({ type: 'chat', user: 'test-user', text: 'test' })
|
|
303
|
-
})
|
|
304
|
-
},
|
|
305
|
-
'will still trigger "message" listeners': async ({ getChannel, resolve, spy }) =>
|
|
306
|
-
getChannel({ echo: true })
|
|
307
|
-
.on('message', spy)
|
|
308
|
-
.on('chat', () => {
|
|
309
|
-
setTimeout(() => {
|
|
310
|
-
expect(spy).toHaveBeenCalled()
|
|
311
|
-
resolve()
|
|
312
|
-
}, 5)
|
|
313
|
-
})
|
|
314
|
-
.send({ type: 'chat', user: 'test-user', text: 'test' }),
|
|
315
|
-
'will include custom payloads at top level and under e.message': async ({ getChannel, resolve, spy }) =>
|
|
316
|
-
getChannel({ echo: true })
|
|
317
|
-
.on('message', spy)
|
|
318
|
-
.on<ChatMessage>('chat', (e) => {
|
|
319
|
-
expect(e.type).toBe('chat')
|
|
320
|
-
expect(e.user).toBe('test-user')
|
|
321
|
-
expect(e.text).toBe('test')
|
|
322
|
-
expect(e.message.type).toBe('chat')
|
|
323
|
-
expect(e.message.user).toBe('test-user')
|
|
324
|
-
expect(e.message.text).toBe('test')
|
|
325
|
-
resolve()
|
|
326
|
-
})
|
|
327
|
-
.send({ type: 'chat', user: 'test-user', text: 'test' })
|
|
328
|
-
},
|
|
329
|
-
'.on(\'*\', listener)': {
|
|
330
|
-
'catches both typed and untyped events': async ({ getChannel, resolve }) => {
|
|
331
|
-
const received: any[] = []
|
|
332
|
-
getChannel({ echo: true })
|
|
333
|
-
.on('*', (e) => {
|
|
334
|
-
received.push(e)
|
|
335
|
-
if (received.length === 2) {
|
|
336
|
-
expect(received[0].type).toBe('join')
|
|
337
|
-
expect(received[1].message).toBe('test')
|
|
338
|
-
resolve()
|
|
339
|
-
}
|
|
340
|
-
})
|
|
341
|
-
.send('test')
|
|
342
|
-
},
|
|
343
|
-
},
|
|
344
|
-
'.on(eventFilter, listener)': {
|
|
345
|
-
'can accept a filter function as type': async ({ getChannel, resolve, spy }) =>
|
|
346
|
-
getChannel({ echo: true })
|
|
347
|
-
.on('message', spy)
|
|
348
|
-
.on(e => e.type === 'chat', () => {
|
|
349
|
-
setTimeout(() => {
|
|
350
|
-
expect(spy).toHaveBeenCalled()
|
|
351
|
-
resolve()
|
|
352
|
-
}, 5)
|
|
353
|
-
})
|
|
354
|
-
.send({ type: 'chat', user: 'test-user', text: 'test' }),
|
|
355
|
-
},
|
|
356
|
-
'.remove(\'open\', listener)': {
|
|
357
|
-
'removes a listener (will not fire)': async ({ channel, resolve, spy }) =>
|
|
358
|
-
channel
|
|
359
|
-
.on('open', spy)
|
|
360
|
-
.on('open', () => {
|
|
361
|
-
expect(spy).not.toHaveBeenCalled()
|
|
362
|
-
resolve()
|
|
363
|
-
})
|
|
364
|
-
.remove('open', spy)
|
|
365
|
-
},
|
|
366
|
-
'.send(message, recipient?)': {
|
|
367
|
-
'delivers a message to the channel': async ({ channel, getChannel, resolve }) => {
|
|
368
|
-
channel
|
|
369
|
-
.on('message', e => {
|
|
370
|
-
expect(e.message).toBe('test')
|
|
371
|
-
resolve()
|
|
372
|
-
})
|
|
373
|
-
.on('open', () => {
|
|
374
|
-
getChannel().send('test')
|
|
375
|
-
})
|
|
376
|
-
},
|
|
377
|
-
'delivers a message to a recipient': async ({ channel, getChannel, resolve }) => {
|
|
378
|
-
channel
|
|
379
|
-
.on('join', ({ uid }) => {
|
|
380
|
-
channel.send('test', uid)
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
getChannel({ announce: true })
|
|
384
|
-
.on('message', (e) => {
|
|
385
|
-
expect(e.message).toBe('test')
|
|
386
|
-
resolve()
|
|
387
|
-
})
|
|
388
|
-
},
|
|
389
|
-
'private messages are ONLY delivered to the recipient': async ({ channel, getChannel, resolve, spy }) =>
|
|
390
|
-
channel
|
|
391
|
-
.on('join', ({ uid, alias }) => {
|
|
392
|
-
if (alias === 'test-user') {
|
|
393
|
-
channel.send('test', uid)
|
|
394
|
-
}
|
|
395
|
-
})
|
|
396
|
-
.on('open', () => {
|
|
397
|
-
getChannel()
|
|
398
|
-
.on('message', spy)
|
|
399
|
-
.on('open', () => {
|
|
400
|
-
getChannel({ announce: true, alias: 'test-user' })
|
|
401
|
-
.on('message', (e) => {
|
|
402
|
-
expect(e.message).toBe('test')
|
|
403
|
-
expect(spy).not.toHaveBeenCalled()
|
|
404
|
-
resolve()
|
|
405
|
-
})
|
|
406
|
-
})
|
|
407
|
-
}),
|
|
408
|
-
'will send an error if the recipient does not exist': async ({ channel, resolve }) =>
|
|
409
|
-
channel
|
|
410
|
-
.on('error', e => {
|
|
411
|
-
expect(e.message).toContain('non-existent-user')
|
|
412
|
-
resolve()
|
|
413
|
-
})
|
|
414
|
-
.send('test', 'non-existent-user'),
|
|
415
|
-
},
|
|
416
|
-
'.push(message, recipient?)': {
|
|
417
|
-
'sends a message to the channel': async ({ channel, getChannel, resolve }) => {
|
|
418
|
-
channel
|
|
419
|
-
.on('message', e => {
|
|
420
|
-
expect(e.message).toBe('test')
|
|
421
|
-
resolve()
|
|
422
|
-
})
|
|
423
|
-
.on('open', () => {
|
|
424
|
-
getChannel().push('test')
|
|
425
|
-
})
|
|
426
|
-
},
|
|
427
|
-
'closes after sending a message': async ({ channel, resolve }) =>
|
|
428
|
-
channel
|
|
429
|
-
.on('close', () => {
|
|
430
|
-
resolve()
|
|
431
|
-
})
|
|
432
|
-
.push('test')
|
|
433
|
-
},
|
|
434
|
-
},
|
|
435
|
-
'MISC BEHAVIOR': {
|
|
436
|
-
'messages are queued and delivered upon connection': async ({ getChannel, resolve }) => {
|
|
437
|
-
const messages = ['first', 'second', 'third']
|
|
438
|
-
const received: string[] = []
|
|
439
|
-
|
|
440
|
-
getChannel({ echo: true })
|
|
441
|
-
.on('message', e => {
|
|
442
|
-
received.push(e.message)
|
|
443
|
-
|
|
444
|
-
if (received.length === messages.length) {
|
|
445
|
-
expect(received).toEqual(messages)
|
|
446
|
-
resolve()
|
|
447
|
-
}
|
|
448
|
-
})
|
|
449
|
-
.send(messages[0])
|
|
450
|
-
.send(messages[1])
|
|
451
|
-
.send(messages[2])
|
|
452
|
-
},
|
|
453
|
-
'connection is opened when registering a listener': async ({ channel, resolve }) => channel.on('open', resolve),
|
|
454
|
-
},
|
|
455
|
-
'EVENT PAYLOADS': {
|
|
456
|
-
'join': {
|
|
457
|
-
'default': async ({ channel, resolve }) =>
|
|
458
|
-
channel
|
|
459
|
-
.on('join', e => {
|
|
460
|
-
expect(e.users).toBe(1)
|
|
461
|
-
expect(e.uid).toBeUndefined()
|
|
462
|
-
expect(e.alias).toBeUndefined()
|
|
463
|
-
expect(e.date).toBeTypeOf('number')
|
|
464
|
-
resolve()
|
|
465
|
-
}),
|
|
466
|
-
'with { announce: true }': async ({ getChannel, resolve }) =>
|
|
467
|
-
getChannel({ announce: true, as: 'test-user' })
|
|
468
|
-
.on('join', e => {
|
|
469
|
-
expect(e.users).toBe(1)
|
|
470
|
-
expect(e.uid).toBeTypeOf('string')
|
|
471
|
-
expect(e.alias).toBe('test-user')
|
|
472
|
-
expect(e.date).toBeTypeOf('number')
|
|
473
|
-
resolve()
|
|
474
|
-
})
|
|
475
|
-
},
|
|
476
|
-
'leave': {
|
|
477
|
-
'default': async ({ channel, getChannel, resolve }) => {
|
|
478
|
-
channel
|
|
479
|
-
.on('leave', e => {
|
|
480
|
-
expect(e.users).toBe(1)
|
|
481
|
-
expect(e.uid).toBeUndefined()
|
|
482
|
-
expect(e.alias).toBeUndefined()
|
|
483
|
-
expect(e.date).toBeTypeOf('number')
|
|
484
|
-
resolve()
|
|
485
|
-
})
|
|
486
|
-
.on('open', () => {
|
|
487
|
-
getChannel().push('test')
|
|
488
|
-
})
|
|
489
|
-
},
|
|
490
|
-
'with { announce: true }': async ({ getChannel, resolve }) => {
|
|
491
|
-
getChannel()
|
|
492
|
-
.on('leave', e => {
|
|
493
|
-
expect(e.users).toBe(1)
|
|
494
|
-
expect(e.uid).toBeTypeOf('string')
|
|
495
|
-
expect(e.alias).toBe('test-user')
|
|
496
|
-
expect(e.date).toBeTypeOf('number')
|
|
497
|
-
resolve()
|
|
498
|
-
})
|
|
499
|
-
.on('open', () => {
|
|
500
|
-
getChannel({ announce: true, as: 'test-user' }).push('test')
|
|
501
|
-
})
|
|
502
|
-
}
|
|
503
|
-
},
|
|
504
|
-
'message': {
|
|
505
|
-
'default': async ({ getChannel, resolve }) =>
|
|
506
|
-
getChannel({ echo: true })
|
|
507
|
-
.on('message', e => {
|
|
508
|
-
expect(e.message).toBe('test')
|
|
509
|
-
expect(e.uid).toBeTypeOf('string')
|
|
510
|
-
expect(e.alias).toBeUndefined()
|
|
511
|
-
expect(e.date).toBeTypeOf('number')
|
|
512
|
-
resolve()
|
|
513
|
-
})
|
|
514
|
-
.send('test'),
|
|
515
|
-
'with { alias: string } includes users alias in payload': async ({ getChannel, resolve }) =>
|
|
516
|
-
getChannel({ echo: true, alias: 'test-user' })
|
|
517
|
-
.on('message', e => {
|
|
518
|
-
expect(e.message).toBe('test')
|
|
519
|
-
expect(e.uid).toBeTypeOf('string')
|
|
520
|
-
expect(e.alias).toBe('test-user')
|
|
521
|
-
expect(e.date).toBeTypeOf('number')
|
|
522
|
-
resolve()
|
|
523
|
-
})
|
|
524
|
-
.send('test'),
|
|
525
|
-
},
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// setup function for each test
|
|
531
|
-
const setup = () => {
|
|
532
|
-
const id = 'itty:itty-sockets:test-' + Math.random().toString(36).slice(2)
|
|
533
|
-
const getChannel = (options = {}): IttySocket<UseItty> => {
|
|
534
|
-
const channel = connect(id, options)
|
|
535
|
-
OPEN_CHANNELS.push(channel)
|
|
536
|
-
return channel
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return {
|
|
540
|
-
getChannel,
|
|
541
|
-
channel: getChannel(id) as IttySocket<UseItty>,
|
|
542
|
-
spy: mock(() => {}),
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// recursive test runner
|
|
547
|
-
const runTests = (tests: TestTree) => {
|
|
548
|
-
for (const [name, test] of Object.entries(tests)) {
|
|
549
|
-
if (typeof test === 'function') {
|
|
550
|
-
if (test.constructor.name === 'AsyncFunction') {
|
|
551
|
-
// @ts-ignore
|
|
552
|
-
it(name, () => new Promise(resolve => test({ ...setup(), resolve })))
|
|
553
|
-
} else {
|
|
554
|
-
// @ts-ignore
|
|
555
|
-
it(name, () => test({ ...setup() }))
|
|
556
|
-
}
|
|
557
|
-
} else {
|
|
558
|
-
describe(name, () => runTests(test))
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// run the tests!
|
|
564
|
-
runTests(tests)
|
|
565
|
-
|
|
566
|
-
// close any open channels
|
|
567
|
-
afterAll(() => {
|
|
568
|
-
console.log(`closing ${OPEN_CHANNELS.length} channels`)
|
|
569
|
-
OPEN_CHANNELS.forEach(channel => channel.close())
|
|
570
|
-
})
|
package/src/connect.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
type IttySocketEvent<BaseFormat> = BaseFormat extends UseItty
|
|
2
|
-
? 'open' | 'close' | 'message' | 'join' | 'leave'
|
|
3
|
-
: 'open' | 'close' | 'message'
|
|
4
|
-
|
|
5
|
-
type Timestamp = { date: number }
|
|
6
|
-
type UserDetails = { uid: string, alias?: string }
|
|
7
|
-
type OptionalUserDetails = { uid?: string, alias?: string }
|
|
8
|
-
|
|
9
|
-
export type UseItty<MessageType = any> = {
|
|
10
|
-
message: MessageType
|
|
11
|
-
} & UserDetails & Timestamp
|
|
12
|
-
|
|
13
|
-
export type MessageEvent<MessageType = any> = {
|
|
14
|
-
message: MessageType
|
|
15
|
-
} & Timestamp & OptionalUserDetails
|
|
16
|
-
|
|
17
|
-
export type JoinEvent = {
|
|
18
|
-
type: 'join'
|
|
19
|
-
users: number
|
|
20
|
-
} & Timestamp & OptionalUserDetails
|
|
21
|
-
|
|
22
|
-
export type LeaveEvent = {
|
|
23
|
-
type: 'leave'
|
|
24
|
-
users: number
|
|
25
|
-
} & Timestamp & OptionalUserDetails
|
|
26
|
-
|
|
27
|
-
export type ErrorEvent = {
|
|
28
|
-
type: 'error'
|
|
29
|
-
message: string
|
|
30
|
-
} & Timestamp
|
|
31
|
-
|
|
32
|
-
export type IttySocketOptions = {
|
|
33
|
-
as?: string,
|
|
34
|
-
alias?: string,
|
|
35
|
-
echo?: true,
|
|
36
|
-
announce?: true,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface IttySocketConnect {
|
|
40
|
-
<BaseFormat = object>(
|
|
41
|
-
...args: BaseFormat extends UseItty
|
|
42
|
-
? [channelID: string, options?: IttySocketOptions]
|
|
43
|
-
: [url: string, queryParams?: any]
|
|
44
|
-
): IttySocket<BaseFormat>
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
type UseIttyEvents<BaseFormat> = {
|
|
48
|
-
on(type: 'join', listener: (event: JoinEvent) => any): IttySocket<BaseFormat>
|
|
49
|
-
on(type: 'leave', listener: (event: LeaveEvent) => any): IttySocket<BaseFormat>
|
|
50
|
-
on(type: 'error', listener: (event: ErrorEvent) => any): IttySocket<BaseFormat>
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
type SendMessage<BaseFormat> = BaseFormat extends UseItty
|
|
54
|
-
? <MessageFormat = any>(message: MessageFormat, uid?: string) => IttySocket<BaseFormat>
|
|
55
|
-
: <MessageFormat = any>(message: MessageFormat) => IttySocket<BaseFormat>
|
|
56
|
-
|
|
57
|
-
export type IttySocket<BaseFormat = object> = {
|
|
58
|
-
open: () => IttySocket<BaseFormat>
|
|
59
|
-
close: () => IttySocket<BaseFormat>
|
|
60
|
-
send: SendMessage<BaseFormat>
|
|
61
|
-
push: SendMessage<BaseFormat>
|
|
62
|
-
remove(type: IttySocketEvent<BaseFormat>, listener: () => any): IttySocket<BaseFormat>
|
|
63
|
-
remove(type: string, listener: () => any): IttySocket<BaseFormat>
|
|
64
|
-
|
|
65
|
-
// EVENTS
|
|
66
|
-
on(type: 'open', listener: () => any): IttySocket<BaseFormat>
|
|
67
|
-
on(type: 'close', listener: () => any): IttySocket<BaseFormat>
|
|
68
|
-
on<MessageFormat = BaseFormat>(type: 'message', listener: (event: BaseFormat & MessageFormat) => any): IttySocket<BaseFormat>
|
|
69
|
-
on<MessageFormat = BaseFormat>(type: string, listener: (event: BaseFormat & MessageFormat & { type: string }) => any): IttySocket<BaseFormat>
|
|
70
|
-
on<MessageFormat = BaseFormat>(type: (event?: any) => any, listener: (event: BaseFormat & MessageFormat & { type: string }) => any): IttySocket<BaseFormat>
|
|
71
|
-
} & (BaseFormat extends UseItty ? UseIttyEvents<BaseFormat> : object)
|
|
72
|
-
|
|
73
|
-
export let connect: IttySocketConnect = (channelId: string, options = {}) => {
|
|
74
|
-
let ws: WebSocket | null,
|
|
75
|
-
closeAfterSend: any,
|
|
76
|
-
queue: string[] = [],
|
|
77
|
-
events: Record<string, Array<(event?: any) => any>> = {}
|
|
78
|
-
|
|
79
|
-
let open = () => (
|
|
80
|
-
ws || (
|
|
81
|
-
// @ts-ignore - options will be cast as string regardless of what is passed
|
|
82
|
-
ws = new WebSocket((/^wss?:/.test(channelId) ? channelId : 'wss://itty.ws/c/' + channelId) + '?' + new URLSearchParams(options)),
|
|
83
|
-
|
|
84
|
-
ws.onmessage = (
|
|
85
|
-
event: any,
|
|
86
|
-
parsed = JSON.parse(event.data),
|
|
87
|
-
payload = parsed?.message,
|
|
88
|
-
eventPayload = {
|
|
89
|
-
...(payload?.[0] == null && payload),
|
|
90
|
-
...parsed,
|
|
91
|
-
},
|
|
92
|
-
) =>
|
|
93
|
-
[eventPayload.type, parsed.type ? 0 : 'message', '*'].map(key =>
|
|
94
|
-
events[key]?.map(listener => listener(eventPayload))
|
|
95
|
-
),
|
|
96
|
-
|
|
97
|
-
ws.onopen = () => (
|
|
98
|
-
queue.splice(0).map(m => ws!.send(m)),
|
|
99
|
-
events.open?.map(listener => listener(closeAfterSend)),
|
|
100
|
-
closeAfterSend && ws?.close()
|
|
101
|
-
),
|
|
102
|
-
|
|
103
|
-
ws.onclose = () => (
|
|
104
|
-
closeAfterSend = ws = null,
|
|
105
|
-
events.close?.map(listener => listener(closeAfterSend))
|
|
106
|
-
)
|
|
107
|
-
),
|
|
108
|
-
socket
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
let socket: any = {
|
|
112
|
-
open,
|
|
113
|
-
send: (message: any, recipient?: string) => (
|
|
114
|
-
message = (recipient ? `\x1F${recipient}\x1F` : '') + JSON.stringify(message),
|
|
115
|
-
// @ts-ignore
|
|
116
|
-
ws?.readyState & 1 ? ws!.send(message) : queue.push(message),
|
|
117
|
-
open()
|
|
118
|
-
),
|
|
119
|
-
on: (type: any, listener: (e?: any) => any) => (
|
|
120
|
-
(events[type?.[0] ? type : '*'] ??= []).push(type?.[0] ? listener : (e: any) => type?.(e) && listener(e)),
|
|
121
|
-
open()
|
|
122
|
-
),
|
|
123
|
-
remove: (type: any, listener: () => any) => (
|
|
124
|
-
events[type] = events[type]?.filter((l: any) => l != listener),
|
|
125
|
-
socket
|
|
126
|
-
),
|
|
127
|
-
// @ts-ignore
|
|
128
|
-
close: () => (ws?.readyState & 1 ? ws!.close() : closeAfterSend = 1, socket),
|
|
129
|
-
push: (message: any, recipient?: string) => (closeAfterSend = 1, socket.send(message, recipient!)),
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return socket
|
|
133
|
-
}
|
package/src/connect.ts.backup
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
type IttySocketEvent<BaseFormat> = BaseFormat extends UseItty
|
|
2
|
-
? 'open' | 'close' | 'message' | 'join' | 'leave'
|
|
3
|
-
: 'open' | 'close' | 'message'
|
|
4
|
-
|
|
5
|
-
type Timestamp = { date: number }
|
|
6
|
-
type UserDetails = { uid: string, alias?: string }
|
|
7
|
-
type OptionalUserDetails = { uid?: string, alias?: string }
|
|
8
|
-
|
|
9
|
-
export type UseItty<MessageType = any> = {
|
|
10
|
-
message: MessageType
|
|
11
|
-
} & UserDetails & Timestamp
|
|
12
|
-
|
|
13
|
-
export type MessageEvent<MessageType = any> = {
|
|
14
|
-
message: MessageType
|
|
15
|
-
} & Timestamp & OptionalUserDetails
|
|
16
|
-
|
|
17
|
-
export type JoinEvent = {
|
|
18
|
-
type: 'join'
|
|
19
|
-
users: number
|
|
20
|
-
} & Timestamp & OptionalUserDetails
|
|
21
|
-
|
|
22
|
-
export type LeaveEvent = {
|
|
23
|
-
type: 'leave'
|
|
24
|
-
users: number
|
|
25
|
-
} & Timestamp & OptionalUserDetails
|
|
26
|
-
|
|
27
|
-
export type ErrorEvent = {
|
|
28
|
-
type: 'error'
|
|
29
|
-
message: string
|
|
30
|
-
} & Timestamp
|
|
31
|
-
|
|
32
|
-
export type IttySocketOptions = {
|
|
33
|
-
as?: string,
|
|
34
|
-
alias?: string,
|
|
35
|
-
echo?: true,
|
|
36
|
-
announce?: true,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface IttySocketConnect {
|
|
40
|
-
<BaseFormat = object>(
|
|
41
|
-
...args: BaseFormat extends UseItty
|
|
42
|
-
? [channelID: string, options?: IttySocketOptions]
|
|
43
|
-
: [url: string, queryParams?: any]
|
|
44
|
-
): IttySocket<BaseFormat>
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
type UseIttyEvents<BaseFormat> = {
|
|
48
|
-
on(type: 'join', listener: (event: JoinEvent) => any): IttySocket<BaseFormat>
|
|
49
|
-
on(type: 'leave', listener: (event: LeaveEvent) => any): IttySocket<BaseFormat>
|
|
50
|
-
on(type: 'error', listener: (event: ErrorEvent) => any): IttySocket<BaseFormat>
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
type SendMessage<BaseFormat> = BaseFormat extends UseItty
|
|
54
|
-
? <MessageFormat = any>(message: MessageFormat, uid?: string) => IttySocket<BaseFormat>
|
|
55
|
-
: <MessageFormat = any>(message: MessageFormat) => IttySocket<BaseFormat>
|
|
56
|
-
|
|
57
|
-
export type IttySocket<BaseFormat = object> = {
|
|
58
|
-
open: () => IttySocket<BaseFormat>
|
|
59
|
-
close: () => IttySocket<BaseFormat>
|
|
60
|
-
send: SendMessage<BaseFormat>
|
|
61
|
-
push: SendMessage<BaseFormat>
|
|
62
|
-
remove(type: IttySocketEvent<BaseFormat>, listener: () => any): IttySocket<BaseFormat>
|
|
63
|
-
remove(type: string, listener: () => any): IttySocket<BaseFormat>
|
|
64
|
-
|
|
65
|
-
// EVENTS
|
|
66
|
-
on(type: 'open', listener: () => any): IttySocket<BaseFormat>
|
|
67
|
-
on(type: 'close', listener: () => any): IttySocket<BaseFormat>
|
|
68
|
-
on<MessageFormat = BaseFormat>(type: 'message', listener: (event: BaseFormat & MessageFormat) => any): IttySocket<BaseFormat>
|
|
69
|
-
on<MessageFormat = BaseFormat>(type: string, listener: (event: BaseFormat & MessageFormat & { type: string }) => any): IttySocket<BaseFormat>
|
|
70
|
-
on<MessageFormat = BaseFormat>(type: (event?: any) => any, listener: (event: BaseFormat & MessageFormat & { type: string }) => any): IttySocket<BaseFormat>
|
|
71
|
-
} & (BaseFormat extends UseItty ? UseIttyEvents<BaseFormat> : object)
|
|
72
|
-
|
|
73
|
-
export let connect: IttySocketConnect = (channelId: string, options = {}) => {
|
|
74
|
-
let ws: WebSocket | null,
|
|
75
|
-
closeAfterSend: any,
|
|
76
|
-
queue: string[] = [],
|
|
77
|
-
events: Record<string, Array<(event?: any) => any>> = {}
|
|
78
|
-
|
|
79
|
-
let open = () => {
|
|
80
|
-
if (ws) return socket
|
|
81
|
-
|
|
82
|
-
// @ts-ignore - options will be cast as string regardless of what is passed
|
|
83
|
-
ws = new WebSocket((channelId[3] < 'a' ? channelId : 'wss://itty.ws/c/' + channelId) + '?' + new URLSearchParams(options))
|
|
84
|
-
|
|
85
|
-
ws.onmessage = (
|
|
86
|
-
event: any,
|
|
87
|
-
parsed = JSON.parse(event.data),
|
|
88
|
-
payload = parsed?.message,
|
|
89
|
-
eventPayload = {
|
|
90
|
-
...(payload?.[0] == null && payload),
|
|
91
|
-
...parsed,
|
|
92
|
-
},
|
|
93
|
-
) => (
|
|
94
|
-
events[eventPayload.type]?.map(listener => listener(eventPayload)),
|
|
95
|
-
parsed.type || events.message?.map(listener => listener(eventPayload)),
|
|
96
|
-
events['*']?.map(listener => listener(eventPayload))
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
ws.onopen = () => (
|
|
100
|
-
queue.splice(0).map(m => ws!.send(m)),
|
|
101
|
-
events.open?.map(listener => listener(closeAfterSend)),
|
|
102
|
-
closeAfterSend && ws?.close()
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
ws.onclose = () => (
|
|
106
|
-
closeAfterSend = ws = null,
|
|
107
|
-
events.close?.map(listener => listener(closeAfterSend))
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
return socket
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
let send = (message: any, recipient?: string) => (
|
|
114
|
-
message = (recipient ? `\x1F${recipient}\x1F` : '') + JSON.stringify(message),
|
|
115
|
-
ws?.readyState & 1 ? ws!.send(message) : queue.push(message),
|
|
116
|
-
open()
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
let socket: any = {
|
|
120
|
-
open,
|
|
121
|
-
send,
|
|
122
|
-
on: (type: any, listener: (e?: any) => any) => (
|
|
123
|
-
(events[type?.[0] ? type : '*'] ??= []).push(type?.[0] ? listener : (e: any) => type(e) && listener(e)),
|
|
124
|
-
open()
|
|
125
|
-
),
|
|
126
|
-
remove: (type: any, listener: () => any) => (
|
|
127
|
-
events[type] = events[type]?.filter((l: any) => l != listener),
|
|
128
|
-
socket
|
|
129
|
-
),
|
|
130
|
-
close: () => (ws?.readyState & 1 ? ws!.close() : closeAfterSend = 1, socket),
|
|
131
|
-
push: (message: any, recipient?: string) => (closeAfterSend = 1, send(message, recipient!)),
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return socket
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// type Chat = { type: 'chat', user: string, text: string }
|
|
138
|
-
|
|
139
|
-
// connect<UseItty>('doo')
|
|
140
|
-
// .on<Chat>('message', e => {
|
|
141
|
-
// e
|
|
142
|
-
// })
|
|
143
|
-
// .on<Chat>('chat', (e) => {
|
|
144
|
-
// e.text
|
|
145
|
-
// })
|
|
146
|
-
// .on<Chat>(v => v.type === 'chat', e => {
|
|
147
|
-
// e.texts
|
|
148
|
-
// })
|
|
149
|
-
// // .send() // test for (message) vs (message, recipient) based on BaseFormat type
|
|
150
|
-
// .remove('leave',
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// GENERICS TESTING
|
|
154
|
-
// connect('test')
|
|
155
|
-
// .on('message', (e) => e.message.name)
|
|
156
|
-
// .on('close', () => {})
|
|
157
|
-
// .send(123)
|
|
158
|
-
// .on<{ x: string }>('message', (e) => parseInt(e.message.x))
|
|
159
|
-
// .send<{ foo: string }>({ foo: 'bar' })
|
|
160
|
-
// .on('join', e => e.users + 4)
|
|
161
|
-
// .on('leave', e => e.users - 4)
|
|
162
|
-
// .on('error', e => e.message)
|
|
163
|
-
// .on('message', e => e.message.whatever)
|
|
164
|
-
// .on('message', e => e.whatever)
|
|
165
|
-
// .on<{ foo: string }>('message', e => e.message.foo)
|
|
166
|
-
// .on<{ foo: string }>('message', e => e.foo)
|
|
167
|
-
// .on<{ foo: string }>('chat', e => e.foo)
|
|
168
|
-
// .on<{ foo: string }>('chat', e => e.type)
|
|
169
|
-
// .send({ $type: 'chat', foo: 'bar' })
|
|
170
|
-
// .on('*', e => e.message)
|
|
171
|
-
|
|
172
|
-
// .on<{ age: number }>('message', (e) => e.message.name) // ERROR
|
|
173
|
-
// .on<{ x: number }>('message', (e) => parseInt(e.message.x)) // ERROR
|
|
174
|
-
// .send<string>(123) // ERROR
|
|
175
|
-
// .send<{ foo: string }>(123) // ERROR
|
|
176
|
-
// .send<{ foo: string }>({ foo: 'foo', bar: 123 }) // ERROR
|
|
177
|
-
// .on('join', e => e.message) // ERROR
|
|
178
|
-
// .on<{ foo: string }>('join', e => e.users) // ERROR
|
|
179
|
-
// .on('leave', e => e.message) // ERROR
|
|
180
|
-
// .on('error', e => e.foo) // ERROR
|
|
181
|
-
// .on<{ foo: string }>('message', e => e.message.whatever) // ERROR
|
|
182
|
-
// .on<{ foo: string }>('chat', e => e.bar) // ERROR
|
|
183
|
-
|
package/tsconfig.json
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"allowJs": true,
|
|
4
|
-
"allowSyntheticDefaultImports": true,
|
|
5
|
-
"baseUrl": "src",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"sourceMap": false,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"inlineSourceMap": false,
|
|
10
|
-
"lib": ["esnext", "es2015", "dom"],
|
|
11
|
-
"listEmittedFiles": false,
|
|
12
|
-
"listFiles": false,
|
|
13
|
-
"noFallthroughCasesInSwitch": true,
|
|
14
|
-
"pretty": true,
|
|
15
|
-
"rootDir": "src",
|
|
16
|
-
"skipLibCheck": true,
|
|
17
|
-
"strict": true,
|
|
18
|
-
"traceResolution": false,
|
|
19
|
-
"outDir": "",
|
|
20
|
-
"target": "esnext",
|
|
21
|
-
"module": "esnext"
|
|
22
|
-
},
|
|
23
|
-
"exclude": ["node_modules", "dist", "**/*.spec.ts"],
|
|
24
|
-
"include": ["src"]
|
|
25
|
-
}
|