itty-sockets 0.2.2 → 0.3.0

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/README.md CHANGED
@@ -12,18 +12,31 @@
12
12
  [![Issues](https://img.shields.io/github/issues/kwhitley/itty-sockets?style=flat-square)](https://github.com/kwhitley/itty-sockets/issues)
13
13
  [![Discord](https://img.shields.io/discord/832353585802903572?label=Discord&logo=Discord&style=flat-square&logoColor=fff)](https://discord.gg/53vyrZAu9u)
14
14
 
15
- ### [Documentation](https://itty.dev/itty-sockets)  |   [Discord](https://discord.gg/53vyrZAu9u)
15
+ ### [Documentation](https://ittysockets.io)  |   [Discord](https://discord.gg/53vyrZAu9u)
16
16
 
17
17
  ---
18
18
 
19
- Tiny realtime messaging client in under 450 bytes. No backend needed.
19
+ Tiny realtime messaging client in under 500 bytes. **No backend needed.**
20
20
 
21
- ## Example (using [ittysockets.io](https://ittysockets.io) public channels)
21
+ ## What does this solve?
22
+
23
+ Itty Sockets simplifies sending/receiving realtime data.
24
+
25
+ By pairing an ultra-tiny client (this) with the public **[ittysockets.io](https://ittysockets.io)** backend, you
26
+ can focus on sending/receiving messages, instead of building a transport layer.
27
+
28
+ The idea is simple:
29
+
30
+ 1. One or more parties connect to a channel (by name).
31
+ 2. They send/receive messages (this can be anything) in the channel.
32
+ 3. That's it!
33
+
34
+ # Example
22
35
  ```ts
23
- import { connect } from 'itty-sockets' // ~422 bytes
36
+ import { connect } from 'itty-sockets'
24
37
 
25
- // connect to a channel (optionally echo messages back to yourself)
26
- const foo = connect('my-secret-room-name', { echo: true })
38
+ // connect to a channel
39
+ const foo = connect('my-secret-room-name')
27
40
 
28
41
  foo
29
42
  // we can listen for messages
@@ -35,73 +48,112 @@ foo
35
48
  .send({ foo: 'bar' }) // { foo: "bar" }
36
49
  ```
37
50
 
38
- ## Features
39
-
40
- - Simple and powerful API for sending and receiving messages & data.
41
- - No backend service needed. Ours is fast and private.
42
- - Full TypeScript support, including custom types for messages.
43
- - Prevents WebSocket race conditions. Automatically connects when needed to send/listen.
44
- - Chainable. Every method returns the channel again.
45
- - Ultra-tiny. It's an itty library, after all.
51
+ ### Important Considerations
46
52
 
47
- ## What is itty-sockets?
53
+ 1. **There is no history/replay/storage.** It's a live stream only.
54
+ 2. **We don't authenticate.** [ittysockets.io](https://ittysockets.io) leverages security through obfuscation (a near-infinite number of channel names). Choose a more unique channel for more privacy. Need more? Consider encrypting/decrypting your payloads before transmission (this is easy).
55
+ 3. **There are no guarantees of delivery.** While [ittysockets.io](https://ittysockets.io) is *extremely* stable, it's a free public service that is provided without any guarantees of delivery or uptime. Manage risk accordingly.
48
56
 
49
- `itty-sockets` is a tiny messaging client that simplifies data/message transmission between users/connections.
50
- It's powered by [ittysockets.io](https://ittysockets.io), a free, fast, and private public service. The idea is simple:
51
-
52
- 1. Connect to a channel by name (creates a new channel if it doesn't exist).
53
- 2. Send/receive messages in the channel.
54
- 3. That's it!
55
-
56
- This is an easy way to transmit messages between clients, but comes with limitations and considerations:
57
-
58
- 1. **There is no history/replay.** It's a live stream.
59
- 2. **We don't authenticate.** Itty Sockets leverages security merely through obfuscation (a near-infinite number of channel names). Use a secure channel name and/or encode your payloads if concerned about eavesdropping. Add your own authentication layer, if needed.
60
- 3. **There are no guarantees of delivery.** Itty Sockets is not a traditional messaging system. It's a public service that is provided without any guarantees of delivery, order, or persistence. Use it for real-time communication, not for mission-critical data.
61
-
62
- ### Privacy Concerns
63
- **We do not store any messages or data**
64
- There is intentionally no message logging or tracking of any kind. It's easier for us that way, and safer for you.
57
+ <br />
65
58
 
66
- ## Browser Usage
59
+ # Getting Started
67
60
 
68
- For use in browser/DevTools scripting, copy and paste this snippet directly into your browser console, then use as normal:
61
+ ### 1. Import the [tiny client](https://npmjs.com/package/itty-sockets).
62
+ ```ts
63
+ import { connect } from 'itty-sockets'
64
+ ```
69
65
 
66
+ ...or simply paste this into your environment/console:
70
67
  <!-- BEGIN SNIPPET -->
71
68
  ```ts
72
- let connect=(e,s={})=>{let o,t=[],n=[],a=0,r={},c=()=>(o||(o=new WebSocket(`wss://ittysockets.io/r/${e??""}?${new URLSearchParams(s)}`),o.onopen=()=>{for(;t.length;)o?.send(t.shift());r.open?.(),a&&o?.close()},o.onmessage=(e,s=JSON.parse(e.data))=>{for(let e of n)e({...s,date:new Date(s.date)})},o.onclose=()=>(a=0,o=null,r.close?.())),l);const l=new Proxy(c,{get:(e,s)=>({open:c,close:()=>(1==o?.readyState?o.close():a=1,l),send:(e,s)=>(e=JSON.stringify(e),e=s?`@@${s}@@${e}`:e,1==o?.readyState?o.send(e)??l:(t.push(e),c())),push:(e,s)=>(a=1,l.send(e,s)),on:(e,s)=>(r[e]=s,"message"==e?(n.push(s),c()):l)}[s])});return l};
69
+ let connect=(e,o={})=>{let s,t=[],n=0,r={},a=()=>(s||(s=new WebSocket(`wss://ittysockets.io/r/${e}?${new URLSearchParams(o)}`),s.onopen=()=>{for(;t.length;)s?.send(t.shift());for(let e of r.open??[])e();n&&s?.close()},s.onmessage=(e,o=JSON.parse(e.data))=>{for(let e of r[o.type??"message"]??[])e({...o,date:new Date(o.date)})},s.onclose=()=>{n=0,s=null;for(let e of r.close??[])e()}),l);const l=new Proxy(a,{get:(e,o)=>({open:a,close:()=>(1==s?.readyState?s.close():n=1,l),send:(e,o)=>(e=JSON.stringify(e),e=o?`@@${o}@@${e}`:e,1==s?.readyState?(s.send(e),l):(t.push(e),a())),push:(e,o)=>(n=1,l.send(e,o)),on:(e,o)=>((r[e]??=[]).push(o),a()),remove:(e,o,s=r[e],t=s?.indexOf(o)??-1)=>(~t&&s?.splice(t,1),a())}[o])});return l};
73
70
  ```
74
71
  <!-- END SNIPPET -->
75
72
 
76
- afterwards:
73
+ <br />
74
+
75
+ ### 2. Create a Channel
76
+ To start, simply connect to a channel based on a unique name (this can be anything).
77
+
77
78
  ```ts
78
- // send a message on connect 'foo'
79
- connect('foo').push('hello world!')
79
+ import { connect } from 'itty-sockets'
80
+
81
+ // basic connection
82
+ const channel = connect('my-channels/my-super-secret-channel')
83
+
84
+ // with options
85
+ const channel = connect('my-channels/my-super-secret-channel', {
86
+ as: 'Kevin',
87
+ announce: true,
88
+ echo: true
89
+ })
80
90
  ```
81
91
 
82
- ## API
92
+ #### Connection Options
83
93
 
84
- | METHOD | DESCRIPTION | EXAMPLE |
94
+ | option | default value | description |
85
95
  | --- | --- | --- |
86
- | **connect(id, options)** | Creates a new channel connection | `connect('foo')` |
87
- | **.open()** | Opens/re-opens the connection (manually, usually not needed) | `channel.open()` |
88
- | **.close()** | Closes the connection | `channel.close()` |
89
- | **.send(message)** | Sends a message to the channel | `channel.send({ type: 'chat', text: 'hello' })` |
90
- | **.push(message)** | Sends a message and closes the connection | `channel.push({ type: 'goodbye' })` |
91
- | **.on('message', listener)** | Adds a message listener (multiple allowed) | `channel.on('message', event => console.log(event))` |
92
- | **.on('open', listener)** | Executes a listener on channel open (one allowed) | `channel.on('open', () => console.log('channel opened'))` |
93
- | **.on('close', listener)** | Executes a listener on channel close (one allowed) | `channel.on('close', () => console.log('channel closed'))` |
96
+ | `{ alias: 'any-string' }` | `undefined` | An optional display name to be included in your messages. |
97
+ | `{ as: 'any-string' }` | `undefined` | An optional display name to be included in your message (same as alias). |
98
+ | `{ announce: true }` | `false` | Shares your uid/alias when joining/leaving. | `false` |
99
+ | `{ echo: true }` | `false` | Echos messages back to original sender (good for testing). |
94
100
 
101
+ <br />
95
102
 
96
- ### Available Options
103
+ ### 3. Use the channel.
104
+ With the channel connected, simply call methods on it. Every method is chainable, returning the connection again (for more chaining).
97
105
 
98
- | OPTION | DESCRIPTION | DEFAULT | EXAMPLE |
106
+ | method | description | example |
107
+ | --- | --- | --- |
108
+ | **`.open()`** | Opens/re-opens the connection (manually, usually not needed). |
109
+ | **`.close()`** | Closes the connection. | `channel.close()` |
110
+ | **`.send(message: any)`** | Sends a message to the channel. This can be anything serializable with JSON.stringify. | `channel.send({ type: 'chat', text: 'hello' })` |
111
+ | **`.push(message: any)`** | Sends a message and immediately closes the connection. | `channel.push('Hello World!')` |
112
+ | **`.on(eventName: string, listener)`** | Add an event listener. | `channel.on('close', () => console.log('channel closed'))` |
113
+ | **`.remove(eventName: string, listener)`** | Remove an event listener. The 2nd argument must be the same listener function registered in the `on` method. | `channel.remove('open', myListenerFunction)` |
114
+
115
+ #### Example
116
+
117
+ ```ts
118
+
119
+ // connect
120
+ const channel = connect('my-secret-channel')
121
+
122
+ // add event listeners or send messages
123
+
124
+ channel
125
+ .on('message', ({ alias, uid, message, date }) =>
126
+ console.log(`${alias ?? uid} says: ${message} @ ${date.toLocaleTimeString()}`)
127
+ )
128
+ .on('join', ({ users }) =>
129
+ console.log(`A user has joined. There are now ${users} in the channel.`)
130
+ )
131
+ .on('leave', ({ users }) =>
132
+ console.log(`A user has left. There are now ${users} in the channel.`)
133
+ )
134
+ .send('Hello World!') // this will queue up and send the message once connected
135
+ ```
136
+
137
+ <br />
138
+
139
+ # Events
140
+ Each event can have multiple listeners registered on it. These are stable, even if the underlying WebSocket is broken/re-established.
141
+ | event name | description | payload | example |
99
142
  | --- | --- | --- | --- |
100
- | **alias** | An optional display name for the connection | `undefined` | `{ alias: 'Kevin' }` |
101
- | **echo** | Whether to echo messages back to the sender | `false` | `{ echo: true }` |
143
+ | `message` | Triggered when receiving a message event. | [MessageEvent](#messageevent) | `channel.on<MessageType = any>('message', listener)` |
144
+ | `join` | Triggered when a user (including self) joins the channel. This alerts all users that someone has joined, and informs them of the total number of users in the channel. If the joining party connected with { announce: true }, their user details will be shared with the channel. | [JoinEvent](#joineevent) | `channel.on('join', e => console.log('There are now', e.users, 'users in the channel.')` |
145
+ | `leave` | Triggered when a user leaves the channel. This alerts all users that someone has left, and informs them of the total number of users in the channel. If the leaving party connected with { announce: true }, their user details will be shared with the channel. | [LeaveEvent](#leaveeevent) | `channel.on('leave', e => console.log('There are now', e.users, 'users in the channel.')` |
146
+ | `error` | Triggered when the server sends an error to the user. This is rare. | [ErrorEvent](#error) | `channel.on('error', e => console.error('IttySockets Error:', e.message)` |
147
+ | `open` | Triggered when the connection is established. | none | `channel.on('open', () => console.log('connected to channel.')` |
148
+ | `close` | Triggered when the connection is closed. | none | `channel.on('close', () => console.log('disconnected from channel.')` |
102
149
 
103
150
 
104
- ## MessageEvent Format
151
+ <br />
152
+
153
+ ## EventTypes
154
+ All event types *other* than `message` are identified with a `type` attribute. For the sake of smaller payloads, `type` is omitted on normal messages.
155
+
156
+ #### MessageEvent
105
157
  ```ts
106
158
  type MessageEvent = {
107
159
  id: string // unique message ID
@@ -112,5 +164,46 @@ type MessageEvent = {
112
164
  }
113
165
  ```
114
166
 
167
+ #### JoinEvent <a id="joinevent" />
168
+ ```ts
169
+ type JoinEvent = {
170
+ type: 'join' // type of event
171
+ uid?: string // uid of joiner if { announce: true }
172
+ alias: string? // alias of joiner if { announce: true }
173
+ date: Date // date of event
174
+ users: number // new number of users in the channel
175
+ }
176
+ ```
177
+
178
+ #### LeaveEvent
179
+ ```ts
180
+ type LeaveEvent = {
181
+ type: 'leave' // type of event
182
+ uid?: string // uid of leaver if { announce: true }
183
+ alias: string? // alias of leaver if { announce: true }
184
+ date: Date // date of event
185
+ users: number // new number of users in the channel
186
+ }
187
+ ```
188
+
189
+ #### ErrorEvent
190
+ ```ts
191
+ type MessageEvent = {
192
+ type: 'error' // error event identifier
193
+ date: Date // JavaScript Date object
194
+ message: any // the message payload
195
+ }
196
+ ```
197
+
198
+ <br />
199
+
200
+ # Privacy
201
+ [ittysockets.io](https://ittysockets.io) is a free, public-use, but _private_ service.
202
+
203
+ It was designed by me (a developer), to help myself and other developers achieve cool things. As such:
204
+
205
+ 1. Your messages are never transmitted to anything other than the sockets on the channel you're connected to. No third-party service, no loggers, no storage (local or otherwise), not even a collection in memory. This protects your privacy/data, but keeps my costs to virtually zero, allowing me to share this service with the world... hopefully indefinitely.
206
+
207
+ 2. I ask that you please use the channels responsibly. We're all sharing this space!
115
208
 
116
209
 
package/connect.d.ts CHANGED
@@ -1,10 +1,30 @@
1
- export type AllowedProperty = 'open' | 'close' | 'send' | 'push' | 'on';
2
- export type MessageEvent<MessageType = any> = {
1
+ export type IttySocketEvent = 'open' | 'close' | 'message' | 'join' | 'leave';
2
+ type Date = {
3
3
  date: Date;
4
+ };
5
+ type UserDetails = {
6
+ uid: string;
7
+ alias?: string;
8
+ };
9
+ type OptionalUserDetails = {
4
10
  uid?: string;
5
11
  alias?: string;
6
- message: MessageType;
7
12
  };
13
+ export type MessageEvent<MessageType = any> = {
14
+ message: MessageType;
15
+ } & Date & UserDetails;
16
+ export type JoinEvent = {
17
+ type: 'join';
18
+ users: number;
19
+ } & Date & OptionalUserDetails;
20
+ export type LeaveEvent = {
21
+ type: 'leave';
22
+ users: number;
23
+ } & Date & OptionalUserDetails;
24
+ export type ErrorEvent = {
25
+ type: 'error';
26
+ message: string;
27
+ } & Date;
8
28
  export type SendMessage = <MessageFormat = any>(message: MessageFormat, recipient?: string) => IttySocket;
9
29
  export type IttySocket = {
10
30
  open: () => IttySocket;
@@ -12,11 +32,18 @@ export type IttySocket = {
12
32
  connected: boolean;
13
33
  send: SendMessage;
14
34
  push: SendMessage;
15
- on: <T extends string, MessageType = any>(type: T, listener: T extends 'message' ? (event: MessageEvent<MessageType>) => any : () => any) => IttySocket;
35
+ on<MessageFormat = any>(type: 'message', listener: (event: MessageEvent<MessageFormat>) => any): IttySocket;
36
+ on(type: 'join', listener: (event: JoinEvent) => any): IttySocket;
37
+ on(type: 'leave', listener: (event: LeaveEvent) => any): IttySocket;
38
+ on(type: 'error', listener: (event: ErrorEvent) => any): IttySocket;
39
+ on(type: Exclude<IttySocketEvent, 'message'>, listener: () => any): IttySocket;
40
+ remove(type: IttySocketEvent, listener: () => any): IttySocket;
16
41
  };
17
42
  export type IttySocketOptions = {
18
43
  as?: string;
19
44
  alias?: string;
20
- echo?: boolean;
45
+ echo?: true;
46
+ announce?: true;
21
47
  };
22
- export declare const connect: (id: string, options?: IttySocketOptions) => IttySocket;
48
+ export declare const connect: (channelId: string, options?: IttySocketOptions) => IttySocket;
49
+ export {};
package/connect.js CHANGED
@@ -1 +1 @@
1
- "use strict";exports.connect=(e,s={})=>{let t,n=[],o=[],a=0,r={},c=()=>(t||(t=new WebSocket(`wss://ittysockets.io/r/${e??""}?${new URLSearchParams(s)}`),t.onopen=()=>{for(;n.length;)t?.send(n.shift());r.open?.(),a&&t?.close()},t.onmessage=(e,s=JSON.parse(e.data))=>{for(let e of o)e({...s,date:new Date(s.date)})},t.onclose=()=>(a=0,t=null,r.close?.())),l);const l=new Proxy(c,{get:(e,s)=>({open:c,close:()=>(1==t?.readyState?t.close():a=1,l),send:(e,s)=>(e=JSON.stringify(e),e=s?`@@${s}@@${e}`:e,1==t?.readyState?t.send(e)??l:(n.push(e),c())),push:(e,s)=>(a=1,l.send(e,s)),on:(e,s)=>(r[e]=s,"message"==e?(o.push(s),c()):l)}[s])});return l};
1
+ "use strict";exports.connect=(e,s={})=>{let o,t=[],n=0,r={},a=()=>(o||(o=new WebSocket(`wss://ittysockets.io/r/${e}?${new URLSearchParams(s)}`),o.onopen=()=>{for(;t.length;)o?.send(t.shift());for(let e of r.open??[])e();n&&o?.close()},o.onmessage=(e,s=JSON.parse(e.data))=>{for(let e of r[s.type??"message"]??[])e({...s,date:new Date(s.date)})},o.onclose=()=>{n=0,o=null;for(let e of r.close??[])e()}),c);const c=new Proxy(a,{get:(e,s)=>({open:a,close:()=>(1==o?.readyState?o.close():n=1,c),send:(e,s)=>(e=JSON.stringify(e),e=s?`@@${s}@@${e}`:e,1==o?.readyState?(o.send(e),c):(t.push(e),a())),push:(e,s)=>(n=1,c.send(e,s)),on:(e,s)=>((r[e]??=[]).push(s),a()),remove:(e,s,o=r[e],t=o?.indexOf(s)??-1)=>(~t&&o?.splice(t,1),a())}[s])});return c};
package/connect.mjs CHANGED
@@ -1 +1 @@
1
- const e=(e,s={})=>{let o,t=[],n=[],a=0,r={},c=()=>(o||(o=new WebSocket(`wss://ittysockets.io/r/${e??""}?${new URLSearchParams(s)}`),o.onopen=()=>{for(;t.length;)o?.send(t.shift());r.open?.(),a&&o?.close()},o.onmessage=(e,s=JSON.parse(e.data))=>{for(let e of n)e({...s,date:new Date(s.date)})},o.onclose=()=>(a=0,o=null,r.close?.())),l);const l=new Proxy(c,{get:(e,s)=>({open:c,close:()=>(1==o?.readyState?o.close():a=1,l),send:(e,s)=>(e=JSON.stringify(e),e=s?`@@${s}@@${e}`:e,1==o?.readyState?o.send(e)??l:(t.push(e),c())),push:(e,s)=>(a=1,l.send(e,s)),on:(e,s)=>(r[e]=s,"message"==e?(n.push(s),c()):l)}[s])});return l};export{e as connect};
1
+ const e=(e,o={})=>{let s,t=[],n=0,r={},a=()=>(s||(s=new WebSocket(`wss://ittysockets.io/r/${e}?${new URLSearchParams(o)}`),s.onopen=()=>{for(;t.length;)s?.send(t.shift());for(let e of r.open??[])e();n&&s?.close()},s.onmessage=(e,o=JSON.parse(e.data))=>{for(let e of r[o.type??"message"]??[])e({...o,date:new Date(o.date)})},s.onclose=()=>{n=0,s=null;for(let e of r.close??[])e()}),l);const l=new Proxy(a,{get:(e,o)=>({open:a,close:()=>(1==s?.readyState?s.close():n=1,l),send:(e,o)=>(e=JSON.stringify(e),e=o?`@@${o}@@${e}`:e,1==s?.readyState?(s.send(e),l):(t.push(e),a())),push:(e,o)=>(n=1,l.send(e,o)),on:(e,o)=>((r[e]??=[]).push(o),a()),remove:(e,o,s=r[e],t=s?.indexOf(o)??-1)=>(~t&&s?.splice(t,1),a())}[o])});return l};export{e as connect};
@@ -1 +1 @@
1
- let connect=(e,s={})=>{let o,t=[],n=[],a=0,r={},c=()=>(o||(o=new WebSocket(`wss://ittysockets.io/r/${e??""}?${new URLSearchParams(s)}`),o.onopen=()=>{for(;t.length;)o?.send(t.shift());r.open?.(),a&&o?.close()},o.onmessage=(e,s=JSON.parse(e.data))=>{for(let e of n)e({...s,date:new Date(s.date)})},o.onclose=()=>(a=0,o=null,r.close?.())),l);const l=new Proxy(c,{get:(e,s)=>({open:c,close:()=>(1==o?.readyState?o.close():a=1,l),send:(e,s)=>(e=JSON.stringify(e),e=s?`@@${s}@@${e}`:e,1==o?.readyState?o.send(e)??l:(t.push(e),c())),push:(e,s)=>(a=1,l.send(e,s)),on:(e,s)=>(r[e]=s,"message"==e?(n.push(s),c()):l)}[s])});return l};
1
+ let connect=(e,o={})=>{let s,t=[],n=0,r={},a=()=>(s||(s=new WebSocket(`wss://ittysockets.io/r/${e}?${new URLSearchParams(o)}`),s.onopen=()=>{for(;t.length;)s?.send(t.shift());for(let e of r.open??[])e();n&&s?.close()},s.onmessage=(e,o=JSON.parse(e.data))=>{for(let e of r[o.type??"message"]??[])e({...o,date:new Date(o.date)})},s.onclose=()=>{n=0,s=null;for(let e of r.close??[])e()}),l);const l=new Proxy(a,{get:(e,o)=>({open:a,close:()=>(1==s?.readyState?s.close():n=1,l),send:(e,o)=>(e=JSON.stringify(e),e=o?`@@${o}@@${e}`:e,1==s?.readyState?(s.send(e),l):(t.push(e),a())),push:(e,o)=>(n=1,l.send(e,o)),on:(e,o)=>((r[e]??=[]).push(o),a()),remove:(e,o,s=r[e],t=s?.indexOf(o)??-1)=>(~t&&s?.splice(t,1),a())}[o])});return l};
package/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";exports.connect=(e,s={})=>{let t,n=[],o=[],a=0,r={},c=()=>(t||(t=new WebSocket(`wss://ittysockets.io/r/${e??""}?${new URLSearchParams(s)}`),t.onopen=()=>{for(;n.length;)t?.send(n.shift());r.open?.(),a&&t?.close()},t.onmessage=(e,s=JSON.parse(e.data))=>{for(let e of o)e({...s,date:new Date(s.date)})},t.onclose=()=>(a=0,t=null,r.close?.())),l);const l=new Proxy(c,{get:(e,s)=>({open:c,close:()=>(1==t?.readyState?t.close():a=1,l),send:(e,s)=>(e=JSON.stringify(e),e=s?`@@${s}@@${e}`:e,1==t?.readyState?t.send(e)??l:(n.push(e),c())),push:(e,s)=>(a=1,l.send(e,s)),on:(e,s)=>(r[e]=s,"message"==e?(o.push(s),c()):l)}[s])});return l};
1
+ "use strict";exports.connect=(e,s={})=>{let o,t=[],n=0,r={},a=()=>(o||(o=new WebSocket(`wss://ittysockets.io/r/${e}?${new URLSearchParams(s)}`),o.onopen=()=>{for(;t.length;)o?.send(t.shift());for(let e of r.open??[])e();n&&o?.close()},o.onmessage=(e,s=JSON.parse(e.data))=>{for(let e of r[s.type??"message"]??[])e({...s,date:new Date(s.date)})},o.onclose=()=>{n=0,o=null;for(let e of r.close??[])e()}),c);const c=new Proxy(a,{get:(e,s)=>({open:a,close:()=>(1==o?.readyState?o.close():n=1,c),send:(e,s)=>(e=JSON.stringify(e),e=s?`@@${s}@@${e}`:e,1==o?.readyState?(o.send(e),c):(t.push(e),a())),push:(e,s)=>(n=1,c.send(e,s)),on:(e,s)=>((r[e]??=[]).push(s),a()),remove:(e,s,o=r[e],t=o?.indexOf(s)??-1)=>(~t&&o?.splice(t,1),a())}[s])});return c};
package/index.mjs CHANGED
@@ -1 +1 @@
1
- const e=(e,s={})=>{let o,t=[],n=[],a=0,r={},c=()=>(o||(o=new WebSocket(`wss://ittysockets.io/r/${e??""}?${new URLSearchParams(s)}`),o.onopen=()=>{for(;t.length;)o?.send(t.shift());r.open?.(),a&&o?.close()},o.onmessage=(e,s=JSON.parse(e.data))=>{for(let e of n)e({...s,date:new Date(s.date)})},o.onclose=()=>(a=0,o=null,r.close?.())),l);const l=new Proxy(c,{get:(e,s)=>({open:c,close:()=>(1==o?.readyState?o.close():a=1,l),send:(e,s)=>(e=JSON.stringify(e),e=s?`@@${s}@@${e}`:e,1==o?.readyState?o.send(e)??l:(t.push(e),c())),push:(e,s)=>(a=1,l.send(e,s)),on:(e,s)=>(r[e]=s,"message"==e?(n.push(s),c()):l)}[s])});return l};export{e as connect};
1
+ const e=(e,o={})=>{let s,t=[],n=0,r={},a=()=>(s||(s=new WebSocket(`wss://ittysockets.io/r/${e}?${new URLSearchParams(o)}`),s.onopen=()=>{for(;t.length;)s?.send(t.shift());for(let e of r.open??[])e();n&&s?.close()},s.onmessage=(e,o=JSON.parse(e.data))=>{for(let e of r[o.type??"message"]??[])e({...o,date:new Date(o.date)})},s.onclose=()=>{n=0,s=null;for(let e of r.close??[])e()}),l);const l=new Proxy(a,{get:(e,o)=>({open:a,close:()=>(1==s?.readyState?s.close():n=1,l),send:(e,o)=>(e=JSON.stringify(e),e=o?`@@${o}@@${e}`:e,1==s?.readyState?(s.send(e),l):(t.push(e),a())),push:(e,o)=>(n=1,l.send(e,o)),on:(e,o)=>((r[e]??=[]).push(o),a()),remove:(e,o,s=r[e],t=s?.indexOf(o)??-1)=>(~t&&s?.splice(t,1),a())}[o])});return l};export{e as connect};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itty-sockets",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Tiny realtime messaging client in under 450 bytes. No backend needed.",
5
5
  "main": "./sockets.js",
6
6
  "module": "./sockets.mjs",
@@ -12,7 +12,7 @@
12
12
  "prerelease": "bun run verify",
13
13
  "prerelease:next": "bun run verify",
14
14
  "prebuild": "rimraf dist && mkdir dist",
15
- "build": "rollup -c && node scripts/inject-snippet.mjs",
15
+ "build": "rollup -c && bun readme-inject.mjs",
16
16
  "release": "release --tag --push --patch --src=dist",
17
17
  "release:next": "release --tag --push --type=next --src=dist"
18
18
  },