passing ice params through webapp

This commit is contained in:
Roland Osborne 2023-04-13 00:39:14 -07:00
parent 8e2e0d6a6c
commit de043e1f36
16 changed files with 216 additions and 25 deletions

View File

@ -3855,7 +3855,15 @@ components:
type: string
pushSupported:
type: boolean
enableIce:
type: boolean
iceUrl:
type: string
iceUsername:
type: string
icePassword:
type: string
Seal:
type: object
required:
@ -3904,7 +3912,9 @@ components:
type: boolean
seal:
$ref: '#/components/schemas/Seal'
enableIce:
type: boolean
AccountProfile:
type: object
required:
@ -4614,6 +4624,12 @@ components:
keepAlive:
type: integer
format: int32
iceUrl:
type: string
iceUsername:
type: string
icePassword:
type: string
Ring:
type: object
@ -4629,6 +4645,12 @@ components:
index:
type: integer
format: int32
iceUrl:
type: string
iceUsername:
type: string
icePassword:
type: string
securitySchemes:

View File

@ -61,6 +61,9 @@ func AddCall(w http.ResponseWriter, r *http.Request) {
// 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);
// create response
@ -69,6 +72,9 @@ func AddCall(w http.ResponseWriter, r *http.Request) {
CardId: cardId,
CallerToken: callerToken,
CalleeToken: calleeToken,
IceUrl: iceUrl,
IceUsername: iceUsername,
IcePassword: icePassword,
KeepAlive: BridgeKeepAlive,
}
WriteResponse(w, call);

View File

@ -36,6 +36,7 @@ func GetAccountStatus(w http.ResponseWriter, r *http.Request) {
status.ForwardingAddress = account.Forward
status.Searchable = account.Searchable
status.Sealable = true
status.EnableIce = getBoolConfigValue(CNFEnableIce, false)
status.PushEnabled = session.PushEnabled
status.Seal = seal
WriteResponse(w, status)

View File

@ -22,6 +22,10 @@ func GetNodeConfig(w http.ResponseWriter, r *http.Request) {
config.EnableVideo = getBoolConfigValue(CNFEnableVideo, true)
config.KeyType = getStrConfigValue(CNFKeyType, APPRSA4096)
config.PushSupported = getBoolConfigValue(CNFPushSupported, true)
config.EnableIce = getBoolConfigValue(CNFEnableIce, false)
config.IceUrl = getStrConfigValue(CNFIceUrl, "")
config.IceUsername = getStrConfigValue(CNFIceUsername, "")
config.IcePassword = getStrConfigValue(CNFIcePassword, "")
WriteResponse(w, config)
}

View File

@ -82,6 +82,38 @@ func SetNodeConfig(w http.ResponseWriter, r *http.Request) {
return res
}
// upsert push supported
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"bool_value"}),
}).Create(&store.Config{ConfigID: CNFEnableIce, BoolValue: config.EnableIce}).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 {
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: CNFIceUsername, StrValue: config.IceUsername}).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: CNFIcePassword, StrValue: config.IcePassword}).Error; res != nil {
return res
}
return nil
})
if err != nil {
@ -89,5 +121,27 @@ func SetNodeConfig(w http.ResponseWriter, r *http.Request) {
return
}
// increment revision of all account data
var accounts []*store.Account
if err := store.DB.Find(&accounts).Error; err != nil {
ErrMsg(err);
return
}
err = store.DB.Transaction(func(tx *gorm.DB) error {
for _, account := range accounts {
if res := tx.Model(account).Update("account_revision", account.AccountRevision+1).Error; res != nil {
return res
}
}
return nil
})
if err != nil {
ErrMsg(err);
return
}
for _, account := range accounts {
SetStatus(account)
}
w.WriteHeader(http.StatusOK)
}

View File

@ -129,6 +129,9 @@ func SetRing(card *store.Card, ring Ring) {
var phone Phone
phone.CallID = ring.CallID
phone.CalleeToken = ring.CalleeToken
phone.IceUrl = ring.IceUrl
phone.IceUsername = ring.IceUsername
phone.IcePassword = ring.IcePassword
phone.CardID = card.CardSlot.CardSlotID
var a Activity
a.Phone = ☎

View File

@ -45,6 +45,19 @@ const CNFEnableVideo = "enable_video"
//CNFKeyType specifies the type of key to use for identity
const CNFKeyType = "key_type"
//CNFEnableIce specifies whether webrtc is enabled
const CNFEnableIce = "enable_ice"
//CNFIceUrl specifies the ice candidate url
const CNFIceUrl = "ice_url"
//CNFIceUrl specifies the ice candidate username
const CNFIceUsername = "ice_username"
//CNFIceUrl specifies the ice candidate url
const CNFIcePassword = "ice_password"
func getStrConfigValue(configID string, empty string) string {
var config store.Config
err := store.DB.Where("config_id = ?", configID).First(&config).Error

View File

@ -61,6 +61,5 @@ func SetCredentials(r *http.Request, login string) {
func ParseRequest(r *http.Request, w http.ResponseWriter, obj interface{}) error {
r.Body = http.MaxBytesReader(w, r.Body, APPBodyLimit)
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
return dec.Decode(&obj)
}

View File

@ -38,6 +38,8 @@ type AccountStatus struct {
Sealable bool `json:"sealable"`
Seal *Seal `json:"seal,omitempty"`
EnableIce bool `json:"enableIce"`
}
//Announce initial message sent on websocket
@ -354,6 +356,14 @@ type NodeConfig struct {
EnableVideo bool `json:"enableVideo"`
EnableIce bool `json:"enableIce"`
IceUrl string `json:"iceUrl"`
IceUsername string `json:"iceUsername"`
IcePassword string `json:"icePassword"`
KeyType string `json:"keyType"`
AccountStorage int64 `json:"accountStorage"`
@ -422,6 +432,12 @@ type Phone struct {
CallID string `json:"callId"`
CalleeToken string `json:"calleeToken"`
IceUrl string `json:"iceUrl"`
IceUsername string `json:"iceUsername"`
IcePassword string `json:"icePassword"`
}
//Seal key for channel sealing
@ -522,7 +538,11 @@ type Call struct {
KeepAlive int32 `json:"keepAlive"`
SturnPort int32 `json:"sturnPort"`
IceUrl string `json:"iceUrl"`
IceUsername string `json:"iceUsername"`
IcePassword string `json:"icePassword"`
}
type Ring struct {
@ -532,4 +552,10 @@ type Ring struct {
CalleeToken string `json:"calleeToken"`
Index int32 `json:"index"`
IceUrl string `json:"iceUrl"`
IceUsername string `json:"iceUsername"`
IcePassword string `json:"icePassword"`
}

View File

@ -373,9 +373,9 @@ export function useRingContext() {
// create call
const call = await addCall(access.current, cardId);
const { id, keepAlive, callerToken, calleeToken } = call;
const { id, keepAlive, callerToken, calleeToken, iceUrl, iceUsername, icePassword } = call;
try {
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken });
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken, iceUrl, iceUsername, icePassword });
}
catch (err) {
console.log(err);
@ -397,7 +397,7 @@ export function useRingContext() {
}
}
else {
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken });
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken, iceUrl, iceUsername, icePassword });
index += 1;
}
}

View File

@ -127,20 +127,51 @@ export function Dashboard() {
<Switch onChange={(e) => actions.setPushSupported(e)} size="small"
defaultChecked={true} checked={state.pushSupported} />
</div>
<div className="field label">
<span>Topic Content:</span>
</div>
<Tooltip placement="topLeft" title="Allows images to be posted and processed in topics">
<div className="field">
<div>Enable Image Queue:&nbsp;</div>
<Switch onChange={(e) => actions.setEnableImage(e)} size="small"
defaultChecked={true} checked={state.enableImage} />
</div>
</Tooltip>
<Tooltip placement="topLeft" title="Allows for audio to be posted and processed in topics">
<div className="field">
<div>Enable Audio Queue:&nbsp;</div>
<Switch onChange={(e) => actions.setEnableAudio(e)} size="small"
defaultChecked={true} checked={state.enableAudio} />
</div>
</Tooltip>
<Tooltip placement="topLeft" title="Allows videos to be posted and processed in topics">
<div className="field">
<div>Enable Video Queue:&nbsp;</div>
<Switch onChange={(e) => actions.setEnableVideo(e)} size="small"
defaultChecked={true} checked={state.enableVideo} />
</div>
</Tooltip>
<Tooltip placement="topLeft" title="Enabled audio and video calls to contacts">
<div className="field label">
<div>Enable WebRTC calls:&nbsp;</div>
<Switch onChange={(e) => actions.setEnableIce(e)} size="small"
defaultChecked={false} checked={state.enableIce} />
</div>
</Tooltip>
<div className="field">
<div>Enable Image Queue:&nbsp;</div>
<Switch onChange={(e) => actions.setEnableImage(e)} size="small"
defaultChecked={true} checked={state.enableImage} />
<div>WebRTC Server URL:&nbsp;</div>
<Input placeholder="turn:ip:port?transport=udp" onChange={(e) => actions.setIceUrl(e.target.value)}
disabled={!state.enableIce} value={state.iceUrl} />
</div>
<div className="field">
<div>Enable Audio Queue:&nbsp;</div>
<Switch onChange={(e) => actions.setEnableAudio(e)} size="small"
defaultChecked={true} checked={state.enableAudio} />
<div>WebRTC Username:&nbsp;</div>
<Input placeholder="username" onChange={(e) => actions.setIceUsername(e.target.value)}
disabled={!state.enableIce} value={state.iceUsername} />
</div>
<div className="field">
<div>Enable Video Queue:&nbsp;</div>
<Switch onChange={(e) => actions.setEnableVideo(e)} size="small"
defaultChecked={true} checked={state.enableVideo} />
<div>WebRTC Password:&nbsp;</div>
<Input placeholder="password" onChange={(e) => actions.setIcePassword(e.target.value)}
disabled={!state.enableIce} value={state.icePassword} />
</div>
</SettingsLayout>
</Modal>

View File

@ -86,6 +86,12 @@ export const AlertIcon = styled.div`
export const SettingsLayout = styled(Space)`
width: 100%;
.label {
border-top: 1px solid ${Colors.divider};
padding-top: 8px;
margin-top: 8px;
}
.field {
white-space: nowrap;
display: flex;

View File

@ -17,6 +17,10 @@ export function useDashboard() {
enableImage: null,
enableAudio: null,
enableVideo: null,
enableIce: null,
iceUrl: null,
iceUsername: null,
icePassword: null,
configError: false,
accountsError: false,
@ -87,6 +91,18 @@ export function useDashboard() {
setEnableVideo: (enableVideo) => {
updateState({ enableVideo });
},
setEnableIce: (enableIce) => {
updateState({ enableIce });
},
setIceUrl: (iceUrl) => {
updateState({ iceUrl });
},
setIceUsername: (iceUsername) => {
updateState({ iceUsername });
},
setIcePassword: (icePassword) => {
updateState({ icePassword });
},
setShowSettings: (value) => {
updateState({ showSettings: value });
},
@ -101,9 +117,9 @@ export function useDashboard() {
if (!state.busy) {
updateState({ busy: true });
try {
const { domain, keyType, accountStorage, pushSupported, enableImage, enableAudio, enableVideo } = state;
const { domain, keyType, accountStorage, pushSupported, enableImage, enableAudio, enableVideo, enableIce, iceUrl, iceUsername, icePassword } = state;
const storage = accountStorage * 1073741824;
const config = { domain, accountStorage: storage, keyType, enableImage, enableAudio, enableVideo, pushSupported };
const config = { domain, accountStorage: storage, keyType, enableImage, enableAudio, enableVideo, pushSupported, enableIce, iceUrl, iceUsername, icePassword };
await setNodeConfig(app.state.adminToken, config);
updateState({ busy: false, showSettings: false });
}
@ -119,9 +135,9 @@ export function useDashboard() {
const syncConfig = async () => {
try {
const config = await getNodeConfig(app.state.adminToken);
const { storage, domain, keyType, pushSupported, enableImage, enableAudio, enableVideo } = config;
const { storage, domain, keyType, pushSupported, enableImage, enableAudio, enableVideo, enableIce, iceUrl, iceUsername, icePassword } = config;
const accountStorage = Math.ceil(storage / 1073741824);
updateState({ configError: false, domain, accountStorage, keyType, enableImage, enableAudio, enableVideo, pushSupported });
updateState({ configError: false, domain, accountStorage, keyType, enableImage, enableAudio, enableVideo, pushSupported, enableIce, iceUrl, iceUsername, icePassword });
}
catch(err) {
console.log(err);

View File

@ -72,7 +72,7 @@ export function Cards({ closeCards, openContact, openChannel, openListing }) {
{ state.cards.length > 0 && (
<List local={{ emptyText: '' }} itemLayout="horizontal" dataSource={state.cards} gutter="0"
renderItem={item => (
<CardItem item={item} tooltip={state.tooltip} resync={() => actions.resync(item.cardId)}
<CardItem item={item} enableIce={state.enableIce} tooltip={state.tooltip} resync={() => actions.resync(item.cardId)}
open={() => openContact(item.guid)} message={() => message(item.cardId)}
call={() => call(item)} />
)} />

View File

@ -6,7 +6,7 @@ import { Logo } from 'logo/Logo';
import { Tooltip } from 'antd';
import { MessageOutlined, PhoneOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
export function CardItem({ item, tooltip, resync, open, call, message }) {
export function CardItem({ item, tooltip, enableIce, resync, open, call, message }) {
const onResync = (e) => {
e.stopPropagation();
@ -48,9 +48,11 @@ export function CardItem({ item, tooltip, resync, open, call, message }) {
<Tooltip className="option" placement="left" title="message contact">
<MessageOutlined onClick={onMessage} />
</Tooltip>
<Tooltip className="option" placement="left" title="call contact">
<PhoneOutlined onClick={onCall} />
</Tooltip>
{ enableIce && (
<Tooltip className="option" placement="left" title="call contact">
<PhoneOutlined onClick={onCall} />
</Tooltip>
)}
</ComOptions>
)}
{ item.status === 'connected' && (

View File

@ -3,6 +3,7 @@ import { CardContext } from 'context/CardContext';
import { ViewportContext } from 'context/ViewportContext';
import { StoreContext } from 'context/StoreContext';
import { ChannelContext } from 'context/ChannelContext';
import { AccountContext } from 'context/AccountContext';
import { RingContext } from 'context/RingContext';
export function useCards() {
@ -13,10 +14,12 @@ export function useCards() {
tooltip: false,
sorted: false,
display: 'small',
enableIce: false,
cards: [],
});
const ring = useContext(RingContext);
const account = useContext(AccountContext);
const card = useContext(CardContext);
const channel = useContext(ChannelContext);
const store = useContext(StoreContext);
@ -31,6 +34,11 @@ export function useCards() {
updateState({ display });
}, [viewport.state]);
useEffect(() => {
const { enableIce } = account.state?.status || {};
updateState({ enableIce });
}, [account.state]);
useEffect(() => {
const contacts = Array.from(card.state.cards.values()).map(item => {
const profile = item?.data?.cardProfile;