@7365admin1/layer-common 3.1.4-staging.56 → 3.1.4-staging.58
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/components/HidIntercomManagement.vue +286 -92
- package/components/HidQrCodeConfiguration.vue +195 -31
- package/components/HidUserEnrollment.vue +94 -2
- package/components/SiteSettings.vue +4 -2
- package/composables/useHidAmico.ts +64 -3
- package/composables/useSipWebPhone.ts +105 -9
- package/package.json +1 -1
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/types/site.d.ts +6 -0
|
@@ -234,6 +234,13 @@
|
|
|
234
234
|
<v-radio label="UDP" value="UDP" />
|
|
235
235
|
<v-radio label="TCP" value="TCP" />
|
|
236
236
|
</v-radio-group>
|
|
237
|
+
<div class="sip-switch-row sip-section-switch">
|
|
238
|
+
<div>
|
|
239
|
+
<span>Enable video</span>
|
|
240
|
+
<small>Allow this reader to send and receive video during SIP calls.</small>
|
|
241
|
+
</div>
|
|
242
|
+
<v-switch v-model="sipForm.videoEnabled" :disabled="!sipForm.enabled" color="success" density="compact" hide-details />
|
|
243
|
+
</div>
|
|
237
244
|
</v-card-text>
|
|
238
245
|
<v-card-actions class="dialog-actions pa-0">
|
|
239
246
|
<v-btn class="text-none action-cancel" variant="flat" height="44" @click="sipDialog = false">Cancel</v-btn>
|
|
@@ -386,54 +393,28 @@
|
|
|
386
393
|
<v-card-text class="web-phone-body">
|
|
387
394
|
<div class="web-phone-settings">
|
|
388
395
|
<div class="web-phone-section-title">SIP Account</div>
|
|
389
|
-
<div class="
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
class="call-input"
|
|
407
|
-
:disabled="webPhone.isRegistered.value"
|
|
408
|
-
/>
|
|
409
|
-
<div class="form-two-col">
|
|
410
|
-
<div>
|
|
411
|
-
<div class="call-label">Username <span>*</span></div>
|
|
412
|
-
<v-text-field
|
|
413
|
-
v-model="webPhoneForm.username"
|
|
414
|
-
placeholder="Username"
|
|
415
|
-
variant="outlined"
|
|
416
|
-
density="compact"
|
|
417
|
-
hide-details="auto"
|
|
418
|
-
class="call-input"
|
|
419
|
-
:disabled="webPhone.isRegistered.value"
|
|
420
|
-
/>
|
|
421
|
-
</div>
|
|
396
|
+
<div v-if="sipAccountLoading" class="sip-account-state">
|
|
397
|
+
<v-progress-circular indeterminate color="primary" size="30" width="3" />
|
|
398
|
+
<span>Preparing your private SIP account...</span>
|
|
399
|
+
</div>
|
|
400
|
+
<div v-else-if="sipAccountError" class="sip-account-state is-error">
|
|
401
|
+
<v-icon icon="mdi-alert-circle-outline" size="28" />
|
|
402
|
+
<span>{{ sipAccountError }}</span>
|
|
403
|
+
<v-btn
|
|
404
|
+
class="text-none sip-account-retry"
|
|
405
|
+
variant="outlined"
|
|
406
|
+
prepend-icon="mdi-refresh"
|
|
407
|
+
@click="loadSipAccount"
|
|
408
|
+
>
|
|
409
|
+
Retry
|
|
410
|
+
</v-btn>
|
|
411
|
+
</div>
|
|
412
|
+
<div v-else-if="sipAccount" class="sip-account-summary">
|
|
422
413
|
<div>
|
|
423
|
-
<
|
|
424
|
-
<
|
|
425
|
-
v-model="webPhoneForm.password"
|
|
426
|
-
placeholder="Password"
|
|
427
|
-
:type="showWebPhonePassword ? 'text' : 'password'"
|
|
428
|
-
:append-inner-icon="showWebPhonePassword ? 'mdi-eye-off' : 'mdi-eye'"
|
|
429
|
-
variant="outlined"
|
|
430
|
-
density="compact"
|
|
431
|
-
hide-details="auto"
|
|
432
|
-
class="call-input"
|
|
433
|
-
:disabled="webPhone.isRegistered.value"
|
|
434
|
-
@click:append-inner="showWebPhonePassword = !showWebPhonePassword"
|
|
435
|
-
/>
|
|
414
|
+
<span class="sip-account-label">Assigned extension</span>
|
|
415
|
+
<strong class="sip-account-extension">{{ sipAccount.extension }}</strong>
|
|
436
416
|
</div>
|
|
417
|
+
<span class="sip-account-address">{{ sipAccount.sipAddress }}</span>
|
|
437
418
|
</div>
|
|
438
419
|
<div class="call-toggle-row web-video-toggle">
|
|
439
420
|
<span>Enable video</span>
|
|
@@ -452,6 +433,7 @@
|
|
|
452
433
|
variant="flat"
|
|
453
434
|
prepend-icon="mdi-lan-connect"
|
|
454
435
|
:loading="webPhoneConnecting"
|
|
436
|
+
:disabled="!sipAccount || sipAccountLoading"
|
|
455
437
|
@click="connectWebPhone"
|
|
456
438
|
>
|
|
457
439
|
Connect Web Phone
|
|
@@ -471,14 +453,27 @@
|
|
|
471
453
|
</div>
|
|
472
454
|
|
|
473
455
|
<div class="web-phone-console">
|
|
474
|
-
<div
|
|
456
|
+
<div
|
|
457
|
+
class="web-phone-media"
|
|
458
|
+
:class="{
|
|
459
|
+
'has-video': webPhoneForm.video && webPhone.callState.value === 'active',
|
|
460
|
+
'has-local-video': webPhone.localVideoAvailable.value,
|
|
461
|
+
'has-remote-video': webPhone.remoteVideoAvailable.value,
|
|
462
|
+
}"
|
|
463
|
+
>
|
|
475
464
|
<video ref="remoteVideoEl" autoplay playsinline class="remote-video" />
|
|
476
465
|
<video ref="localVideoEl" autoplay muted playsinline class="local-video" />
|
|
477
466
|
<audio ref="remoteAudioEl" autoplay />
|
|
478
|
-
<div
|
|
467
|
+
<div
|
|
468
|
+
v-if="!webPhoneForm.video || webPhone.callState.value !== 'active' || !webPhone.remoteVideoAvailable.value"
|
|
469
|
+
class="audio-call-state"
|
|
470
|
+
>
|
|
479
471
|
<v-icon size="54">{{ webPhoneCallIcon }}</v-icon>
|
|
480
472
|
<strong>{{ webPhoneCallLabel }}</strong>
|
|
481
473
|
<span v-if="webPhone.currentTarget.value">{{ webPhone.currentTarget.value }}</span>
|
|
474
|
+
<span v-if="webPhoneForm.video && webPhone.callState.value === 'active' && !webPhone.remoteVideoAvailable.value">
|
|
475
|
+
Waiting for video from the other device
|
|
476
|
+
</span>
|
|
482
477
|
</div>
|
|
483
478
|
</div>
|
|
484
479
|
|
|
@@ -532,7 +527,15 @@
|
|
|
532
527
|
variant="outlined"
|
|
533
528
|
:disabled="webPhone.callState.value !== 'active'"
|
|
534
529
|
:title="webPhone.muted.value ? 'Unmute' : 'Mute'"
|
|
535
|
-
@click="
|
|
530
|
+
@click="toggleWebMute"
|
|
531
|
+
/>
|
|
532
|
+
<v-btn
|
|
533
|
+
v-if="webPhoneForm.video"
|
|
534
|
+
:icon="webPhone.cameraEnabled.value ? 'mdi-video' : 'mdi-video-off'"
|
|
535
|
+
variant="outlined"
|
|
536
|
+
:disabled="webPhone.callState.value !== 'active' || !webPhone.localVideoAvailable.value"
|
|
537
|
+
:title="webPhone.cameraEnabled.value ? 'Turn camera off' : 'Turn camera on'"
|
|
538
|
+
@click="toggleWebCamera"
|
|
536
539
|
/>
|
|
537
540
|
<v-btn
|
|
538
541
|
icon="mdi-phone-hangup"
|
|
@@ -613,6 +616,10 @@
|
|
|
613
616
|
|
|
614
617
|
<script setup lang="ts">
|
|
615
618
|
const props = defineProps({
|
|
619
|
+
org: {
|
|
620
|
+
type: String,
|
|
621
|
+
required: true,
|
|
622
|
+
},
|
|
616
623
|
site: {
|
|
617
624
|
type: String,
|
|
618
625
|
required: true,
|
|
@@ -631,6 +638,19 @@ type ContactRow = {
|
|
|
631
638
|
dialCode: string;
|
|
632
639
|
};
|
|
633
640
|
|
|
641
|
+
type SipAccount = {
|
|
642
|
+
orgId: string;
|
|
643
|
+
site: string;
|
|
644
|
+
userId: string;
|
|
645
|
+
extension: string;
|
|
646
|
+
username: string;
|
|
647
|
+
password: string;
|
|
648
|
+
sipAddress: string;
|
|
649
|
+
websocketUrl: string;
|
|
650
|
+
status: "active";
|
|
651
|
+
provisionedAt: string;
|
|
652
|
+
};
|
|
653
|
+
|
|
634
654
|
type DetailItem = {
|
|
635
655
|
label: string;
|
|
636
656
|
value?: string | number | boolean;
|
|
@@ -639,7 +659,7 @@ type DetailItem = {
|
|
|
639
659
|
};
|
|
640
660
|
|
|
641
661
|
type IntercomRow = {
|
|
642
|
-
reader: Record<string,
|
|
662
|
+
reader: Record<string, unknown>;
|
|
643
663
|
name: string;
|
|
644
664
|
location: string;
|
|
645
665
|
serverAddress: string;
|
|
@@ -655,6 +675,7 @@ type IntercomRow = {
|
|
|
655
675
|
password: string;
|
|
656
676
|
operatingMode: string;
|
|
657
677
|
protocol: string;
|
|
678
|
+
videoEnabled: boolean;
|
|
658
679
|
dialMode: string;
|
|
659
680
|
dialModeLabel: string;
|
|
660
681
|
dialCode: string;
|
|
@@ -667,7 +688,7 @@ type IntercomRow = {
|
|
|
667
688
|
externalDialButton: boolean;
|
|
668
689
|
unknownContact: boolean;
|
|
669
690
|
callAfter: string;
|
|
670
|
-
pjsip: Record<string,
|
|
691
|
+
pjsip: Record<string, unknown>;
|
|
671
692
|
};
|
|
672
693
|
|
|
673
694
|
const {
|
|
@@ -678,6 +699,7 @@ const {
|
|
|
678
699
|
getIntercomStatus,
|
|
679
700
|
makeIntercomCall,
|
|
680
701
|
finalizeIntercomCall,
|
|
702
|
+
ensureSipAccount,
|
|
681
703
|
} = useHidAmico();
|
|
682
704
|
|
|
683
705
|
const activeTab = ref<IntercomTab>("intercom");
|
|
@@ -696,9 +718,11 @@ const limit = 20;
|
|
|
696
718
|
const selectedRow = ref<IntercomRow | null>(null);
|
|
697
719
|
const selectedContact = ref<ContactRow | null>(null);
|
|
698
720
|
const showSipPassword = ref(false);
|
|
699
|
-
const showWebPhonePassword = ref(false);
|
|
700
721
|
const webPhoneConnecting = ref(false);
|
|
701
722
|
const webPhoneActionLoading = ref(false);
|
|
723
|
+
const sipAccount = ref<SipAccount | null>(null);
|
|
724
|
+
const sipAccountLoading = ref(false);
|
|
725
|
+
const sipAccountError = ref("");
|
|
702
726
|
const localVideoEl = ref<HTMLVideoElement | null>(null);
|
|
703
727
|
const remoteVideoEl = ref<HTMLVideoElement | null>(null);
|
|
704
728
|
const remoteAudioEl = ref<HTMLAudioElement | null>(null);
|
|
@@ -758,6 +782,7 @@ const sipForm = reactive({
|
|
|
758
782
|
password: "",
|
|
759
783
|
operatingMode: "sip-client",
|
|
760
784
|
protocol: "UDP",
|
|
785
|
+
videoEnabled: false,
|
|
761
786
|
});
|
|
762
787
|
|
|
763
788
|
const callForm = reactive({
|
|
@@ -787,10 +812,6 @@ const makeCallForm = reactive({
|
|
|
787
812
|
});
|
|
788
813
|
|
|
789
814
|
const webPhoneForm = reactive({
|
|
790
|
-
webSocketServer: "",
|
|
791
|
-
aor: "",
|
|
792
|
-
username: "",
|
|
793
|
-
password: "",
|
|
794
815
|
destination: "",
|
|
795
816
|
video: false,
|
|
796
817
|
});
|
|
@@ -872,6 +893,7 @@ const intercomDetailSections = computed(() => {
|
|
|
872
893
|
items: [
|
|
873
894
|
{ label: "Operating Mode", value: row?.operatingMode },
|
|
874
895
|
{ label: "Protocol", value: row?.protocol },
|
|
896
|
+
{ label: "Video", value: row?.videoEnabled ? "Enabled" : "Disabled" },
|
|
875
897
|
],
|
|
876
898
|
},
|
|
877
899
|
{
|
|
@@ -926,8 +948,14 @@ const webPhoneCallIcon = computed(() => {
|
|
|
926
948
|
});
|
|
927
949
|
|
|
928
950
|
watch(
|
|
929
|
-
() => props.site,
|
|
930
|
-
() =>
|
|
951
|
+
[() => props.org, () => props.site],
|
|
952
|
+
() => {
|
|
953
|
+
if (webPhone.isRegistered.value) void webPhone.disconnect();
|
|
954
|
+
sipAccount.value = null;
|
|
955
|
+
sipAccountError.value = "";
|
|
956
|
+
webPhoneForm.destination = "";
|
|
957
|
+
void reload();
|
|
958
|
+
},
|
|
931
959
|
{ immediate: true },
|
|
932
960
|
);
|
|
933
961
|
|
|
@@ -1008,7 +1036,7 @@ function paginate<T>(items: T[]) {
|
|
|
1008
1036
|
return items.slice(start, start + limit);
|
|
1009
1037
|
}
|
|
1010
1038
|
|
|
1011
|
-
function toIntercomRow(reader: Record<string,
|
|
1039
|
+
function toIntercomRow(reader: Record<string, unknown>, pjsip: Record<string, unknown>, status: string): IntercomRow {
|
|
1012
1040
|
const peerToPeer = toFlag(getPjsipValue(pjsip, ["peer_to_peer_enabled", "peerToPeerEnabled"])) === "1";
|
|
1013
1041
|
const dialMode = toText(getPjsipValue(pjsip, ["dialing_display_mode", "dialingDisplayMode"]), "0");
|
|
1014
1042
|
const initialRtpPort = toText(getPjsipValue(pjsip, ["server_outbound_port", "initialRtpPort", "initial_rtp_port"]), "N/A");
|
|
@@ -1022,13 +1050,13 @@ function toIntercomRow(reader: Record<string, any>, pjsip: Record<string, any>,
|
|
|
1022
1050
|
|
|
1023
1051
|
return {
|
|
1024
1052
|
reader,
|
|
1025
|
-
name: reader.name || reader.deviceId
|
|
1026
|
-
location: reader.location
|
|
1053
|
+
name: toText(reader.name || reader.deviceId, "HID Reader"),
|
|
1054
|
+
location: toText(reader.location, "N/A"),
|
|
1027
1055
|
serverAddress: toText(getPjsipValue(pjsip, ["server_ip", "serverAddress", "server_address", "serverIp"]), "N/A"),
|
|
1028
1056
|
serverPort: toText(getPjsipValue(pjsip, ["server_port", "serverPort", "port"]), "N/A"),
|
|
1029
1057
|
initialRtpPort,
|
|
1030
1058
|
finalRtpPort,
|
|
1031
|
-
monitorPath: reader.monitorPath
|
|
1059
|
+
monitorPath: toText(reader.monitorPath, "N/A"),
|
|
1032
1060
|
status,
|
|
1033
1061
|
sipEnabled: toFlag(getPjsipValue(pjsip, ["enabled"])) === "1",
|
|
1034
1062
|
numericExtension: toFlag(getPjsipValue(pjsip, ["numeric_branch_enabled", "numericExtension", "numericExtensionEnabled"])) === "1",
|
|
@@ -1037,6 +1065,7 @@ function toIntercomRow(reader: Record<string, any>, pjsip: Record<string, any>,
|
|
|
1037
1065
|
password: toText(getPjsipValue(pjsip, ["password"]), ""),
|
|
1038
1066
|
operatingMode: peerToPeer ? "Peer-to-Peer" : "SIP Client",
|
|
1039
1067
|
protocol: toText(getPjsipValue(pjsip, ["protocol"]), "UDP"),
|
|
1068
|
+
videoEnabled: toFlag(getPjsipValue(pjsip, ["video_enabled", "videoEnabled"])) === "1",
|
|
1040
1069
|
dialMode,
|
|
1041
1070
|
dialModeLabel: getDialModeLabel(dialMode),
|
|
1042
1071
|
dialCode: toText(getPjsipValue(pjsip, ["open_door_command", "dialCode"]), "N/A"),
|
|
@@ -1053,7 +1082,7 @@ function toIntercomRow(reader: Record<string, any>, pjsip: Record<string, any>,
|
|
|
1053
1082
|
};
|
|
1054
1083
|
}
|
|
1055
1084
|
|
|
1056
|
-
function normalizePjsip(...sources: Record<string,
|
|
1085
|
+
function normalizePjsip(...sources: Record<string, unknown>[]) {
|
|
1057
1086
|
for (const source of sources) {
|
|
1058
1087
|
const candidate = findPjsipCandidate(source);
|
|
1059
1088
|
if (candidate) return candidate;
|
|
@@ -1061,42 +1090,43 @@ function normalizePjsip(...sources: Record<string, any>[]) {
|
|
|
1061
1090
|
return {};
|
|
1062
1091
|
}
|
|
1063
1092
|
|
|
1064
|
-
function findPjsipCandidate(source: Record<string,
|
|
1093
|
+
function findPjsipCandidate(source: Record<string, unknown> | undefined): Record<string, unknown> | undefined {
|
|
1065
1094
|
if (!source || typeof source !== "object") return undefined;
|
|
1066
1095
|
const candidates = [
|
|
1067
1096
|
source.pjsip,
|
|
1068
|
-
source.
|
|
1069
|
-
source.
|
|
1070
|
-
source.result
|
|
1071
|
-
source.
|
|
1072
|
-
source.data
|
|
1073
|
-
source.data
|
|
1074
|
-
source.data
|
|
1075
|
-
source.data
|
|
1076
|
-
source.data
|
|
1077
|
-
source.configuration,
|
|
1078
|
-
source.
|
|
1079
|
-
source.data
|
|
1097
|
+
toRecord(source.pjsip),
|
|
1098
|
+
toRecord(toRecord(source.configuration).pjsip),
|
|
1099
|
+
toRecord(toRecord(source.result).pjsip),
|
|
1100
|
+
toRecord(toRecord(toRecord(source.result).configuration).pjsip),
|
|
1101
|
+
toRecord(toRecord(source.data).pjsip),
|
|
1102
|
+
toRecord(toRecord(toRecord(source.data).configuration).pjsip),
|
|
1103
|
+
toRecord(toRecord(toRecord(source.data).result).pjsip),
|
|
1104
|
+
toRecord(toRecord(toRecord(toRecord(source.data).result).configuration).pjsip),
|
|
1105
|
+
toRecord(toRecord(toRecord(source.data).data).pjsip),
|
|
1106
|
+
toRecord(toRecord(toRecord(toRecord(source.data).data).configuration).pjsip),
|
|
1107
|
+
toRecord(source.configuration),
|
|
1108
|
+
toRecord(toRecord(source.data).configuration),
|
|
1109
|
+
toRecord(toRecord(toRecord(source.data).data).configuration),
|
|
1080
1110
|
source,
|
|
1081
1111
|
];
|
|
1082
1112
|
|
|
1083
1113
|
return candidates.find((candidate) => isPjsipCandidate(candidate));
|
|
1084
1114
|
}
|
|
1085
1115
|
|
|
1086
|
-
function isPjsipCandidate(value: unknown): value is Record<string,
|
|
1116
|
+
function isPjsipCandidate(value: unknown): value is Record<string, unknown> {
|
|
1087
1117
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1088
1118
|
return pjsipKeys.some((key) => Object.prototype.hasOwnProperty.call(value, key)) ||
|
|
1089
1119
|
["serverAddress", "serverIp", "serverPort", "extension", "peerToPeerEnabled"].some((key) => Object.prototype.hasOwnProperty.call(value, key));
|
|
1090
1120
|
}
|
|
1091
1121
|
|
|
1092
|
-
function getPjsipValue(source: Record<string,
|
|
1122
|
+
function getPjsipValue(source: Record<string, unknown>, keys: string[]) {
|
|
1093
1123
|
for (const key of keys) {
|
|
1094
1124
|
if (source?.[key] !== undefined && source?.[key] !== null && source?.[key] !== "") return source[key];
|
|
1095
1125
|
}
|
|
1096
1126
|
return undefined;
|
|
1097
1127
|
}
|
|
1098
1128
|
|
|
1099
|
-
function normalizeStatus(response: Record<string,
|
|
1129
|
+
function normalizeStatus(response: Record<string, unknown>, pjsip: Record<string, unknown>) {
|
|
1100
1130
|
const status = Number(response?.status);
|
|
1101
1131
|
if (toFlag(pjsip.enabled) === "0" || status === -1) return "Disabled";
|
|
1102
1132
|
if (status === 200) return "Connected";
|
|
@@ -1136,6 +1166,7 @@ function openSipDialog(row: IntercomRow) {
|
|
|
1136
1166
|
sipForm.password = row.password;
|
|
1137
1167
|
sipForm.operatingMode = row.operatingMode === "Peer-to-Peer" ? "peer-to-peer" : "sip-client";
|
|
1138
1168
|
sipForm.protocol = row.protocol || "UDP";
|
|
1169
|
+
sipForm.videoEnabled = row.videoEnabled;
|
|
1139
1170
|
sipDialog.value = true;
|
|
1140
1171
|
}
|
|
1141
1172
|
|
|
@@ -1175,19 +1206,46 @@ function openWebPhoneDialog(row?: IntercomRow) {
|
|
|
1175
1206
|
selectedRow.value = activeRow;
|
|
1176
1207
|
if (!webPhone.isRegistered.value && activeRow.extension !== "N/A") {
|
|
1177
1208
|
webPhoneForm.destination = activeRow.extension;
|
|
1209
|
+
webPhoneForm.video = activeRow.videoEnabled;
|
|
1178
1210
|
}
|
|
1179
1211
|
}
|
|
1180
1212
|
webPhoneDialog.value = true;
|
|
1213
|
+
if (!sipAccount.value && !sipAccountLoading.value) void loadSipAccount();
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
async function loadSipAccount() {
|
|
1217
|
+
if (!props.org || !props.site) return;
|
|
1218
|
+
|
|
1219
|
+
sipAccountLoading.value = true;
|
|
1220
|
+
sipAccountError.value = "";
|
|
1221
|
+
try {
|
|
1222
|
+
const response = await ensureSipAccount(props.site, props.org);
|
|
1223
|
+
sipAccount.value = normalizeSipAccount(response);
|
|
1224
|
+
} catch (error: unknown) {
|
|
1225
|
+
sipAccount.value = null;
|
|
1226
|
+
sipAccountError.value = getErrorMessage(error);
|
|
1227
|
+
} finally {
|
|
1228
|
+
sipAccountLoading.value = false;
|
|
1229
|
+
}
|
|
1181
1230
|
}
|
|
1182
1231
|
|
|
1183
1232
|
async function connectWebPhone() {
|
|
1233
|
+
const account = sipAccount.value;
|
|
1234
|
+
if (!account) {
|
|
1235
|
+
await loadSipAccount();
|
|
1236
|
+
if (!sipAccount.value) return;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const activeAccount = sipAccount.value;
|
|
1240
|
+
if (!activeAccount) return;
|
|
1241
|
+
|
|
1184
1242
|
webPhoneConnecting.value = true;
|
|
1185
1243
|
try {
|
|
1186
1244
|
await webPhone.connect({
|
|
1187
|
-
webSocketServer:
|
|
1188
|
-
aor:
|
|
1189
|
-
authorizationUsername:
|
|
1190
|
-
authorizationPassword:
|
|
1245
|
+
webSocketServer: activeAccount.websocketUrl,
|
|
1246
|
+
aor: activeAccount.sipAddress,
|
|
1247
|
+
authorizationUsername: activeAccount.username,
|
|
1248
|
+
authorizationPassword: activeAccount.password,
|
|
1191
1249
|
displayName: "iService365 Web",
|
|
1192
1250
|
video: webPhoneForm.video,
|
|
1193
1251
|
}, {
|
|
@@ -1196,7 +1254,7 @@ async function connectWebPhone() {
|
|
|
1196
1254
|
remoteAudio: remoteAudioEl.value,
|
|
1197
1255
|
});
|
|
1198
1256
|
showMessage("Web phone connected and ready to receive calls.", "success");
|
|
1199
|
-
} catch (error:
|
|
1257
|
+
} catch (error: unknown) {
|
|
1200
1258
|
showMessage(getErrorMessage(error), "error");
|
|
1201
1259
|
} finally {
|
|
1202
1260
|
webPhoneConnecting.value = false;
|
|
@@ -1217,7 +1275,7 @@ async function startWebCall() {
|
|
|
1217
1275
|
webPhoneActionLoading.value = true;
|
|
1218
1276
|
try {
|
|
1219
1277
|
await webPhone.call(webPhoneForm.destination);
|
|
1220
|
-
} catch (error:
|
|
1278
|
+
} catch (error: unknown) {
|
|
1221
1279
|
showMessage(getErrorMessage(error), "error");
|
|
1222
1280
|
} finally {
|
|
1223
1281
|
webPhoneActionLoading.value = false;
|
|
@@ -1228,7 +1286,7 @@ async function answerWebCall() {
|
|
|
1228
1286
|
webPhoneActionLoading.value = true;
|
|
1229
1287
|
try {
|
|
1230
1288
|
await webPhone.answer();
|
|
1231
|
-
} catch (error:
|
|
1289
|
+
} catch (error: unknown) {
|
|
1232
1290
|
showMessage(getErrorMessage(error), "error");
|
|
1233
1291
|
} finally {
|
|
1234
1292
|
webPhoneActionLoading.value = false;
|
|
@@ -1239,7 +1297,7 @@ async function declineWebCall() {
|
|
|
1239
1297
|
webPhoneActionLoading.value = true;
|
|
1240
1298
|
try {
|
|
1241
1299
|
await webPhone.decline();
|
|
1242
|
-
} catch (error:
|
|
1300
|
+
} catch (error: unknown) {
|
|
1243
1301
|
showMessage(getErrorMessage(error), "error");
|
|
1244
1302
|
} finally {
|
|
1245
1303
|
webPhoneActionLoading.value = false;
|
|
@@ -1250,13 +1308,29 @@ async function hangupWebCall() {
|
|
|
1250
1308
|
webPhoneActionLoading.value = true;
|
|
1251
1309
|
try {
|
|
1252
1310
|
await webPhone.hangup();
|
|
1253
|
-
} catch (error:
|
|
1311
|
+
} catch (error: unknown) {
|
|
1254
1312
|
showMessage(getErrorMessage(error), "error");
|
|
1255
1313
|
} finally {
|
|
1256
1314
|
webPhoneActionLoading.value = false;
|
|
1257
1315
|
}
|
|
1258
1316
|
}
|
|
1259
1317
|
|
|
1318
|
+
function toggleWebCamera() {
|
|
1319
|
+
try {
|
|
1320
|
+
webPhone.toggleCamera();
|
|
1321
|
+
} catch (error: unknown) {
|
|
1322
|
+
showMessage(getErrorMessage(error), "error");
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function toggleWebMute() {
|
|
1327
|
+
try {
|
|
1328
|
+
webPhone.toggleMute();
|
|
1329
|
+
} catch (error: unknown) {
|
|
1330
|
+
showMessage(getErrorMessage(error), "error");
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1260
1334
|
function openContactDialog(contact?: ContactRow) {
|
|
1261
1335
|
selectedContact.value = contact || null;
|
|
1262
1336
|
contactForm.id = contact?.id;
|
|
@@ -1305,6 +1379,7 @@ async function saveSipSettings() {
|
|
|
1305
1379
|
login: isPeerToPeer ? "" : sipForm.login,
|
|
1306
1380
|
password: isPeerToPeer ? "" : sipForm.password,
|
|
1307
1381
|
peer_to_peer_enabled: isPeerToPeer ? "1" : "0",
|
|
1382
|
+
video_enabled: sipForm.videoEnabled ? "1" : "0",
|
|
1308
1383
|
});
|
|
1309
1384
|
if (saved) {
|
|
1310
1385
|
sipDialog.value = false;
|
|
@@ -1492,8 +1567,42 @@ function showMessage(text: string, type: MessageType = "info") {
|
|
|
1492
1567
|
message.type = type;
|
|
1493
1568
|
}
|
|
1494
1569
|
|
|
1495
|
-
function
|
|
1496
|
-
|
|
1570
|
+
function normalizeSipAccount(response: unknown): SipAccount {
|
|
1571
|
+
const responseRecord = toRecord(response);
|
|
1572
|
+
const payload = toRecord(responseRecord.data || responseRecord);
|
|
1573
|
+
const requiredFields = ["extension", "username", "password", "sipAddress", "websocketUrl"] as const;
|
|
1574
|
+
|
|
1575
|
+
if (requiredFields.some((field) => !toText(payload[field]))) {
|
|
1576
|
+
throw new Error("The SIP account response is incomplete. Please try again.");
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
return {
|
|
1580
|
+
orgId: toText(payload.orgId, props.org),
|
|
1581
|
+
site: toText(payload.site, props.site),
|
|
1582
|
+
userId: toText(payload.userId),
|
|
1583
|
+
extension: toText(payload.extension),
|
|
1584
|
+
username: toText(payload.username),
|
|
1585
|
+
password: toText(payload.password),
|
|
1586
|
+
sipAddress: toText(payload.sipAddress),
|
|
1587
|
+
websocketUrl: toText(payload.websocketUrl),
|
|
1588
|
+
status: "active",
|
|
1589
|
+
provisionedAt: toText(payload.provisionedAt),
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
function getErrorMessage(error: unknown) {
|
|
1594
|
+
const errorRecord = toRecord(error);
|
|
1595
|
+
const data = toRecord(errorRecord.data);
|
|
1596
|
+
const response = toRecord(errorRecord.response);
|
|
1597
|
+
const responseData = toRecord(response._data);
|
|
1598
|
+
return toText(data.message)
|
|
1599
|
+
|| toText(responseData.message)
|
|
1600
|
+
|| toText(errorRecord.message)
|
|
1601
|
+
|| "HID intercom request failed.";
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
function toRecord(value: unknown): Record<string, unknown> {
|
|
1605
|
+
return typeof value === "object" && value !== null ? value as Record<string, unknown> : {};
|
|
1497
1606
|
}
|
|
1498
1607
|
|
|
1499
1608
|
function toText(value: unknown, fallback = "") {
|
|
@@ -1795,6 +1904,20 @@ export default {
|
|
|
1795
1904
|
margin-bottom: 12px;
|
|
1796
1905
|
}
|
|
1797
1906
|
|
|
1907
|
+
.sip-section-switch > div {
|
|
1908
|
+
display: flex;
|
|
1909
|
+
min-width: 0;
|
|
1910
|
+
padding: 8px 12px 8px 0;
|
|
1911
|
+
flex-direction: column;
|
|
1912
|
+
gap: 2px;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
.sip-section-switch small {
|
|
1916
|
+
color: #667085;
|
|
1917
|
+
font-size: 10px;
|
|
1918
|
+
line-height: 1.4;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1798
1921
|
.sip-switch-row :deep(.v-switch) {
|
|
1799
1922
|
flex: 0 0 auto;
|
|
1800
1923
|
}
|
|
@@ -2226,6 +2349,73 @@ export default {
|
|
|
2226
2349
|
font-weight: 700;
|
|
2227
2350
|
}
|
|
2228
2351
|
|
|
2352
|
+
.sip-account-state {
|
|
2353
|
+
display: flex;
|
|
2354
|
+
min-height: 126px;
|
|
2355
|
+
align-items: center;
|
|
2356
|
+
justify-content: center;
|
|
2357
|
+
padding: 18px;
|
|
2358
|
+
border: 1px solid #dce2e8;
|
|
2359
|
+
border-radius: 6px;
|
|
2360
|
+
color: #5d6673;
|
|
2361
|
+
flex-direction: column;
|
|
2362
|
+
font-size: 12px;
|
|
2363
|
+
gap: 12px;
|
|
2364
|
+
text-align: center;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
.sip-account-state.is-error {
|
|
2368
|
+
border-color: #f0c7c7;
|
|
2369
|
+
background: #fff8f8;
|
|
2370
|
+
color: #a92222;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
.sip-account-retry {
|
|
2374
|
+
min-width: 104px;
|
|
2375
|
+
border-color: #aeb8c2;
|
|
2376
|
+
color: #344054;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
.sip-account-summary {
|
|
2380
|
+
display: grid;
|
|
2381
|
+
min-height: 96px;
|
|
2382
|
+
align-items: center;
|
|
2383
|
+
padding: 16px;
|
|
2384
|
+
border: 1px solid #dce2e8;
|
|
2385
|
+
border-radius: 6px;
|
|
2386
|
+
background: #f8fafc;
|
|
2387
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
2388
|
+
gap: 18px;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
.sip-account-summary > div {
|
|
2392
|
+
display: flex;
|
|
2393
|
+
min-width: 0;
|
|
2394
|
+
flex-direction: column;
|
|
2395
|
+
gap: 4px;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
.sip-account-label {
|
|
2399
|
+
color: #667085;
|
|
2400
|
+
font-size: 11px;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
.sip-account-extension {
|
|
2404
|
+
color: #0d1720;
|
|
2405
|
+
font-size: 24px;
|
|
2406
|
+
line-height: 1.1;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
.sip-account-address {
|
|
2410
|
+
max-width: 170px;
|
|
2411
|
+
overflow: hidden;
|
|
2412
|
+
color: #536273;
|
|
2413
|
+
font-size: 11px;
|
|
2414
|
+
text-align: right;
|
|
2415
|
+
text-overflow: ellipsis;
|
|
2416
|
+
white-space: nowrap;
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2229
2419
|
.web-video-toggle {
|
|
2230
2420
|
margin-top: 12px;
|
|
2231
2421
|
}
|
|
@@ -2268,7 +2458,7 @@ export default {
|
|
|
2268
2458
|
object-fit: cover;
|
|
2269
2459
|
}
|
|
2270
2460
|
|
|
2271
|
-
.web-phone-media.has-video .remote-video {
|
|
2461
|
+
.web-phone-media.has-remote-video .remote-video {
|
|
2272
2462
|
display: block;
|
|
2273
2463
|
}
|
|
2274
2464
|
|
|
@@ -2285,10 +2475,14 @@ export default {
|
|
|
2285
2475
|
object-fit: cover;
|
|
2286
2476
|
}
|
|
2287
2477
|
|
|
2288
|
-
.web-phone-media.has-video .local-video {
|
|
2478
|
+
.web-phone-media.has-local-video .local-video {
|
|
2289
2479
|
display: block;
|
|
2290
2480
|
}
|
|
2291
2481
|
|
|
2482
|
+
.web-phone-media.has-remote-video .audio-call-state {
|
|
2483
|
+
display: none;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2292
2486
|
.audio-call-state {
|
|
2293
2487
|
display: flex;
|
|
2294
2488
|
align-items: center;
|