mirror of
https://github.com/balzack/databag.git
synced 2025-03-13 00:50:03 +00:00
Merge branch 'cloudrtc'
This commit is contained in:
commit
8a93d123ad
@ -160,6 +160,7 @@ const Strings = [
|
||||
enableVideo: 'Enable Video Queue',
|
||||
enableBinary: 'Enable Binary Files',
|
||||
enableCalls: 'Enable WebRTC Calls',
|
||||
iceService: 'Cloudflare Service',
|
||||
relayUrl: 'Relay URL',
|
||||
relayUsername: 'Relay Username',
|
||||
relayPassword: 'Relay Password',
|
||||
@ -370,6 +371,7 @@ const Strings = [
|
||||
enableVideo: 'Activer les Fichiers Vidéo',
|
||||
enableBinary: 'Activer les Fichiers Binaires',
|
||||
enableCalls: 'Activer les Appels',
|
||||
iceService: 'Service Cloudflare',
|
||||
relayUrl: 'URL de Relais',
|
||||
relayUsername: 'Nom d\'Utilisateur du Relais',
|
||||
relayPassword: 'Mot de Passe du Relais',
|
||||
@ -580,6 +582,7 @@ const Strings = [
|
||||
enableVideo: 'Permitir Archivos de Vídeo',
|
||||
enableBinary: 'Permitir Archivos Binarios',
|
||||
enableCalls: 'Permitier Llamadas',
|
||||
iceService: 'Servicio Cloudflare',
|
||||
relayUrl: 'URL para Llamadas',
|
||||
relayUsername: 'Nombre de Usuario para Llamadas',
|
||||
relayPassword: 'Contraseña para Llamadas',
|
||||
@ -791,6 +794,7 @@ const Strings = [
|
||||
enableVideo: 'Videodateien aktivieren',
|
||||
enableBinary: 'Binärdateien aktivieren',
|
||||
enableCalls: 'Anrufe Ermöglichen',
|
||||
iceService: 'Cloudflare-Dienst',
|
||||
relayUrl: 'URL für Anrufe',
|
||||
relayUsername: 'Benutzername für Anrufe',
|
||||
relayPassword: 'Passwort für Anrufe',
|
||||
@ -990,6 +994,7 @@ const Strings = [
|
||||
enableVideo: 'Habilitar Fila de Vídeo',
|
||||
enableBinary: 'Habilitar Fila Binários',
|
||||
enableCalls: 'Habilitar Chamadas WebRTC',
|
||||
iceService: 'Serviço Cloudflare',
|
||||
relayUrl: 'URL do Relay',
|
||||
relayUsername: 'Nome de Usuário do Relay',
|
||||
relayPassword: 'Senha do Relay',
|
||||
@ -1186,6 +1191,7 @@ const Strings = [
|
||||
enableVideo: 'Включить очередь видео',
|
||||
enableBinary: 'Включить двоичные файлы',
|
||||
enableCalls: 'Включить звонки WebRTC',
|
||||
iceService: 'Сервис Cloudflare',
|
||||
relayUrl: 'URL релея',
|
||||
relayUsername: 'Имя пользователя релея',
|
||||
relayPassword: 'Пароль релея',
|
||||
|
@ -213,8 +213,9 @@ export function useAppContext() {
|
||||
card.actions.setRevision(cardRev);
|
||||
}
|
||||
else if (activity.ring) {
|
||||
const { cardId, callId, calleeToken, iceUrl, iceUsername, icePassword } = activity.ring;
|
||||
ring.actions.ring(cardId, callId, calleeToken, iceUrl, iceUsername, icePassword);
|
||||
const { cardId, callId, calleeToken, ice, iceUrl, iceUsername, icePassword } = activity.ring;
|
||||
const config = ice ? ice : [{ urls: iceUrl, username: iceUsername, credential: icePassword }];
|
||||
ring.actions.ring(cardId, callId, calleeToken, config);
|
||||
}
|
||||
else {
|
||||
const { profile: profileRev, account: accountRev, channel: channelRev, card: cardRev } = activity;
|
||||
|
@ -331,9 +331,9 @@ export function useRingContext() {
|
||||
clearSession: () => {
|
||||
access.current = null;
|
||||
},
|
||||
ring: (cardId, callId, calleeToken, iceUrl, iceUsername, icePassword) => {
|
||||
ring: (cardId, callId, calleeToken, ice) => {
|
||||
const key = `${cardId}:${callId}`
|
||||
const call = ringing.current.get(key) || { cardId, calleeToken, callId, iceUrl, iceUsername, icePassword }
|
||||
const call = ringing.current.get(key) || { cardId, calleeToken, callId, ice }
|
||||
call.expires = Date.now() + EXPIRE;
|
||||
ringing.current.set(key, call);
|
||||
updateState({ ringing: ringing.current });
|
||||
@ -365,7 +365,7 @@ export function useRingContext() {
|
||||
}
|
||||
}
|
||||
},
|
||||
accept: async (cardId, callId, contactNode, contactToken, calleeToken, iceUrl, iceUsername, icePassword) => {
|
||||
accept: async (cardId, callId, contactNode, contactToken, calleeToken, ice) => {
|
||||
if (calling.current) {
|
||||
throw new Error("active session");
|
||||
}
|
||||
@ -378,7 +378,6 @@ export function useRingContext() {
|
||||
updateState({ ringing: ringing.current, callStatus: "connecting", cardId });
|
||||
|
||||
calling.current = { callId, contactNode, contactToken, host: false };
|
||||
const ice = [{ urls: iceUrl, username: iceUsername, credential: icePassword }];
|
||||
await connect('impolite', contactNode, calleeToken, () => {}, () => {}, ice);
|
||||
}
|
||||
},
|
||||
@ -422,9 +421,9 @@ export function useRingContext() {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const { id, keepAlive, callerToken, calleeToken, iceUrl, iceUsername, icePassword } = call;
|
||||
const { id, keepAlive, callerToken, calleeToken, ice, iceUrl, iceUsername, icePassword } = call;
|
||||
try {
|
||||
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken, iceUrl, iceUsername, icePassword });
|
||||
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken, ice, iceUrl, iceUsername, icePassword });
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
@ -446,7 +445,7 @@ export function useRingContext() {
|
||||
}
|
||||
}
|
||||
else {
|
||||
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken, iceUrl, iceUsername, icePassword });
|
||||
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken, ice, iceUrl, iceUsername, icePassword });
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
@ -457,8 +456,8 @@ export function useRingContext() {
|
||||
|
||||
updateState({ callStatus: "ringing" });
|
||||
calling.current = { callId: id, host: true };
|
||||
const ice = [{ urls: iceUrl, username: iceUsername, credential: icePassword }];
|
||||
await connect('polite', server, callerToken, () => clearInterval(ringInterval), () => clearInterval(aliveInterval), ice);
|
||||
const iceLegacy = [{ urls: iceUrl, username: iceUsername, credential: icePassword }];
|
||||
await connect('polite', server, callerToken, () => clearInterval(ringInterval), () => clearInterval(aliveInterval), ice ? ice : iceLegacy);
|
||||
},
|
||||
enableVideo: async () => {
|
||||
if (!videoTrack.current) {
|
||||
|
@ -283,33 +283,48 @@ export function Dashboard(props) {
|
||||
onValueChange={actions.setEnableIce} trackColor={styles.track}/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<InputField style={styles.field}
|
||||
label={state.strings.relayUrl}
|
||||
value={state.iceUrl}
|
||||
autoCapitalize={'none'}
|
||||
spellCheck={false}
|
||||
disabled={!state.enableIce}
|
||||
onChangeText={actions.setIceUrl}
|
||||
/>
|
||||
{ state.enableIce && (
|
||||
<>
|
||||
{ state.iceServiceFlag != null && (
|
||||
<TouchableOpacity style={styles.ice} activeOpacity={1}
|
||||
onPress={() => actions.setIceServiceFlag(!state.iceServiceFlag)}>
|
||||
<Text style={styles.modalLabel}>{ state.strings.iceService }</Text>
|
||||
<Switch style={styles.switch} value={state.iceServiceFlag}
|
||||
onValueChange={actions.setIceServiceFlag} trackColor={styles.track}/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<InputField style={styles.field}
|
||||
label={state.strings.relayUsername}
|
||||
value={state.iceUsername}
|
||||
autoCapitalize={'none'}
|
||||
spellCheck={false}
|
||||
disabled={!state.enableIce}
|
||||
onChangeText={actions.setIceUsername}
|
||||
/>
|
||||
{ !state.iceServiceFlag && (
|
||||
<InputField style={styles.field}
|
||||
label={state.strings.relayUrl}
|
||||
value={state.iceUrl}
|
||||
autoCapitalize={'none'}
|
||||
spellCheck={false}
|
||||
disabled={!state.enableIce}
|
||||
onChangeText={actions.setIceUrl}
|
||||
/>
|
||||
)}
|
||||
|
||||
<InputField style={styles.field}
|
||||
label={state.iceServiceFlag ? 'TURN_KEY_ID' : state.strings.relayUsername}
|
||||
value={state.iceUsername}
|
||||
autoCapitalize={'none'}
|
||||
spellCheck={false}
|
||||
disabled={!state.enableIce}
|
||||
onChangeText={actions.setIceUsername}
|
||||
/>
|
||||
|
||||
<InputField style={styles.field}
|
||||
label={state.iceServiceFlag ? 'TURN_KEY_API_TOKEN' : state.strings.relayPassword}
|
||||
value={state.icePassword}
|
||||
autoCapitalize={'none'}
|
||||
spellCheck={false}
|
||||
disabled={!state.enableIce}
|
||||
onChangeText={actions.setIcePassword}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<InputField style={styles.field}
|
||||
label={state.strings.relayPassword}
|
||||
value={state.icePassword}
|
||||
autoCapitalize={'none'}
|
||||
spellCheck={false}
|
||||
disabled={!state.enableIce}
|
||||
onChangeText={actions.setIcePassword}
|
||||
/>
|
||||
|
||||
<View style={styles.pad} />
|
||||
|
||||
</ScrollView>
|
||||
|
@ -41,6 +41,7 @@ export function useDashboard(server, token, mfa) {
|
||||
enableBinary: true,
|
||||
createToken: null,
|
||||
enableIce: false,
|
||||
iceServiceFlag: false,
|
||||
iceUrl: null,
|
||||
iceUsername: null,
|
||||
icePassword: null,
|
||||
@ -78,9 +79,12 @@ export function useDashboard(server, token, mfa) {
|
||||
const config = await getNodeConfig(server, token);
|
||||
const nodeAccounts = await getNodeAccounts(server, token);
|
||||
const accounts = nodeAccounts.map(setAccountItem);
|
||||
const { keyType, accountStorage, domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceUrl, iceUsername, icePassword } = config || {};
|
||||
const { keyType, accountStorage, domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceService, iceUrl, iceUsername, icePassword } = config || {};
|
||||
const storage = Math.ceil(accountStorage / 1073741824);
|
||||
updateState({ keyType, storage: storage.toString(), domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceUrl, iceUsername, icePassword, accounts, mfaEnabled });
|
||||
const iceServiceFlag = iceService === 'cloudflare' ? true : iceService == null ? null : true;
|
||||
console.log("ICE:", iceService, iceServiceFlag);
|
||||
|
||||
updateState({ keyType, storage: storage.toString(), domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceServiceFlag, iceUrl, iceUsername, icePassword, accounts, mfaEnabled });
|
||||
}
|
||||
|
||||
const refreshAccounts = async () => {
|
||||
@ -150,6 +154,9 @@ export function useDashboard(server, token, mfa) {
|
||||
setEnableIce: (enableIce) => {
|
||||
updateState({ enableIce });
|
||||
},
|
||||
setIceServiceFlag: (iceServiceFlag) => {
|
||||
updateState({ iceServiceFlag });
|
||||
},
|
||||
setIceUrl: (iceUrl) => {
|
||||
updateState({ iceUrl });
|
||||
},
|
||||
@ -160,9 +167,10 @@ export function useDashboard(server, token, mfa) {
|
||||
updateState({ icePassword });
|
||||
},
|
||||
saveConfig: async () => {
|
||||
const { storage, domain, keyType, enableImage, pushSupported, allowUnsealed, transformSupported, enableAudio, enableVideo, enableBinary, enableIce, iceUrl, iceUsername, icePassword } = state;
|
||||
const { storage, domain, keyType, enableImage, pushSupported, allowUnsealed, transformSupported, enableAudio, enableVideo, enableBinary, enableIce, iceServiceFlag, iceUrl, iceUsername, icePassword } = state;
|
||||
const iceService = iceServiceFlag ? 'cloudflare' : '';
|
||||
const accountStorage = Number(storage) * 1073741824;
|
||||
const config = { accountStorage, domain, keyType, enableImage, pushSupported, allowUnsealed, transformSupported, enableAudio, enableVideo, enableBinary, enableIce, iceUrl, iceUsername, icePassword };
|
||||
const config = { accountStorage, domain, keyType, enableImage, pushSupported, allowUnsealed, transformSupported, enableAudio, enableVideo, enableBinary, enableIce, iceService, iceUrl, iceUsername, icePassword };
|
||||
await setNodeConfig(server, token, config);
|
||||
},
|
||||
enableUser: async (accountId, enabled) => {
|
||||
|
@ -49,7 +49,7 @@ export function useSession() {
|
||||
const expired = Date.now();
|
||||
ring.state.ringing.forEach(call => {
|
||||
if (call.expires > expired && !call.status) {
|
||||
const { callId, cardId, calleeToken, iceUrl, iceUsername, icePassword } = call;
|
||||
const { callId, cardId, calleeToken, ice } = call;
|
||||
const contact = card.state.cards.get(cardId);
|
||||
if (contact) {
|
||||
const { imageSet, name, handle, node, guid } = contact.card?.profile || {};
|
||||
@ -57,7 +57,7 @@ export function useSession() {
|
||||
const contactToken = `${guid}.${token}`;
|
||||
const server = node ? node : profile.state.server;
|
||||
const img = imageSet ? card.actions.getCardImageUrl(cardId) : null;
|
||||
ringing.push({ cardId, img, name, handle, contactNode: server, callId, contactToken, calleeToken, iceUrl, iceUsername, icePassword });
|
||||
ringing.push({ cardId, img, name, handle, contactNode: server, callId, contactToken, calleeToken, ice });
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -124,8 +124,8 @@ export function useSession() {
|
||||
await ring.actions.decline(cardId, contactNode, contactToken, callId);
|
||||
},
|
||||
accept: async (call) => {
|
||||
const { cardId, callId, contactNode, contactToken, calleeToken, iceUrl, iceUsername, icePassword } = call;
|
||||
await ring.actions.accept(cardId, callId, contactNode, contactToken, calleeToken, iceUrl, iceUsername, icePassword);
|
||||
const { cardId, callId, contactNode, contactToken, calleeToken, ice } = call;
|
||||
await ring.actions.accept(cardId, callId, contactNode, contactToken, calleeToken, ice);
|
||||
},
|
||||
end: async () => {
|
||||
await ring.actions.end();
|
||||
|
23
doc/api.oa3
23
doc/api.oa3
@ -4119,6 +4119,8 @@ components:
|
||||
type: boolean
|
||||
enableIce:
|
||||
type: boolean
|
||||
iceService:
|
||||
type: string
|
||||
iceUrl:
|
||||
type: string
|
||||
iceUsername:
|
||||
@ -4130,6 +4132,7 @@ components:
|
||||
openAccessLimit:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
|
||||
Seal:
|
||||
type: object
|
||||
@ -4899,12 +4902,28 @@ components:
|
||||
keepAlive:
|
||||
type: integer
|
||||
format: int32
|
||||
iceService:
|
||||
type: string
|
||||
iceUrl:
|
||||
type: string
|
||||
iceUsername:
|
||||
type: string
|
||||
icePassword:
|
||||
type: string
|
||||
|
||||
IceUrl:
|
||||
tyle: object
|
||||
required:
|
||||
- urls
|
||||
- username
|
||||
- credential
|
||||
properties:
|
||||
urls:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
credential:
|
||||
type: string
|
||||
|
||||
Ring:
|
||||
type: object
|
||||
@ -4920,6 +4939,10 @@ components:
|
||||
index:
|
||||
type: integer
|
||||
format: int32
|
||||
ice:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/IceUrl'
|
||||
iceUrl:
|
||||
type: string
|
||||
iceUsername:
|
||||
|
@ -40,6 +40,17 @@ func AddCall(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
iceService := getStrConfigValue(CNFIceService, "");
|
||||
iceURL := getStrConfigValue(CNFIceUrl, "")
|
||||
iceUsername := getStrConfigValue(CNFIceUsername, "")
|
||||
icePassword := getStrConfigValue(CNFIcePassword, "")
|
||||
|
||||
ice, err := getIce(iceService, iceURL, iceUsername, icePassword);
|
||||
if err != nil || len(ice) == 0 {
|
||||
ErrResponse(w, http.StatusServiceUnavailable, err)
|
||||
return
|
||||
}
|
||||
|
||||
// generate call params
|
||||
callerBin, callerErr := securerandom.Bytes(APPTokenSize)
|
||||
if callerErr != nil {
|
||||
@ -51,31 +62,28 @@ func AddCall(w http.ResponseWriter, r *http.Request) {
|
||||
ErrResponse(w, http.StatusInternalServerError, calleeErr)
|
||||
return
|
||||
}
|
||||
//turnBin, turnErr := securerandom.Bytes(APPTokenSize)
|
||||
//if turnErr != nil {
|
||||
// ErrResponse(w, http.StatusInternalServerError, turnErr)
|
||||
// return
|
||||
//}
|
||||
callId := uuid.New().String()
|
||||
|
||||
// allocate bridge
|
||||
callerToken := hex.EncodeToString(callerBin);
|
||||
calleeToken := hex.EncodeToString(calleeBin);
|
||||
iceUrl := getStrConfigValue(CNFIceUrl, "")
|
||||
iceUsername := getStrConfigValue(CNFIceUsername, "")
|
||||
icePassword := getStrConfigValue(CNFIcePassword, "")
|
||||
bridgeRelay.AddBridge(account.ID, callId, cardId, callerToken, calleeToken);
|
||||
|
||||
turn := getDefaultIce(ice);
|
||||
|
||||
// create response
|
||||
call := Call{
|
||||
Id: callId,
|
||||
CardId: cardId,
|
||||
CallerToken: callerToken,
|
||||
CalleeToken: calleeToken,
|
||||
IceUrl: iceUrl,
|
||||
IceUsername: iceUsername,
|
||||
IcePassword: icePassword,
|
||||
Ice: ice,
|
||||
IceService: iceService,
|
||||
IceURL: turn.URLs,
|
||||
IceUsername: turn.Username,
|
||||
IcePassword: turn.Credential,
|
||||
KeepAlive: BridgeKeepAlive,
|
||||
}
|
||||
|
||||
WriteResponse(w, call);
|
||||
}
|
||||
|
@ -25,7 +25,8 @@ func GetNodeConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config.KeyType = getStrConfigValue(CNFKeyType, APPRSA2048)
|
||||
config.PushSupported = getBoolConfigValue(CNFPushSupported, true)
|
||||
config.EnableIce = getBoolConfigValue(CNFEnableIce, false)
|
||||
config.IceUrl = getStrConfigValue(CNFIceUrl, "")
|
||||
config.IceService = getStrConfigValue(CNFIceService, "")
|
||||
config.IceURL = getStrConfigValue(CNFIceUrl, "")
|
||||
config.IceUsername = getStrConfigValue(CNFIceUsername, "")
|
||||
config.IcePassword = getStrConfigValue(CNFIcePassword, "")
|
||||
config.EnableOpenAccess = getBoolConfigValue(CNFEnableOpenAccess, false);
|
||||
|
@ -101,7 +101,7 @@ func SetNodeConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return res
|
||||
}
|
||||
|
||||
// upsert push supported
|
||||
// upsert ice supported
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"bool_value"}),
|
||||
@ -109,11 +109,19 @@ func SetNodeConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return res
|
||||
}
|
||||
|
||||
// upsert ice service name
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"str_value"}),
|
||||
}).Create(&store.Config{ConfigID: CNFIceService, StrValue: config.IceService}).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
|
||||
// upsert key type
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"str_value"}),
|
||||
}).Create(&store.Config{ConfigID: CNFIceUrl, StrValue: config.IceUrl}).Error; res != nil {
|
||||
}).Create(&store.Config{ConfigID: CNFIceUrl, StrValue: config.IceURL}).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,8 @@ func SetRing(card *store.Card, ring Ring) {
|
||||
var phone Phone
|
||||
phone.CallID = ring.CallID
|
||||
phone.CalleeToken = ring.CalleeToken
|
||||
phone.IceUrl = ring.IceUrl
|
||||
phone.Ice = ring.Ice
|
||||
phone.IceURL = ring.IceURL
|
||||
phone.IceUsername = ring.IceUsername
|
||||
phone.IcePassword = ring.IcePassword
|
||||
phone.CardID = card.CardSlot.CardSlotID
|
||||
|
@ -54,6 +54,9 @@ const CNFKeyType = "key_type"
|
||||
//CNFEnableIce specifies whether webrtc is enabled
|
||||
const CNFEnableIce = "enable_ice"
|
||||
|
||||
//CNFIceMode specifies if turn service is used
|
||||
const CNFIceService = "ice_service"
|
||||
|
||||
//CNFIceUrl specifies the ice candidate url
|
||||
const CNFIceUrl = "ice_url"
|
||||
|
||||
|
57
net/server/internal/iceUtil.go
Normal file
57
net/server/internal/iceUtil.go
Normal file
@ -0,0 +1,57 @@
|
||||
package databag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"errors"
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getIce(service string, urls string, username string, credential string) ([]IceURL, error) {
|
||||
|
||||
if service != "" {
|
||||
gen := "https://rtc.live.cloudflare.com/v1/turn/keys/" + username + "/credentials/generate"
|
||||
req, err := http.NewRequest(http.MethodPost, gen, bytes.NewBuffer([]byte("{\"ttl\": 86400}")))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
req.Header.Set("Authorization", "Bearer " + credential)
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil || resp == nil || resp.StatusCode != 201 {
|
||||
return nil, errors.New("invalid ice service response")
|
||||
}
|
||||
|
||||
var r IceService
|
||||
err = json.NewDecoder(resp.Body).Decode(&r)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid ice service response")
|
||||
}
|
||||
|
||||
ice := []IceURL{}
|
||||
for _, url := range r.Servers.URLs {
|
||||
ice = append(ice, IceURL{ URLs: url, Username: r.Servers.Username, Credential: r.Servers.Credential });
|
||||
}
|
||||
return ice, nil
|
||||
}
|
||||
|
||||
return []IceURL {
|
||||
IceURL {
|
||||
URLs: urls,
|
||||
Username: username,
|
||||
Credential: credential,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getDefaultIce(ice []IceURL) IceURL {
|
||||
for _, url := range ice {
|
||||
if strings.HasSuffix(url.URLs, "?transport=udp") {
|
||||
return url
|
||||
}
|
||||
}
|
||||
return ice[0];
|
||||
}
|
||||
|
@ -375,7 +375,9 @@ type NodeConfig struct {
|
||||
|
||||
EnableIce bool `json:"enableIce"`
|
||||
|
||||
IceUrl string `json:"iceUrl"`
|
||||
IceService string `json:"iceService"`
|
||||
|
||||
IceURL string `json:"iceUrl"`
|
||||
|
||||
IceUsername string `json:"iceUsername"`
|
||||
|
||||
@ -458,7 +460,9 @@ type Phone struct {
|
||||
|
||||
CalleeToken string `json:"calleeToken"`
|
||||
|
||||
IceUrl string `json:"iceUrl"`
|
||||
Ice []IceURL `json:"ice,omitEmpty"`
|
||||
|
||||
IceURL string `json:"iceUrl"`
|
||||
|
||||
IceUsername string `json:"iceUsername"`
|
||||
|
||||
@ -563,13 +567,40 @@ type Call struct {
|
||||
|
||||
KeepAlive int32 `json:"keepAlive"`
|
||||
|
||||
IceUrl string `json:"iceUrl"`
|
||||
IceService string `json:"iceService"`
|
||||
|
||||
Ice []IceURL `json:"ice,omitEmpty"`
|
||||
|
||||
IceURL string `json:"iceUrl"`
|
||||
|
||||
IceUsername string `json:"iceUsername"`
|
||||
|
||||
IcePassword string `json:"icePassword"`
|
||||
}
|
||||
|
||||
type IceServers struct {
|
||||
|
||||
URLs []string `json:"urls"`
|
||||
|
||||
Username string `json:"username"`
|
||||
|
||||
Credential string `json:"credential"`
|
||||
}
|
||||
|
||||
type IceService struct {
|
||||
|
||||
Servers IceServers `json:"iceServers"`
|
||||
}
|
||||
|
||||
type IceURL struct {
|
||||
|
||||
URLs string `json:"urls"`
|
||||
|
||||
Username string `json:"username"`
|
||||
|
||||
Credential string `json:"credential"`
|
||||
}
|
||||
|
||||
type Ring struct {
|
||||
|
||||
CallID string `json:"callId"`
|
||||
@ -578,7 +609,9 @@ type Ring struct {
|
||||
|
||||
Index int32 `json:"index"`
|
||||
|
||||
IceUrl string `json:"iceUrl"`
|
||||
Ice []IceURL `json:"ice,omitEmpty"`
|
||||
|
||||
IceURL string `json:"iceUrl"`
|
||||
|
||||
IceUsername string `json:"iceUsername"`
|
||||
|
||||
|
@ -144,6 +144,8 @@ export const en = {
|
||||
binaryHint: 'Allow binary files to be posted in topics',
|
||||
enableWeb: 'Enable WebRTC Calls',
|
||||
webHint: 'Enable audio and video calls to contacts',
|
||||
enableService: 'Cloudflare Service',
|
||||
serviceHint: 'Enable Cloudflare Service',
|
||||
serverUrl: 'WebRTC Server URL',
|
||||
urlHint: 'turn:ip:port?transport=udp',
|
||||
webUsername: 'WebRTC Username',
|
||||
@ -350,6 +352,8 @@ export const fr = {
|
||||
binaryHint: 'Autoriser la publication de fichiers binaires dans les sujets',
|
||||
enableWeb: 'Activer les Appels WebRTC',
|
||||
webHint: 'Autoriser les appels audio et vidéo aux contacts',
|
||||
enableService: 'Service Cloudflare',
|
||||
serviceHint: 'Activer le Service Cloudflare',
|
||||
serverUrl: 'URL du Serveur WebRTC',
|
||||
urlHint: 'turn:ip:port?transport=udp',
|
||||
webUsername: "Nom d'Utilisateur WebRTC",
|
||||
@ -557,6 +561,8 @@ export const sp = {
|
||||
binaryHint: 'Permitir que se publiquen archivos binarios en temas',
|
||||
enableWeb: 'Activar llamadas WebRTC',
|
||||
webHint: 'Permitir llamadas de audio y video a contactos',
|
||||
enableService: 'Servicio Cloudflare',
|
||||
serviceHint: 'Habilitar el Servicio Cloudflare',
|
||||
serverUrl: 'URL del servidor WebRTC',
|
||||
urlHint: 'turn:ip:puerto?transporte=udp',
|
||||
webUsername: 'Nombre de usuario WebRTC',
|
||||
@ -763,6 +769,8 @@ export const pt = {
|
||||
binaryHint: 'Permitir que arquivos binários sejam postados em tópicos',
|
||||
enableWeb: 'Ativar chamadas WebRTC',
|
||||
webHint: 'Permitir chamadas de áudio e vídeo para contatos',
|
||||
enableService: 'Serviço Cloudflare',
|
||||
serviceHint: 'Habilitar serviço Cloudflare',
|
||||
serverUrl: 'URL do servidor WebRTC',
|
||||
urlHint: 'turn:ip:port?transport=udp',
|
||||
webUsername: 'Nome de usuário WebRTC',
|
||||
@ -969,6 +977,8 @@ export const de = {
|
||||
binaryHint: 'Erlauben Sie die Veröffentlichung von Binärdateien in Themen',
|
||||
enableWeb: 'WebRTC-Anrufe aktivieren',
|
||||
webHint: 'Audio- und Videoanrufe an Kontakte zulassen',
|
||||
enableService: 'Cloudflare-Dienst',
|
||||
serviceHint: 'Aktivieren Sie den Cloudflare-Dienst',
|
||||
serverUrl: 'URL des WebRTC-Servers',
|
||||
urlHint: 'turn:ip:port?transport=udp',
|
||||
webUsername: 'WebRTC-Benutzername',
|
||||
@ -1175,6 +1185,8 @@ export const ru = {
|
||||
binaryHint: 'Разрешить публикацию двоичных файлов в темах',
|
||||
enableWeb: 'Включить WebRTC-звонки',
|
||||
webHint: 'Разрешить аудио- и видеозвонки контактам',
|
||||
enableService: 'Сервис Cloudflare',
|
||||
serviceHint: 'Включить службу Cloudflare',
|
||||
serverUrl: 'URL сервера WebRTC',
|
||||
urlHint: 'turn:ip:port?transport=udp',
|
||||
webUsername: 'Имя пользователя WebRTC',
|
||||
|
@ -185,8 +185,9 @@ export function useAppContext(websocket) {
|
||||
setAppRevision(activity.revision);
|
||||
}
|
||||
else if (activity.ring) {
|
||||
const { cardId, callId, calleeToken, iceUrl, iceUsername, icePassword } = activity.ring;
|
||||
ringContext.actions.ring(cardId, callId, calleeToken, iceUrl, iceUsername, icePassword);
|
||||
const { cardId, callId, calleeToken, ice, iceUrl, iceUsername, icePassword } = activity.ring;
|
||||
const config = ice ? ice : [{ urls: iceUrl, username: iceUsername, credential: icePassword }];
|
||||
ringContext.actions.ring(cardId, callId, calleeToken, config);
|
||||
}
|
||||
else {
|
||||
setAppRevision(activity);
|
||||
|
@ -300,9 +300,9 @@ export function useRingContext() {
|
||||
clearToken: () => {
|
||||
access.current = null;
|
||||
},
|
||||
ring: (cardId, callId, calleeToken, iceUrl, iceUsername, icePassword) => {
|
||||
ring: (cardId, callId, calleeToken, ice) => {
|
||||
const key = `${cardId}:${callId}`
|
||||
const call = ringing.current.get(key) || { cardId, calleeToken, callId, iceUrl, iceUsername, icePassword }
|
||||
const call = ringing.current.get(key) || { cardId, calleeToken, callId, ice }
|
||||
call.expires = Date.now() + EXPIRE;
|
||||
ringing.current.set(key, call);
|
||||
updateState({ ringing: ringing.current });
|
||||
@ -334,13 +334,13 @@ export function useRingContext() {
|
||||
}
|
||||
}
|
||||
},
|
||||
accept: async (cardId, callId, contactNode, contactToken, calleeToken, iceUrl, iceUsername, icePassword, audioId) => {
|
||||
accept: async (cardId, callId, contactNode, contactToken, calleeToken, ice, audioId) => {
|
||||
console.log("ACCEPT", ice);
|
||||
|
||||
if (calling.current) {
|
||||
throw new Error("active session");
|
||||
}
|
||||
|
||||
const ice = [{ urls: iceUrl, username: iceUsername, credential: icePassword }];
|
||||
|
||||
const key = `${cardId}:${callId}`
|
||||
const call = ringing.current.get(key);
|
||||
if (call) {
|
||||
@ -398,9 +398,10 @@ export function useRingContext() {
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
const { id, keepAlive, callerToken, calleeToken, iceUrl, iceUsername, icePassword } = call;
|
||||
const { id, keepAlive, callerToken, calleeToken, ice } = call;
|
||||
try {
|
||||
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken, iceUrl, iceUsername, icePassword });
|
||||
const turn = ice[ice.length - 1]; //backwards compatibility
|
||||
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken, ice, iceUrl: turn.urls, iceUsername: turn.username, icePassword: turn.credential });
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
@ -421,7 +422,8 @@ export function useRingContext() {
|
||||
}
|
||||
}
|
||||
else {
|
||||
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken, iceUrl, iceUsername, icePassword });
|
||||
const turn = ice[ice.length - 1];
|
||||
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken, ice, iceUrl: turn.urls, iceUsername: turn.username, icePassword: turn.credential });
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
@ -432,7 +434,6 @@ export function useRingContext() {
|
||||
|
||||
updateState({ callStatus: "ringing" });
|
||||
calling.current = { callId: id, host: true };
|
||||
const ice = [{ urls: iceUrl, username: iceUsername, credential: icePassword }];
|
||||
await connect('polite', audioId, window.location.host, callerToken, () => clearInterval(ringInterval), () => clearInterval(aliveInterval), ice);
|
||||
},
|
||||
enableVideo: async (videoId) => {
|
||||
|
@ -265,21 +265,34 @@ export function Dashboard() {
|
||||
defaultChecked={false} checked={state.enableIce} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className="field">
|
||||
<div>{state.strings.serverUrl}</div>
|
||||
<Input placeholder={state.strings.urlHint} onChange={(e) => actions.setIceUrl(e.target.value)}
|
||||
disabled={!state.enableIce} value={state.iceUrl} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div>{state.strings.webUsername}</div>
|
||||
<Input placeholder={state.strings.username} onChange={(e) => actions.setIceUsername(e.target.value)}
|
||||
disabled={!state.enableIce} value={state.iceUsername} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div>{state.strings.webPassword}</div>
|
||||
<Input placeholder={state.strings.password} onChange={(e) => actions.setIcePassword(e.target.value)}
|
||||
disabled={!state.enableIce} value={state.icePassword} />
|
||||
</div>
|
||||
{ state.enableIce && (
|
||||
<div className="iceInput">
|
||||
<Tooltip placement="topLeft" title={state.strings.serviceHint}>
|
||||
<div className="field">
|
||||
<div>{state.strings.enableService}</div>
|
||||
<Switch onChange={(e) => actions.setIceServiceFlag(e)} size="small"
|
||||
defaultChecked={false} checked={state.iceServiceFlag} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{ !state.iceServiceFlag && (
|
||||
<div className="field">
|
||||
<div>{state.strings.serverUrl}</div>
|
||||
<Input placeholder={state.strings.urlHint} onChange={(e) => actions.setIceUrl(e.target.value)}
|
||||
value={state.iceUrl} />
|
||||
</div>
|
||||
)}
|
||||
<div className="field">
|
||||
<div>{state.iceServiceFlag ? 'TURN_KEY_ID' : state.strings.webUsername}</div>
|
||||
<Input placeholder={state.strings.username} onChange={(e) => actions.setIceUsername(e.target.value)}
|
||||
value={state.iceUsername} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div>{state.iceServiceFlag ? 'TURN_KEY_API_TOKEN' : state.strings.webPassword}</div>
|
||||
<Input placeholder={state.strings.password} onChange={(e) => actions.setIcePassword(e.target.value)}
|
||||
value={state.icePassword} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="control">
|
||||
<Button key="back" onClick={() => actions.setShowSettings(false)}>{state.strings.cancel}</Button>
|
||||
<Button key="save" type="primary" onClick={() => actions.setSettings()} loading={state.busy}>{state.strings.save}</Button>
|
||||
|
@ -111,6 +111,12 @@ export const SettingsLayout = styled(Space)`
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.iceInput {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.field {
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
|
@ -27,6 +27,7 @@ export function useDashboard(token) {
|
||||
enableVideo: null,
|
||||
enableBinary: null,
|
||||
enableIce: null,
|
||||
iceServiceFlag: null,
|
||||
iceUrl: null,
|
||||
iceUsername: null,
|
||||
icePassword: null,
|
||||
@ -128,6 +129,10 @@ export function useDashboard(token) {
|
||||
setEnableIce: (enableIce) => {
|
||||
updateState({ enableIce });
|
||||
},
|
||||
setIceServiceFlag: (iceServiceFlag) => {
|
||||
const iceUrl = iceServiceFlag ? 'https://rtc.live.cloudflare.com/v1/turn/keys/%%TURN_KEY_ID%%/credentials/generate' : '';
|
||||
updateState({ iceServiceFlag, iceUrl });
|
||||
},
|
||||
setIceUrl: (iceUrl) => {
|
||||
updateState({ iceUrl });
|
||||
},
|
||||
@ -181,9 +186,10 @@ export function useDashboard(token) {
|
||||
if (!state.busy) {
|
||||
updateState({ busy: true });
|
||||
try {
|
||||
const { domain, keyType, accountStorage, pushSupported, transformSupported, allowUnsealed, enableImage, enableAudio, enableVideo, enableBinary, enableIce, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit } = state;
|
||||
const { domain, keyType, accountStorage, pushSupported, transformSupported, allowUnsealed, enableImage, enableAudio, enableVideo, enableBinary, enableIce, iceServiceFlag, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit } = state;
|
||||
const storage = accountStorage * 1073741824;
|
||||
const config = { domain, accountStorage: storage, keyType, enableImage, enableAudio, enableVideo, enableBinary, pushSupported, transformSupported, allowUnsealed, enableIce, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit };
|
||||
const iceService = iceServiceFlag ? 'cloudflare' : '';
|
||||
const config = { domain, accountStorage: storage, keyType, enableImage, enableAudio, enableVideo, enableBinary, pushSupported, transformSupported, allowUnsealed, enableIce, iceService, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit };
|
||||
await setNodeConfig(app.state.adminToken, config);
|
||||
updateState({ busy: false, showSettings: false });
|
||||
}
|
||||
@ -200,9 +206,10 @@ export function useDashboard(token) {
|
||||
try {
|
||||
const enabled = await getAdminMFAuth(app.state.adminToken);
|
||||
const config = await getNodeConfig(app.state.adminToken);
|
||||
const { accountStorage, domain, keyType, pushSupported, transformSupported, allowUnsealed, enableImage, enableAudio, enableVideo, enableBinary, enableIce, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit } = config;
|
||||
const { accountStorage, domain, keyType, pushSupported, transformSupported, allowUnsealed, enableImage, enableAudio, enableVideo, enableBinary, enableIce, iceService, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit } = config;
|
||||
const iceServiceFlag = iceService === 'cloudflare';
|
||||
const storage = Math.ceil(accountStorage / 1073741824);
|
||||
updateState({ mfAuthSet: true, mfaAuthEnabled: enabled, configError: false, domain, accountStorage: storage, keyType, enableImage, enableAudio, enableVideo, enableBinary, pushSupported, transformSupported, allowUnsealed, enableIce, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit });
|
||||
updateState({ mfAuthSet: true, mfaAuthEnabled: enabled, configError: false, domain, accountStorage: storage, keyType, enableImage, enableAudio, enableVideo, enableBinary, pushSupported, transformSupported, allowUnsealed, enableIce, iceServiceFlag, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit });
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
|
@ -58,14 +58,14 @@ export function useSession() {
|
||||
const expired = Date.now();
|
||||
ring.state.ringing.forEach(call => {
|
||||
if (call.expires > expired && !call.status) {
|
||||
const { callId, cardId, calleeToken, iceUrl, iceUsername, icePassword } = call;
|
||||
const { callId, cardId, calleeToken, ice } = call;
|
||||
const contact = card.state.cards.get(cardId);
|
||||
if (contact) {
|
||||
const { imageSet, name, handle, node, guid } = contact.data.cardProfile || {};
|
||||
const { token } = contact.data.cardDetail;
|
||||
const contactToken = `${guid}.${token}`;
|
||||
const img = imageSet ? card.actions.getCardImageUrl(cardId) : null;
|
||||
ringing.push({ cardId, img, name, handle, contactNode: node, callId, contactToken, calleeToken, iceUrl, iceUsername, icePassword });
|
||||
ringing.push({ cardId, img, name, handle, contactNode: node, callId, contactToken, calleeToken, ice });
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -180,9 +180,9 @@ export function useSession() {
|
||||
await ring.actions.decline(cardId, node, contactToken, callId);
|
||||
},
|
||||
accept: async (call) => {
|
||||
const { cardId, callId, contactNode, contactToken, calleeToken, iceUrl, iceUsername, icePassword } = call;
|
||||
const { cardId, callId, contactNode, contactToken, calleeToken, ice } = call;
|
||||
const node = contactNode ? contactNode : window.location.host;
|
||||
await ring.actions.accept(cardId, callId, node, contactToken, calleeToken, iceUrl, iceUsername, icePassword, state.audioId);
|
||||
await ring.actions.accept(cardId, callId, node, contactToken, calleeToken, ice, state.audioId);
|
||||
},
|
||||
end: async () => {
|
||||
await ring.actions.end();
|
||||
|
Loading…
Reference in New Issue
Block a user