iosignal 2.2.1 → 3.0.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.
Files changed (49) hide show
  1. package/README.md +248 -114
  2. package/dist/io.d.ts +481 -0
  3. package/dist/io.js +4 -5
  4. package/dist/io.js.map +1 -1
  5. package/dist/io.min.js +4 -5
  6. package/dist/io.min.js.map +1 -1
  7. package/dist/iosignal.js +9559 -0
  8. package/dist/types/client/IOCore.d.ts +408 -0
  9. package/dist/types/client/browser/IOWebSocket.d.ts +75 -0
  10. package/dist/types/common/constants.d.ts +169 -0
  11. package/dist/types/common/payload.d.ts +6 -0
  12. package/dist/types/common/quotaTable.d.ts +47 -0
  13. package/docs/README.ko.md +371 -0
  14. package/docs/iosignal_architecture.png +0 -0
  15. package/examples/react-chat-js/dist/assets/index-Cy4qaVcJ.js +56 -0
  16. package/examples/react-chat-js/dist/assets/index-NETrjYhz.css +1 -0
  17. package/examples/react-chat-js/dist/index.html +14 -0
  18. package/examples/react-chat-js/index.html +13 -0
  19. package/examples/react-chat-js/package-lock.json +1628 -0
  20. package/examples/react-chat-js/package.json +20 -0
  21. package/examples/react-chat-js/src/App.css +61 -0
  22. package/examples/react-chat-js/src/App.jsx +125 -0
  23. package/examples/react-chat-js/src/index.css +13 -0
  24. package/examples/react-chat-js/src/main.jsx +13 -0
  25. package/examples/react-chat-js/src/shared_io.js +12 -0
  26. package/examples/react-chat-js/vite.config.js +7 -0
  27. package/examples/server/index.js +12 -0
  28. package/examples/server/package.json +12 -0
  29. package/package.json +24 -12
  30. package/rollup.config.js +13 -8
  31. package/src/auth/Auth_File.js +1 -1
  32. package/src/client/IOCongSocket.js +20 -9
  33. package/src/client/IOCore.js +422 -77
  34. package/src/client/IOWS.js +12 -15
  35. package/src/client/browser/IOWebSocket.js +217 -0
  36. package/src/common/constants.js +91 -1
  37. package/test-nodejs/server-Auth_File.js +1 -1
  38. package/tsconfig.json +24 -0
  39. package/dist/iosignal.cjs +0 -23
  40. package/dist/iosignal.mjs +0 -23
  41. package/img/iosignal_stack.png +0 -0
  42. package/src/client/IOWebSocket.js +0 -118
  43. package/test-nodejs/client.cjs +0 -17
  44. package/test-nodejs/server-commonjs.cjs +0 -9
  45. /package/{auth_file.mjs → auth_file.js} +0 -0
  46. /package/test-nodejs/{client.mjs → client.js} +0 -0
  47. /package/test-nodejs/{client_api_reply.mjs → client_api_reply.js} +0 -0
  48. /package/test-nodejs/{server-esm.mjs → server.js} +0 -0
  49. /package/test-nodejs/{simple-server.mjs → simple-server.js} +0 -0
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "react-chat-js",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^18.2.0",
13
+ "react-dom": "^18.2.0",
14
+ "iosignal": "file:../.."
15
+ },
16
+ "devDependencies": {
17
+ "@vitejs/plugin-react": "^4.2.1",
18
+ "vite": "^5.0.8"
19
+ }
20
+ }
@@ -0,0 +1,61 @@
1
+ html, body, #root, .App {
2
+ height: 100%;
3
+ margin: 0;
4
+ display: flex;
5
+ flex-direction: column;
6
+ }
7
+
8
+ .App {
9
+ padding: 20px;
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ h1 {
14
+ margin-top: 0;
15
+ color: #333;
16
+ }
17
+
18
+ .messages {
19
+ flex-grow: 1; /* 남은 공간을 모두 차지하도록 설정 */
20
+ border: 1px solid #ccc;
21
+ padding: 10px;
22
+ overflow-y: auto; /* 내용이 넘치면 스크롤바 표시 */
23
+ margin-bottom: 10px;
24
+ display: flex;
25
+ flex-direction: column; /* 메시지를 아래에서 위로 쌓이도록 */
26
+ justify-content: flex-end; /* 메시지를 아래로 정렬 */
27
+ }
28
+
29
+ .messages div {
30
+ background-color: #f0f0f0;
31
+ padding: 8px;
32
+ margin-bottom: 5px;
33
+ border-radius: 5px;
34
+ text-align: left;
35
+ }
36
+
37
+ .input-area {
38
+ display: flex;
39
+ gap: 10px;
40
+ margin-top: auto; /* 항상 하단에 위치하도록 */
41
+ }
42
+
43
+ .input-area input {
44
+ flex-grow: 1;
45
+ padding: 10px;
46
+ border: 1px solid #ccc;
47
+ border-radius: 5px;
48
+ }
49
+
50
+ .input-area button {
51
+ padding: 10px 15px;
52
+ background-color: #007bff;
53
+ color: white;
54
+ border: none;
55
+ border-radius: 5px;
56
+ cursor: pointer;
57
+ }
58
+
59
+ .input-area button:hover {
60
+ background-color: #0056b3;
61
+ }
@@ -0,0 +1,125 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import './App.css';
3
+ import IO from 'iosignal/io.js';
4
+
5
+ const url = 'ws://localhost:7777';
6
+ const channel_tag = 'channel#topic';
7
+
8
+ function App() {
9
+ const [messages, setMessages] = useState([]);
10
+ const [input, setInput] = useState('Hello, World!');
11
+ const [ioState, setIoState] = useState(null); // ioState is updated by io.on('change')
12
+ const [cid, setCid] = useState(null); // cid is updated by io.on('ready')
13
+ const [counts, setCounts] = useState({ instances: 0, websockets: 0 }); // State for IO counts
14
+ const ioRef = useRef(null); // Manage io instance with useRef
15
+ const messagesEndRef = useRef(null);
16
+
17
+ const scrollToBottom = () => {
18
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
19
+ };
20
+
21
+ useEffect(() => {
22
+ scrollToBottom();
23
+ }, [messages]);
24
+
25
+ useEffect(() => {
26
+ console.log(IO.version)
27
+ ioRef.current = new IO(url); // Create io instance with URL
28
+ setCounts({ instances: IO.instanceCount, websockets: IO.webSocketCount }); // Set initial counts
29
+
30
+ if (!ioRef.current) {
31
+ console.error('IO instance is not initialized.');
32
+ return;
33
+ }
34
+
35
+ const handleReady = () => {
36
+ console.log('ready cid:', ioRef.current.cid);
37
+ setCid(ioRef.current.cid); // Update cid state
38
+ ioRef.current.subscribe(channel_tag); // Subscribe to channel
39
+ };
40
+
41
+ const handleChange = (state) => {
42
+ setIoState(state);
43
+ setCounts({ instances: IO.instanceCount, websockets: IO.webSocketCount }); // Update counts on state change
44
+ };
45
+
46
+ const handleChannelMessage = (msgObj, tag) => {
47
+ console.log('Received message in App:', msgObj, tag);
48
+ if( typeof msgObj === 'string' ) {
49
+ msgObj = { text: msgObj, cid: 'cli unknown' }; // Convert string to object if necessary
50
+ }
51
+ setMessages((prevMessages) => [...prevMessages, `${msgObj.cid} : ${msgObj.text}`]); // Update messages state]);
52
+ };
53
+
54
+ const handleError = (error) => {
55
+ console.error('IO Error in App:', error);
56
+ setIoState(`Error: ${error.message}`);
57
+ };
58
+
59
+ ioRef.current.on('ready', handleReady);
60
+ ioRef.current.on('change', handleChange);
61
+ ioRef.current.on(channel_tag, handleChannelMessage); // Add channel message listener
62
+ ioRef.current.on('error', handleError);
63
+
64
+
65
+ // Cleanup function: remove event listeners when component unmounts
66
+ return () => {
67
+ // ioRef.current.off('ready', handleReady);
68
+ // ioRef.current.off('change', handleChange);
69
+ // ioRef.current.off(channel_tag, handleChannelMessage);
70
+ // ioRef.current.off('error', handleError);
71
+
72
+ // destroy() is called to clean up the io instance, all listeners are removed automatically
73
+ ioRef.current.destroy(); // Clean up the io instance
74
+ console.log('IO instance destroyed.');
75
+ ioRef.current = null; // Release the io instance reference
76
+ };
77
+ }, []); // Empty dependency array ensures this runs only once on mount
78
+
79
+ const sendMessage = () => {
80
+ if (input.trim()) {
81
+ console.log('Sending message:', input);
82
+ const msgObj = { text: input, cid: ioRef.current.cid };
83
+ console.log('Sending message object:', msgObj);
84
+ ioRef.current.signal(channel_tag, msgObj); // Send message to channel
85
+ setInput('date' + Date.now());
86
+ }
87
+ };
88
+
89
+ const ioStateStyle = {
90
+ color: ioState === 'ready' ? 'green' : 'red',
91
+ fontWeight: 'bold',
92
+ };
93
+
94
+ return (
95
+ <div className="App">
96
+ <h1>React Chat Example</h1>
97
+ <div>URL: {url}</div>
98
+ <div>Channel: {channel_tag}</div>
99
+ <div>IO State: <span style={ioStateStyle}>{ioState}</span></div>
100
+ <div>Client ID: {cid}</div>
101
+ <div>IO Instances: {counts.instances}</div>
102
+ <div>WebSockets Created: {counts.websockets}</div>
103
+ <div className="messages">
104
+ {messages.map((msg, index) => (
105
+ <div key={index}>{msg}</div>
106
+ ))}
107
+ <div ref={messagesEndRef} />
108
+ </div>
109
+ <div className="input-area">
110
+ <input
111
+ type="text"
112
+ value={input}
113
+ onChange={(e) => setInput(e.target.value)}
114
+ onKeyUp={(e) => e.key === 'Enter' && sendMessage()}
115
+ disabled={ioState !== 'ready'}
116
+ />
117
+ <button onClick={sendMessage} disabled={ioState !== 'ready'}>
118
+ Send
119
+ </button>
120
+ </div>
121
+ </div>
122
+ );
123
+ }
124
+
125
+ export default App;
@@ -0,0 +1,13 @@
1
+ body {
2
+ margin: 0;
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5
+ sans-serif;
6
+ -webkit-font-smoothing: antialiased;
7
+ -moz-osx-font-smoothing: grayscale;
8
+ }
9
+
10
+ code {
11
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12
+ monospace;
13
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.jsx'
4
+ import './index.css'
5
+
6
+ const strictModeEnabled = true; // true/false 바꾸기만 하면 됨
7
+
8
+
9
+ ReactDOM.createRoot(document.getElementById('root')).render(
10
+ strictModeEnabled
11
+ ? <React.StrictMode><App /></React.StrictMode>
12
+ : <App />
13
+ )
@@ -0,0 +1,12 @@
1
+ import IO from 'iosignal/io.js'
2
+
3
+
4
+ const url = 'ws://localhost:7777';
5
+ const io = new IO(url);
6
+ const channel = 'chat';
7
+
8
+
9
+ console.log('IO instance created:', io);
10
+
11
+ export { io, channel};
12
+
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })
@@ -0,0 +1,12 @@
1
+ import { Server } from "iosignal"
2
+
3
+ const server = new Server(
4
+ {
5
+ port: 7777,
6
+ congPort: 8888,
7
+ showMetric: 2,
8
+ showMessage: 'message' // show signal message
9
+ })
10
+
11
+
12
+ console.log('Signaling server started. Listening on port 7777 and congPort 8888');
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "iosignal-example-server",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node index.js"
8
+ },
9
+ "dependencies": {
10
+ "iosignal": "file:../.."
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,26 +1,28 @@
1
1
  {
2
2
  "name": "iosignal",
3
- "version": "2.2.1",
3
+ "version": "3.0.1",
4
4
  "description": "iosignal",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
8
- "import": "./dist/iosignal.mjs",
9
- "require": "./dist/iosignal.cjs"
8
+ "import": "./dist/iosignal.js",
9
+ "require": null
10
10
  },
11
11
  "./io": "./dist/io.js",
12
12
  "./io.js": "./dist/io.js",
13
13
  "./browser": "./dist/io.js"
14
14
  },
15
+ "types": "./dist/types/src/client/browser/IOWebSocket.d.ts",
15
16
  "browser": "./dist/io.min.js",
16
17
  "jsdelivr": "./dist/io.min.js",
17
18
  "unpkg": "./dist/io.min.js",
18
19
  "scripts": {
19
- "build": "rollup -c",
20
+ "build": "rollup -c && npm run build:types",
21
+ "build:types": "tsc --project tsconfig.json",
20
22
  "start": "node ./test-browser/server.js"
21
23
  },
22
24
  "engines": {
23
- "node": ">=19.0.0"
25
+ "node": ">=18.0.0"
24
26
  },
25
27
  "author": {
26
28
  "name": "Taeo Lee",
@@ -31,14 +33,18 @@
31
33
  "@rollup/plugin-commonjs": "^28.0.0",
32
34
  "@rollup/plugin-json": "^6.1.0",
33
35
  "@rollup/plugin-node-resolve": "^15.3.0",
36
+ "@rollup/plugin-replace": "^6.0.1",
34
37
  "@rollup/plugin-terser": "^0.4.4",
35
- "rollup": "^4.36.0"
38
+ "@types/node": "^24.1.0",
39
+ "rollup": "^4.36.0",
40
+ "rollup-plugin-dts": "^6.2.1",
41
+ "typescript": "^5.8.3"
36
42
  },
37
43
  "dependencies": {
38
- "boho": "^1.0.3",
44
+ "boho": "^1.1.1",
39
45
  "eventemitter3": "^5.0.1",
40
- "meta-buffer-pack": "^2.0.6",
41
- "ws": "^8.18.1"
46
+ "meta-buffer-pack": "^2.1.0",
47
+ "ws": "^8.18.3"
42
48
  },
43
49
  "optionalDependencies": {
44
50
  "bufferutil": "^4.0.9",
@@ -52,9 +58,15 @@
52
58
  "keywords": [
53
59
  "websocket",
54
60
  "pubsub",
55
- "arduino",
61
+ "messaging",
62
+ "real-time",
63
+ "signaling",
64
+ "signal",
65
+ "client-server",
66
+ "nodejs",
67
+ "browser",
56
68
  "iot",
57
- "auth",
58
- "signal"
69
+ "arduino",
70
+ "auth"
59
71
  ]
60
72
  }
package/rollup.config.js CHANGED
@@ -2,10 +2,11 @@ import resolve from '@rollup/plugin-node-resolve'
2
2
  import commonjs from '@rollup/plugin-commonjs'
3
3
  import json from '@rollup/plugin-json'
4
4
  import terser from '@rollup/plugin-terser'
5
+ import dts from 'rollup-plugin-dts'
5
6
 
6
7
  export default [
7
- {
8
- input: './src/client/IOWebSocket.js',
8
+ {
9
+ input: './src/client/browser/IOWebSocket.js',
9
10
  output: [
10
11
  {
11
12
  file: './dist/io.js',
@@ -23,16 +24,21 @@ export default [
23
24
  resolve({
24
25
  preferBuiltins: false
25
26
  }),
26
- json(),
27
- commonjs()
28
- ,terser()
27
+ commonjs(),
28
+ json(),
29
+ terser()
29
30
  ]
30
31
  },
31
32
  {
33
+ input: './src/client/browser/IOWebSocket.js',
34
+ output: { file: './dist/io.d.ts', format: 'es' },
35
+ plugins: [dts()]
36
+ }
37
+
38
+ ,{
32
39
  input: './index.js',
33
40
  output: [
34
- { file: './dist/iosignal.cjs', format: 'cjs' },
35
- { file: './dist/iosignal.mjs', format: 'es' }
41
+ { file: './dist/iosignal.js', format: 'es' }
36
42
  ],
37
43
  plugins: [
38
44
  resolve({
@@ -40,7 +46,6 @@ export default [
40
46
  }),
41
47
  json(),
42
48
  commonjs()
43
- ,terser()
44
49
  ]
45
50
  }
46
51
 
@@ -11,7 +11,7 @@ export class Auth_File extends BohoAuth {
11
11
  this.path = path.resolve(_path)
12
12
  console.log('auth from file path:', this.path)
13
13
  let ext = pathObj.ext;
14
- if (ext.toLowerCase() == '.js' || ext.toLowerCase() == '.mjs') {
14
+ if (ext.toLowerCase() == '.js') {
15
15
  // Read auth data from file
16
16
  console.log("#JS path:", this.path);
17
17
  this.loadAuthInfoFile_JS(this.path)
@@ -1,4 +1,5 @@
1
1
  import { IOCore } from "./IOCore.js"
2
+ import { STATES } from "../common/constants.js";
2
3
  import { pack, CongRx } from './CongPacket.js'
3
4
  import net from 'net'
4
5
 
@@ -9,20 +10,30 @@ export class IOCongSocket extends IOCore {
9
10
  if (url) this.open();
10
11
  }
11
12
 
13
+ /**
14
+ * Closes TCP socket and cleans resources.
15
+ */
12
16
  close() {
13
- this.socket?.end();
14
- this.socket = null;
17
+ if (this.socket) {
18
+ if (this.congRx) {
19
+ this.socket.unpipe(this.congRx);
20
+ this.congRx.destroy(); // Use destroy() for complete stream cleanup
21
+ this.congRx = null;
22
+ }
23
+ this.socket.removeAllListeners();
24
+ if (!this.socket.destroyed) {
25
+ this.socket.destroy(); // destroy() is sufficient for forceful closing
26
+ }
27
+ }
28
+ super.close();
15
29
  }
16
30
 
17
- stop() {
18
- this.close()
19
- clearInterval(this.connectionCheckerIntervalID);
20
- this.connectionCheckerIntervalID = null
21
- }
31
+
22
32
 
23
33
  keepAlive() {
24
- let state = this.socket?.readyState;
25
- if (!this.socket || !(state === 'open' || state === 'opening')) {
34
+ if (!this.autoReconnect) return;
35
+ // Reconnect only if the socket is fully destroyed and the state is closed.
36
+ if ((!this.socket || this.socket.destroyed) && this.state === STATES.CLOSED) {
26
37
  this.open();
27
38
  }
28
39
  }