signalingserver.js 0.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.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/demo.html +38 -0
- package/package.json +29 -0
- package/signalingserver.js +157 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nuzulul Zulkarnain
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# signalingserver.js
|
|
2
|
+
Alternative signaling server that works in browser, no server required.
|
|
3
|
+
|
|
4
|
+
[>DEMO<](https://nuzulul.github.io/signalingserver.js/demo.html)
|
|
5
|
+
|
|
6
|
+
## Benefit
|
|
7
|
+
|
|
8
|
+
* ✅ 0 Dependencies
|
|
9
|
+
* ✅ No server required
|
|
10
|
+
* ✅ Simple API
|
|
11
|
+
|
|
12
|
+
## How does it works?
|
|
13
|
+
|
|
14
|
+
It use free public WebTorrent trackers as transport.
|
|
15
|
+
|
|
16
|
+
## Ideas
|
|
17
|
+
|
|
18
|
+
* WebRTC signaling server
|
|
19
|
+
* Peer discovery
|
|
20
|
+
* Mesh network
|
|
21
|
+
* Chat
|
|
22
|
+
* Multiplayer
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
npm install signalingserver.js
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
CDN
|
|
31
|
+
|
|
32
|
+
* [https://cdn.jsdelivr.net/npm/signalingserver.js/+esm](https://cdn.jsdelivr.net/npm/signalingserver.js/+esm)
|
|
33
|
+
* [https://unpkg.com/signalingserver.js](https://unpkg.com/signalingserver.js)
|
|
34
|
+
* [https://esm.sh/signalingserver.js](https://esm.sh/signalingserver.js)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
import {createSignalingServer} from 'signalingserver.js';
|
|
41
|
+
|
|
42
|
+
const config = {
|
|
43
|
+
appid : 'myApp'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//create new node
|
|
47
|
+
const node = createSignalingServer(config);
|
|
48
|
+
|
|
49
|
+
//signal handler
|
|
50
|
+
node.data((signal)=>{
|
|
51
|
+
console.log(`receive signal : ${signal}`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
//broadcast signal
|
|
55
|
+
const signal = 'test'; //example
|
|
56
|
+
node.send(signal);
|
|
57
|
+
console.log(`send signal : ${signal}`);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API
|
|
61
|
+
|
|
62
|
+
### createSignalingServer(config)
|
|
63
|
+
|
|
64
|
+
Create new signaling server node
|
|
65
|
+
|
|
66
|
+
Config : Parameter object
|
|
67
|
+
|
|
68
|
+
* appid = (string) Custom application name as identifier
|
|
69
|
+
* tracker = (Array) Custom WebTorrent trackers list
|
|
70
|
+
|
|
71
|
+
### send
|
|
72
|
+
|
|
73
|
+
Broadcast signal to all node
|
|
74
|
+
|
|
75
|
+
### data
|
|
76
|
+
|
|
77
|
+
On receive signal handler
|
|
78
|
+
|
|
79
|
+
## Recomendation
|
|
80
|
+
|
|
81
|
+
Encrypt the signal before broadcast it to prevent mitm.
|
|
82
|
+
|
|
83
|
+
## See Also
|
|
84
|
+
|
|
85
|
+
* [webConnect.js](https://github.com/nuzulul/webConnect.js) - Auto WebRTC Mesh P2P Network without signaling server.
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
* [MIT](https://github.com/nuzulul/signaling.js/blob/main/LICENSE) - [Nuzulul Zulkarnain](https://github.com/nuzulul)
|
package/demo.html
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<header>
|
|
3
|
+
<title>signalingserver.js</title>
|
|
4
|
+
</header>
|
|
5
|
+
<body>
|
|
6
|
+
<h3>Demo <a href="https://github.com/nuzulul/signalingserver.js">signalingserver.js</a></h3>
|
|
7
|
+
<ul id="myList" style="overflow:scroll;height:70%;"></ul>
|
|
8
|
+
<script type="module">
|
|
9
|
+
import {createSignalingServer} from './signalingserver.js';
|
|
10
|
+
|
|
11
|
+
const write = (input) => {
|
|
12
|
+
const node = document.createElement("li")
|
|
13
|
+
const textnode = document.createTextNode(input)
|
|
14
|
+
node.appendChild(textnode)
|
|
15
|
+
document.getElementById("myList").appendChild(node)
|
|
16
|
+
var objDiv = document.getElementById("myList");
|
|
17
|
+
objDiv.scrollTop = objDiv.scrollHeight;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const config = {
|
|
21
|
+
appid : 'demo signalingserver.js'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const node = createSignalingServer(config);
|
|
25
|
+
node.data((signal)=>{
|
|
26
|
+
const status = `receive signal : ${signal}`;
|
|
27
|
+
write(status);
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
setInterval(()=>{
|
|
31
|
+
const signal = new Date().getTime(); //example signal
|
|
32
|
+
node.send(signal);
|
|
33
|
+
const status = `send signal : ${signal}`;
|
|
34
|
+
write(status);
|
|
35
|
+
},5000)
|
|
36
|
+
</script>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "signalingserver.js",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Alternative signaling server that works in browser, no server required.",
|
|
5
|
+
"main": "signalingserver.js",
|
|
6
|
+
"module": "signalingserver.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "signalingserver.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/nuzulul/signalingserver.js.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"signaling",
|
|
21
|
+
"signaling server"
|
|
22
|
+
],
|
|
23
|
+
"author": "Nuzulul Zulkarnain",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/nuzulul/signalingserver.js/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/nuzulul/signalingserver.js#readme"
|
|
29
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* signalingserver.js by Nuzulul Zulkarnain
|
|
3
|
+
* https://github.com/nuzulul/signalingserver.js
|
|
4
|
+
**/
|
|
5
|
+
|
|
6
|
+
const defaultTrackers = [
|
|
7
|
+
'wss://tracker.webtorrent.dev',
|
|
8
|
+
'wss://tracker.openwebtorrent.com',
|
|
9
|
+
'wss://tracker.btorrent.xyz',
|
|
10
|
+
'wss://tracker.files.fm:7073/announce'
|
|
11
|
+
];
|
|
12
|
+
const defaultAppId = 'global';
|
|
13
|
+
const appName = 'signaling.js';
|
|
14
|
+
const hashLimit = 20;
|
|
15
|
+
const sockets = {};
|
|
16
|
+
const socketListeners = {};
|
|
17
|
+
const trackerAction = 'announce';
|
|
18
|
+
const intervalMs = 30000;
|
|
19
|
+
const {values} = Object;
|
|
20
|
+
const offerPoolSize = 10;
|
|
21
|
+
const charSet = '0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz';
|
|
22
|
+
const createId = () => new Array(20).fill().map(()=>charSet[Math.floor(Math.random() * charSet.length)]).join('');
|
|
23
|
+
const myid = createId();
|
|
24
|
+
const encodeBytes = txt => new TextEncoder().encode(txt);
|
|
25
|
+
let handledOffers = {}
|
|
26
|
+
|
|
27
|
+
const createSignalingServer = (config={}) => {
|
|
28
|
+
|
|
29
|
+
//use default appid if empty
|
|
30
|
+
if(!config.appid){
|
|
31
|
+
config.appid = defaultAppId;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//use default tracker if empty
|
|
35
|
+
if(!config.tracker){
|
|
36
|
+
config.tracker = defaultTrackers;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//validate tracker length is not empty
|
|
40
|
+
if(!config.tracker.length){
|
|
41
|
+
throw new Error(`Tracker list is empty`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const createInfoHash = crypto.subtle
|
|
45
|
+
.digest('SHA-1', encodeBytes(`${appName}:${config.appid}`))
|
|
46
|
+
.then(buffer =>
|
|
47
|
+
Array.from(new Uint8Array(buffer))
|
|
48
|
+
.map(b => b.toString(36))
|
|
49
|
+
.join('')
|
|
50
|
+
.slice(0,hashLimit)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const send = async (content) => {
|
|
54
|
+
const infoHash = await createInfoHash;
|
|
55
|
+
const offer_id = createId()
|
|
56
|
+
config.tracker.forEach(async url => {
|
|
57
|
+
const socket = makeSocket(url,infoHash);
|
|
58
|
+
if(socket.readyState === WebSocket.OPEN){
|
|
59
|
+
announce(socket,infoHash,content,offer_id);
|
|
60
|
+
}else if(socket.readyState !== WebSocket.CONNECTING){
|
|
61
|
+
announce(await makeSocket(url,infoHash),infoHash,content,offer_id);
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const makeSocket = (url,infoHash) => {
|
|
67
|
+
if(!sockets[url]){
|
|
68
|
+
socketListeners[url] = {[infoHash]: onSocketMessage};
|
|
69
|
+
sockets[url] = new Promise(res => {
|
|
70
|
+
const socket = new WebSocket(url);
|
|
71
|
+
socket.onopen = res.bind(null,socket);
|
|
72
|
+
socket.onmessage = e =>
|
|
73
|
+
values(socketListeners[url]).forEach(f => f(socket,e))
|
|
74
|
+
})
|
|
75
|
+
}else{
|
|
76
|
+
socketListeners[url][infoHash] = onSocketMessage;
|
|
77
|
+
}
|
|
78
|
+
return sockets[url];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const announce = async (socket,infoHash,content,offer_id) =>
|
|
82
|
+
socket.send(
|
|
83
|
+
JSON.stringify({
|
|
84
|
+
action : trackerAction,
|
|
85
|
+
info_hash: infoHash,
|
|
86
|
+
peer_id: myid,
|
|
87
|
+
numwant:offerPoolSize,
|
|
88
|
+
offers:[
|
|
89
|
+
{
|
|
90
|
+
offer:{
|
|
91
|
+
type: 'offer',
|
|
92
|
+
sdp:content
|
|
93
|
+
},
|
|
94
|
+
offer_id
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
})
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const onSocketMessage = async (socket,e) => {
|
|
101
|
+
const infoHash = await createInfoHash;
|
|
102
|
+
let val;
|
|
103
|
+
|
|
104
|
+
try{
|
|
105
|
+
val = JSON.parse(e.data);
|
|
106
|
+
}catch(e){
|
|
107
|
+
console.warn(e);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if(val.info_hash !== infoHash){
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if(val.peer_id && val.peer_id === myid){
|
|
116
|
+
//got self message
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const failure = val['failure reason'];
|
|
121
|
+
if(failure){
|
|
122
|
+
console.warn(failure);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if(handledOffers[val.offer_id]){
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
handledOffers[val.offer_id] = true;
|
|
131
|
+
|
|
132
|
+
if(val.offer){
|
|
133
|
+
contentHandler(val.offer.sdp);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let contentHandler = ()=>{};
|
|
140
|
+
const data = handle => (contentHandler = handle);
|
|
141
|
+
|
|
142
|
+
const auto = async () => {
|
|
143
|
+
const infoHash = await createInfoHash;
|
|
144
|
+
config.tracker.forEach(async url => {
|
|
145
|
+
const socket = makeSocket(url,infoHash);
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
const announceInterval = setInterval(auto, intervalMs);
|
|
149
|
+
auto();
|
|
150
|
+
|
|
151
|
+
return {send,data}
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default createSignalingServer;
|
|
156
|
+
|
|
157
|
+
export {createSignalingServer};
|