@live-change/peer-connection-service 0.2.28
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/definition.js +11 -0
- package/experiments/turnAuth.js +35 -0
- package/index.js +9 -0
- package/message.js +158 -0
- package/package.json +14 -0
- package/peer.js +115 -0
- package/peerState.js +85 -0
- package/turn.js +89 -0
package/definition.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const App = require("@live-change/framework")
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
const relationsPlugin = require('@live-change/relations-plugin')
|
|
5
|
+
|
|
6
|
+
const definition = app.createServiceDefinition({
|
|
7
|
+
name: "peerConnection",
|
|
8
|
+
use: [ relationsPlugin ]
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
module.exports = definition
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const crypto = require("crypto")
|
|
2
|
+
|
|
3
|
+
const config = {
|
|
4
|
+
TURN_URLS: 'turn:turn1.xaos.ninja:4433',//;turn:turn2.xaos.ninja:4433',
|
|
5
|
+
TURN_SECRET: 'c1e3705c2'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const urls = config.TURN_URLS
|
|
9
|
+
const secret = config.TURN_SECRET
|
|
10
|
+
|
|
11
|
+
function randomHexString(size) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
crypto.randomBytes(size / 2, function(err, res) {
|
|
14
|
+
if (err) return reject(err)
|
|
15
|
+
resolve(res.toString('hex'))
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
async function genTurnAuth() {
|
|
20
|
+
const expire = (Date.now() / 1000 + 1 * 60 * 60) | 0 // 1 hour
|
|
21
|
+
const username = await randomHexString(10)
|
|
22
|
+
const rusername = expire + ':' + username
|
|
23
|
+
const password = crypto
|
|
24
|
+
.createHmac('sha1', secret)
|
|
25
|
+
.update(rusername)
|
|
26
|
+
.digest('base64')
|
|
27
|
+
/// TODO: select nearest servers by geoip
|
|
28
|
+
console.dir({
|
|
29
|
+
urls,
|
|
30
|
+
username: rusername,
|
|
31
|
+
credential: password
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
genTurnAuth()
|
package/index.js
ADDED
package/message.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const Peer = require('./peer.js')
|
|
2
|
+
|
|
3
|
+
const messageFields = {
|
|
4
|
+
to: {
|
|
5
|
+
type: Peer
|
|
6
|
+
},
|
|
7
|
+
from: {
|
|
8
|
+
type: Peer
|
|
9
|
+
},
|
|
10
|
+
type: {
|
|
11
|
+
type: String
|
|
12
|
+
},
|
|
13
|
+
data: {
|
|
14
|
+
type: Object
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Message = definition.model({
|
|
19
|
+
name: "Message",
|
|
20
|
+
properties: {
|
|
21
|
+
timestamp: {
|
|
22
|
+
type: Date,
|
|
23
|
+
validation: ['nonEmpty']
|
|
24
|
+
},
|
|
25
|
+
...messageFields
|
|
26
|
+
},
|
|
27
|
+
indexes: {
|
|
28
|
+
/*byToTimestamp: {
|
|
29
|
+
property: ['to', 'timestamp']
|
|
30
|
+
},*/
|
|
31
|
+
},
|
|
32
|
+
crud: {
|
|
33
|
+
deleteTrigger: true,
|
|
34
|
+
writeOptions: {
|
|
35
|
+
access: (params, {client, service}) => {
|
|
36
|
+
return client.roles.includes('admin')
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
definition.view({
|
|
43
|
+
name: "messages",
|
|
44
|
+
properties: {
|
|
45
|
+
peer: {
|
|
46
|
+
type: Peer
|
|
47
|
+
},
|
|
48
|
+
gt: {
|
|
49
|
+
type: String,
|
|
50
|
+
},
|
|
51
|
+
lt: {
|
|
52
|
+
type: String,
|
|
53
|
+
},
|
|
54
|
+
gte: {
|
|
55
|
+
type: String,
|
|
56
|
+
},
|
|
57
|
+
lte: {
|
|
58
|
+
type: String,
|
|
59
|
+
},
|
|
60
|
+
limit: {
|
|
61
|
+
type: Number
|
|
62
|
+
},
|
|
63
|
+
reverse: {
|
|
64
|
+
type: Boolean
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
returns: {
|
|
68
|
+
type: Array,
|
|
69
|
+
of: {
|
|
70
|
+
type: Message
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
access: async({ peer }, { client, service, visibilityTest }) => {
|
|
74
|
+
if(visibilityTest) return true
|
|
75
|
+
if(!peer) throw new Error("peer parameter is required")
|
|
76
|
+
const publicSessionInfo = await getPublicInfo(client.sessionId)
|
|
77
|
+
//console.log('MESSAGES ACCESS', peer.split('_'), "[2] == ", publicSessionInfo.id)
|
|
78
|
+
return peer.split('_')[2] == publicSessionInfo.id
|
|
79
|
+
},
|
|
80
|
+
async daoPath({ peer, gt, lt, gte, lte, limit, reverse }, { client, service }, method) {
|
|
81
|
+
const channelId = peer
|
|
82
|
+
if(!Number.isSafeInteger(limit)) limit = 100
|
|
83
|
+
const range = {
|
|
84
|
+
gt: gt ? `${channelId}_${gt.split('_').pop()}` : (gte ? undefined : `${channelId}_`),
|
|
85
|
+
lt: lt ? `${channelId}_${lt.split('_').pop()}` : undefined,
|
|
86
|
+
gte: gte ? `${channelId}_${gte.split('_').pop()}` : undefined,
|
|
87
|
+
lte: lte ? `${channelId}_${lte.split('_').pop()}` : ( lt ? undefined : `${channelId}_\xFF\xFF\xFF\xFF`),
|
|
88
|
+
limit,
|
|
89
|
+
reverse
|
|
90
|
+
}
|
|
91
|
+
const messages = await Message.rangeGet(range)
|
|
92
|
+
console.log("MESSAGES RANGE", JSON.stringify({ peer, gt, lt, gte, lte, limit, reverse }) ,
|
|
93
|
+
"\n TO", JSON.stringify(range),
|
|
94
|
+
"\n RESULTS", messages.length, messages.map(m => m.id))
|
|
95
|
+
|
|
96
|
+
/* console.log("MESSAGES RANGE", range, "RESULTS", messages.length)*/
|
|
97
|
+
return Message.rangePath(range)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
let lastMessageTime = new Map()
|
|
102
|
+
|
|
103
|
+
async function postMessage(props, { client, service }, emit, conversation) {
|
|
104
|
+
console.log("POST MESSAGE", props)
|
|
105
|
+
const channelId = props.to
|
|
106
|
+
let lastTime = lastMessageTime.get(channelId)
|
|
107
|
+
const now = new Date()
|
|
108
|
+
if(lastTime && now.toISOString() <= lastTime.toISOString()) {
|
|
109
|
+
lastTime.setTime(lastTime.getTime() + 1)
|
|
110
|
+
} else {
|
|
111
|
+
lastTime = now
|
|
112
|
+
}
|
|
113
|
+
if(lastTime.getTime() > now.getTime() + 100) { /// Too many messages per second, drop message
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
lastMessageTime.set(channelId, lastTime)
|
|
117
|
+
const message = `${channelId}_${lastTime.toISOString()}`
|
|
118
|
+
let data = {}
|
|
119
|
+
for(const key in messageFields) {
|
|
120
|
+
data[key] = props[key]
|
|
121
|
+
}
|
|
122
|
+
data.timestamp = now
|
|
123
|
+
if(!data.user) {
|
|
124
|
+
const publicInfo = await getPublicInfo(client.sessionId)
|
|
125
|
+
data.session = publicInfo.id
|
|
126
|
+
}
|
|
127
|
+
emit({
|
|
128
|
+
type: "MessageCreated",
|
|
129
|
+
message,
|
|
130
|
+
data
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
definition.action({
|
|
135
|
+
name: "postMessage",
|
|
136
|
+
properties: {
|
|
137
|
+
...messageFields
|
|
138
|
+
},
|
|
139
|
+
//queuedBy: (command) => `${command.toType}_${command.toId})`,
|
|
140
|
+
access: async ({ from, to }, context) => {
|
|
141
|
+
const { client, service, visibilityTest } = context
|
|
142
|
+
if(visibilityTest) return true
|
|
143
|
+
const [fromType, fromId, fromSession] = from.split('_')
|
|
144
|
+
const [toType, toId, toSession] = to.split('_')
|
|
145
|
+
if(toType != fromType) return false
|
|
146
|
+
if(toId != fromId) return false
|
|
147
|
+
const publicSessionInfo = await getPublicInfo(client.sessionId)
|
|
148
|
+
if(publicSessionInfo.id != fromSession) return false
|
|
149
|
+
return toType.split('.')[0] == 'priv'
|
|
150
|
+
? checkPrivAccess(toId, context)
|
|
151
|
+
: checkIfRole(toType.split('.')[0], toId, ['speaker', 'vip', 'moderator', 'owner'], context)
|
|
152
|
+
},
|
|
153
|
+
async execute(props, { client, service }, emit) {
|
|
154
|
+
const result = await postMessage(props, { client, service }, emit)
|
|
155
|
+
console.log("MESSAGE POSTED!")
|
|
156
|
+
return result
|
|
157
|
+
}
|
|
158
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@live-change/peer-connection-service",
|
|
3
|
+
"version": "0.2.28",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node index.js",
|
|
8
|
+
"setup": "INSTALL=true node index.js",
|
|
9
|
+
"test": "NODE_ENV=test blue-tape tests/*"
|
|
10
|
+
},
|
|
11
|
+
"author": "Michał Łaszczewski <michal@emikse.com>",
|
|
12
|
+
"license": "BSD-3-Clause",
|
|
13
|
+
"gitHead": "34309fef58572e1a8794b52438430dd421c57d65"
|
|
14
|
+
}
|
package/peer.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const definition = require('./definition.js')
|
|
2
|
+
|
|
3
|
+
const { clientHasAccessRole } = require("../access-control-service/access.js")(definition)
|
|
4
|
+
|
|
5
|
+
const Peer = definition.model({
|
|
6
|
+
name: "Peer",
|
|
7
|
+
itemOfAny: {
|
|
8
|
+
to: ['channel', 'session']
|
|
9
|
+
},
|
|
10
|
+
properties: {
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
definition.view({
|
|
15
|
+
name: "peers",
|
|
16
|
+
properties: {
|
|
17
|
+
channelType: {
|
|
18
|
+
type: String,
|
|
19
|
+
validation: ['nonEmpty']
|
|
20
|
+
},
|
|
21
|
+
channel: {
|
|
22
|
+
type: String,
|
|
23
|
+
validation: ['nonEmpty']
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
returns: {
|
|
27
|
+
type: Array,
|
|
28
|
+
of: {
|
|
29
|
+
type: Peer
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
access: (params, { client, visibilityTest }) => {
|
|
33
|
+
if(visibilityTest) return true
|
|
34
|
+
const { channelType, channel } = params
|
|
35
|
+
//console.log("CHECK PEERS ACCESS", params, client, visibilityTest)
|
|
36
|
+
return clientHasAccessRole(client, { objectType: channelType, object: channel },
|
|
37
|
+
['reader', 'speaker', 'vip', 'moderator', 'owner'])
|
|
38
|
+
},
|
|
39
|
+
async daoPath({ channelType, channel }, { client, service }, method) {
|
|
40
|
+
return Peer.indexRangePath('byChannel', [ channelType, channel.split('.')[0] ])
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
definition.event({
|
|
45
|
+
name: "peerOnline",
|
|
46
|
+
async execute({ channelType, channel, sessionType, session, instance }) {
|
|
47
|
+
const peer = channelType + ':' + channel + ':' + sessionType + ':' + session + ':' + instance
|
|
48
|
+
await Peer.create({ id: peer, channelType, channel, instance, sessionType, session })
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
definition.event({
|
|
53
|
+
name: "peerOffline",
|
|
54
|
+
async execute({ channelType, channel, sessionType, session, instance }) {
|
|
55
|
+
const peer = channelType + ':' + channel + ':' + sessionType + ':' + session + ':' + instance
|
|
56
|
+
Peer.delete(peer)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
definition.event({
|
|
61
|
+
name: "allOffline",
|
|
62
|
+
async execute() {
|
|
63
|
+
await app.dao.request(['database', 'query', app.databaseName, `(${
|
|
64
|
+
async (input, output, { table }) => {
|
|
65
|
+
await input.table(table).range({}).onChange(async obj => {
|
|
66
|
+
output.table(table).delete(obj.id)
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
})`, { table: Peer.tableName }])
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
definition.trigger({
|
|
74
|
+
name: "sessionPeerOnline",
|
|
75
|
+
properties: {
|
|
76
|
+
},
|
|
77
|
+
async execute({ session, peer }, context, emit) {
|
|
78
|
+
console.log("PEER ONLINE PARAMS", { session, peer })
|
|
79
|
+
const [ channelType, channel, sessionType, peerSession, instance ] = peer.split(':')
|
|
80
|
+
if(sessionType != 'session_Session') throw new Error('wrongSessionType')
|
|
81
|
+
if(peerSession != session) throw new Error('wrongSession')
|
|
82
|
+
/// TODO: check channel access
|
|
83
|
+
emit({
|
|
84
|
+
type: 'peerOnline',
|
|
85
|
+
channelType, channel, sessionType, session, instance
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
definition.trigger({
|
|
91
|
+
name: "sessionPeerOffline",
|
|
92
|
+
properties: {
|
|
93
|
+
},
|
|
94
|
+
async execute({ session, peer }, context, emit) {
|
|
95
|
+
console.log("PEER OFFLINE PARAMS", { session, peer })
|
|
96
|
+
const [ channelType, channel, sessionType, peerSession, instance ] = peer.split(':')
|
|
97
|
+
if(sessionType != 'session_Session') throw new Error('wrongSessionType')
|
|
98
|
+
if(peerSession != session) throw new Error('wrongSession')
|
|
99
|
+
emit({
|
|
100
|
+
type: 'peerOffline',
|
|
101
|
+
channelType, channel, sessionType, session, instance
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
definition.trigger({
|
|
107
|
+
name: "allOffline",
|
|
108
|
+
properties: {
|
|
109
|
+
},
|
|
110
|
+
async execute({ }, context, emit) {
|
|
111
|
+
emit({
|
|
112
|
+
type: "allOffline"
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
})
|
package/peerState.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const definition = require('./definition.js')
|
|
2
|
+
|
|
3
|
+
const { Peer } = require('./peer.js')
|
|
4
|
+
|
|
5
|
+
const peerStateFields = {
|
|
6
|
+
audioState: {
|
|
7
|
+
type: String
|
|
8
|
+
},
|
|
9
|
+
videoState: {
|
|
10
|
+
type: String
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const PeerState = definition.model({
|
|
15
|
+
name: "PeerState",
|
|
16
|
+
propertyOf: {
|
|
17
|
+
what: Peer
|
|
18
|
+
},
|
|
19
|
+
properties: {
|
|
20
|
+
...peerStateFields
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
definition.event({
|
|
25
|
+
name: "peerStateSet",
|
|
26
|
+
async execute({ peer, data }) {
|
|
27
|
+
await PeerState.create({ ...data, id: peer })
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
definition.view({
|
|
32
|
+
name: "peerState",
|
|
33
|
+
properties: {
|
|
34
|
+
peer: {
|
|
35
|
+
type: Peer
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
returns: {
|
|
39
|
+
type: PeerState
|
|
40
|
+
},
|
|
41
|
+
access: async ({ peer }, context) => {
|
|
42
|
+
const { client, service, visibilityTest } = context
|
|
43
|
+
if(visibilityTest) return true
|
|
44
|
+
const [toType, toId, toSession] = peer.split('_')
|
|
45
|
+
return toType.split('.')[0] == 'priv'
|
|
46
|
+
? checkPrivAccess(toId, context)
|
|
47
|
+
: checkIfRole(toType.split('.')[0], toId, ['speaker', 'vip', 'moderator', 'owner'], context)
|
|
48
|
+
},
|
|
49
|
+
async daoPath({ peer }, { client, service }, method) {
|
|
50
|
+
return PeerState.path(peer)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
definition.action({
|
|
55
|
+
name: "setPeerState",
|
|
56
|
+
properties: {
|
|
57
|
+
peer: {
|
|
58
|
+
type: Peer
|
|
59
|
+
},
|
|
60
|
+
...peerStateFields
|
|
61
|
+
},
|
|
62
|
+
//queuedBy: (command) => `${command.toType}_${command.toId})`,
|
|
63
|
+
access: async ({ peer }, context) => {
|
|
64
|
+
const { client, service, visibilityTest } = context
|
|
65
|
+
if(visibilityTest) return true
|
|
66
|
+
const [toType, toId, toSession] = peer.split('_')
|
|
67
|
+
const publicSessionInfo = await getPublicInfo(client.sessionId)
|
|
68
|
+
if(publicSessionInfo.id != toSession) return false
|
|
69
|
+
return toType.split('.')[0] == 'priv'
|
|
70
|
+
? checkPrivAccess(toId, context)
|
|
71
|
+
: checkIfRole(toType.split('.')[0], toId, ['speaker', 'vip', 'moderator', 'owner'], context)
|
|
72
|
+
},
|
|
73
|
+
async execute(props, { client, service }, emit) {
|
|
74
|
+
let data = { }
|
|
75
|
+
for(const key in peerStateFields) {
|
|
76
|
+
data[key] = props[key]
|
|
77
|
+
}
|
|
78
|
+
emit({
|
|
79
|
+
type: 'peerStateSet',
|
|
80
|
+
peer: props.peer,
|
|
81
|
+
data
|
|
82
|
+
})
|
|
83
|
+
return 'ok'
|
|
84
|
+
}
|
|
85
|
+
})
|
package/turn.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const crypto = require('crypto')
|
|
2
|
+
const ReactiveDao = require('@live-change/dao')
|
|
3
|
+
const definition = require('./definition.js')
|
|
4
|
+
const config = definition.config
|
|
5
|
+
|
|
6
|
+
const urls = config?.turn?.urls || process.env.TURN_URLS?.split(';')
|
|
7
|
+
const secret = config?.turn?.secret || process.env.TURN_SECRET
|
|
8
|
+
const turnExpireTime = config?.turn?.expire || (+process.env.TURN_EXPIRE) || (60 * 60) // 1 hour for default
|
|
9
|
+
|
|
10
|
+
const { clientHasAccessRole } = require("../access-control-service/access.js")(definition)
|
|
11
|
+
|
|
12
|
+
const { Peer } = require('./peer.js')
|
|
13
|
+
|
|
14
|
+
function randomHexString(size) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
crypto.randomBytes(size / 2, function(err, res) {
|
|
17
|
+
if (err) return reject(err)
|
|
18
|
+
resolve(res.toString('hex'))
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function createTurnConfiguration({ client }) {
|
|
24
|
+
const expire = Date.now() / 1000 + turnExpireTime | 0
|
|
25
|
+
const username = await randomHexString(10)
|
|
26
|
+
const rusername = expire + ':' + username
|
|
27
|
+
const password = crypto
|
|
28
|
+
.createHmac('sha1', secret)
|
|
29
|
+
.update(rusername)
|
|
30
|
+
.digest('base64')
|
|
31
|
+
/// TODO: select nearest servers by geoip
|
|
32
|
+
return {
|
|
33
|
+
urls,
|
|
34
|
+
credentialType: 'password',
|
|
35
|
+
username: rusername,
|
|
36
|
+
credential: password,
|
|
37
|
+
clientIp: client.ip
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function releaseTurnConfiguration() {
|
|
42
|
+
/// not used in static shared secret configuration
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
definition.view({
|
|
46
|
+
name: "turnConfiguration",
|
|
47
|
+
properties: {
|
|
48
|
+
peer: {
|
|
49
|
+
type: Peer
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
access: async ({ peer }, { client, service, visibilityTest }) => {
|
|
53
|
+
if(visibilityTest) return true
|
|
54
|
+
const [ channelType, channel, sessionType, session, instance ] = peer.split(':')
|
|
55
|
+
if(sessionType != 'session_Session') throw new Error('wrongSessionType')
|
|
56
|
+
if(session != client.session) throw new Error('wrongSession')
|
|
57
|
+
return clientHasAccessRole(client, { objectType: channelType.split('.')[0], object: channel },
|
|
58
|
+
['speaker', 'vip', 'moderator', 'owner'])
|
|
59
|
+
},
|
|
60
|
+
observable({ peer }, context) {
|
|
61
|
+
const observable = new ReactiveDao.ObservableValue()
|
|
62
|
+
let turnWorking = true
|
|
63
|
+
const refreshTurn = async () => {
|
|
64
|
+
if(observable.isDisposed()) {
|
|
65
|
+
turnWorking = false
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
observable.set(await createTurnConfiguration(context))
|
|
70
|
+
} catch(error) {
|
|
71
|
+
observable.error(error)
|
|
72
|
+
}
|
|
73
|
+
const refreshDelay = turnExpireTime * 1000 / 2
|
|
74
|
+
setTimeout(refreshTurn, refreshDelay)
|
|
75
|
+
}
|
|
76
|
+
refreshTurn() // must be async!
|
|
77
|
+
const oldRespawn = observable.respawn
|
|
78
|
+
observable.respawn = () => {
|
|
79
|
+
oldRespawn.call(observable)
|
|
80
|
+
if(!turnWorking) refreshTurn() // must be async!
|
|
81
|
+
}
|
|
82
|
+
return observable
|
|
83
|
+
},
|
|
84
|
+
async get({ peer }, context) {
|
|
85
|
+
return await createTurnConfiguration(context)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
module.exports = { createTurnConfiguration, releaseTurnConfiguration, turnExpireTime }
|