@coventia/softphone 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{call-DUVnDxsz.d.cts → call-VKq1I8yP.d.cts} +6 -0
- package/dist/{call-DUVnDxsz.d.ts → call-VKq1I8yP.d.ts} +6 -0
- package/dist/{chunk-YWI6EQYS.js → chunk-EYEDPD7Y.js} +108 -15
- package/dist/chunk-EYEDPD7Y.js.map +1 -0
- package/dist/element/index.d.ts +8 -2
- package/dist/element/index.js +334 -249
- package/dist/element/index.js.map +1 -1
- package/dist/{engine-CDjkrXHY.d.cts → engine-BlQBo2Ys.d.cts} +1 -1
- package/dist/{engine-EPCKf-Bl.d.ts → engine-C0BOkIcW.d.ts} +1 -1
- package/dist/index.cjs +199 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +90 -11
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/{softphone-BtdgZWkm.d.ts → softphone-Cyq3hiIM.d.ts} +1 -1
- package/dist/{softphone-DHG9MTNg.d.cts → softphone-DlGV12Xp.d.cts} +1 -1
- package/dist/softphone.css +82 -34
- package/dist/{sources-5L2R4HTV.js → sources-IRKNQPWW.js} +6 -2
- package/dist/sources-IRKNQPWW.js.map +1 -0
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/ui/index.cjs +332 -247
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +17 -6
- package/dist/ui/index.d.ts +17 -6
- package/dist/ui/index.js +332 -247
- package/dist/ui/index.js.map +1 -1
- package/dist/zadarma/index.cjs +106 -13
- package/dist/zadarma/index.cjs.map +1 -1
- package/dist/zadarma/index.d.cts +2 -2
- package/dist/zadarma/index.d.ts +2 -2
- package/dist/zadarma/index.js +1 -1
- package/dist/zadarma-engine-L5DEFXUS.js +5 -0
- package/dist/{zadarma-engine-4VOPIBXG.js.map → zadarma-engine-L5DEFXUS.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-YWI6EQYS.js.map +0 -1
- package/dist/sources-5L2R4HTV.js.map +0 -1
- package/dist/zadarma-engine-4VOPIBXG.js +0 -5
package/dist/ui/index.cjs
CHANGED
|
@@ -13721,21 +13721,263 @@ function caretAfterDigits(formatted, digitsBefore) {
|
|
|
13721
13721
|
function digitsBeforeCaret(text, caret) {
|
|
13722
13722
|
return (text.slice(0, caret).match(/\d/g) ?? []).length;
|
|
13723
13723
|
}
|
|
13724
|
-
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
["
|
|
13728
|
-
["
|
|
13729
|
-
["
|
|
13730
|
-
["
|
|
13731
|
-
["
|
|
13732
|
-
["
|
|
13733
|
-
["
|
|
13734
|
-
["
|
|
13735
|
-
["
|
|
13736
|
-
["
|
|
13724
|
+
|
|
13725
|
+
// src/ui/dialing/keys.ts
|
|
13726
|
+
var KEYPAD_KEYS = [
|
|
13727
|
+
["1", ""],
|
|
13728
|
+
["2", "ABC"],
|
|
13729
|
+
["3", "DEF"],
|
|
13730
|
+
["4", "GHI"],
|
|
13731
|
+
["5", "JKL"],
|
|
13732
|
+
["6", "MNO"],
|
|
13733
|
+
["7", "PQRS"],
|
|
13734
|
+
["8", "TUV"],
|
|
13735
|
+
["9", "WXYZ"],
|
|
13736
|
+
["*", ""],
|
|
13737
|
+
["0", "+"],
|
|
13738
|
+
["#", ""]
|
|
13737
13739
|
];
|
|
13738
|
-
|
|
13740
|
+
|
|
13741
|
+
// src/ui/audio/audio.ts
|
|
13742
|
+
var TONE_HZ = 425;
|
|
13743
|
+
function createRemoteAudio() {
|
|
13744
|
+
const el = document.createElement("audio");
|
|
13745
|
+
el.autoplay = true;
|
|
13746
|
+
el.style.display = "none";
|
|
13747
|
+
document.body.appendChild(el);
|
|
13748
|
+
return {
|
|
13749
|
+
attach(stream) {
|
|
13750
|
+
el.srcObject = stream;
|
|
13751
|
+
void el.play().catch(() => {
|
|
13752
|
+
});
|
|
13753
|
+
},
|
|
13754
|
+
detach() {
|
|
13755
|
+
el.srcObject = null;
|
|
13756
|
+
},
|
|
13757
|
+
destroy() {
|
|
13758
|
+
el.srcObject = null;
|
|
13759
|
+
el.remove();
|
|
13760
|
+
},
|
|
13761
|
+
async setOutputDevice(deviceId) {
|
|
13762
|
+
if (typeof el.setSinkId !== "function") return;
|
|
13763
|
+
try {
|
|
13764
|
+
await el.setSinkId(deviceId ?? "");
|
|
13765
|
+
} catch {
|
|
13766
|
+
}
|
|
13767
|
+
}
|
|
13768
|
+
};
|
|
13769
|
+
}
|
|
13770
|
+
var shared = null;
|
|
13771
|
+
function sharedContext() {
|
|
13772
|
+
if (shared) return shared;
|
|
13773
|
+
const Ctor = typeof AudioContext !== "undefined" ? AudioContext : globalThis.webkitAudioContext;
|
|
13774
|
+
if (!Ctor) return null;
|
|
13775
|
+
shared = new Ctor();
|
|
13776
|
+
return shared;
|
|
13777
|
+
}
|
|
13778
|
+
function primeAudio() {
|
|
13779
|
+
const ctx = sharedContext();
|
|
13780
|
+
if (ctx && ctx.state === "suspended") void ctx.resume?.().catch(() => {
|
|
13781
|
+
});
|
|
13782
|
+
}
|
|
13783
|
+
function playRingtone() {
|
|
13784
|
+
const ctx = sharedContext();
|
|
13785
|
+
if (!ctx) return { stop() {
|
|
13786
|
+
} };
|
|
13787
|
+
const gain = ctx.createGain();
|
|
13788
|
+
gain.gain.value = 0;
|
|
13789
|
+
gain.connect(ctx.destination);
|
|
13790
|
+
const osc = ctx.createOscillator();
|
|
13791
|
+
osc.type = "sine";
|
|
13792
|
+
osc.frequency.value = TONE_HZ;
|
|
13793
|
+
osc.connect(gain);
|
|
13794
|
+
osc.start();
|
|
13795
|
+
const LEVEL = 0.8;
|
|
13796
|
+
const RAMP = 0.02;
|
|
13797
|
+
const burst = (at, dur) => {
|
|
13798
|
+
gain.gain.setValueAtTime(0, at);
|
|
13799
|
+
gain.gain.linearRampToValueAtTime(LEVEL, at + RAMP);
|
|
13800
|
+
gain.gain.setValueAtTime(LEVEL, at + dur - RAMP);
|
|
13801
|
+
gain.gain.linearRampToValueAtTime(0, at + dur);
|
|
13802
|
+
};
|
|
13803
|
+
const scheduleCycle = (at) => {
|
|
13804
|
+
burst(at, 0.4);
|
|
13805
|
+
burst(at + 0.6, 0.4);
|
|
13806
|
+
return 3;
|
|
13807
|
+
};
|
|
13808
|
+
const LOOKAHEAD = 0.5;
|
|
13809
|
+
const TICK_MS = 150;
|
|
13810
|
+
const LEAD = 0.08;
|
|
13811
|
+
let stopped = false;
|
|
13812
|
+
let timer = null;
|
|
13813
|
+
let nextAt = 0;
|
|
13814
|
+
const pump = () => {
|
|
13815
|
+
while (nextAt < ctx.currentTime + LOOKAHEAD) nextAt += scheduleCycle(nextAt);
|
|
13816
|
+
};
|
|
13817
|
+
void Promise.resolve(ctx.resume?.()).catch(() => {
|
|
13818
|
+
}).finally(() => {
|
|
13819
|
+
if (stopped) return;
|
|
13820
|
+
nextAt = ctx.currentTime + LEAD;
|
|
13821
|
+
pump();
|
|
13822
|
+
timer = setInterval(pump, TICK_MS);
|
|
13823
|
+
});
|
|
13824
|
+
return {
|
|
13825
|
+
stop() {
|
|
13826
|
+
stopped = true;
|
|
13827
|
+
if (timer) clearInterval(timer);
|
|
13828
|
+
try {
|
|
13829
|
+
osc.stop();
|
|
13830
|
+
} catch {
|
|
13831
|
+
}
|
|
13832
|
+
try {
|
|
13833
|
+
gain.disconnect();
|
|
13834
|
+
} catch {
|
|
13835
|
+
}
|
|
13836
|
+
}
|
|
13837
|
+
};
|
|
13838
|
+
}
|
|
13839
|
+
function playCallEndedTone() {
|
|
13840
|
+
const ctx = sharedContext();
|
|
13841
|
+
if (!ctx) return;
|
|
13842
|
+
const gain = ctx.createGain();
|
|
13843
|
+
gain.gain.value = 0;
|
|
13844
|
+
gain.connect(ctx.destination);
|
|
13845
|
+
const osc = ctx.createOscillator();
|
|
13846
|
+
osc.type = "sine";
|
|
13847
|
+
osc.frequency.value = TONE_HZ;
|
|
13848
|
+
osc.connect(gain);
|
|
13849
|
+
osc.start();
|
|
13850
|
+
const LEVEL = 0.55;
|
|
13851
|
+
const RAMP = 8e-3;
|
|
13852
|
+
const BEEP = 0.25;
|
|
13853
|
+
void Promise.resolve(ctx.resume?.()).catch(() => {
|
|
13854
|
+
}).finally(() => {
|
|
13855
|
+
const at = ctx.currentTime + 0.02;
|
|
13856
|
+
gain.gain.setValueAtTime(0, at);
|
|
13857
|
+
gain.gain.linearRampToValueAtTime(LEVEL, at + RAMP);
|
|
13858
|
+
gain.gain.setValueAtTime(LEVEL, at + BEEP - RAMP);
|
|
13859
|
+
gain.gain.linearRampToValueAtTime(0, at + BEEP);
|
|
13860
|
+
const totalMs = (at + BEEP - ctx.currentTime) * 1e3;
|
|
13861
|
+
setTimeout(() => {
|
|
13862
|
+
try {
|
|
13863
|
+
osc.stop();
|
|
13864
|
+
} catch {
|
|
13865
|
+
}
|
|
13866
|
+
try {
|
|
13867
|
+
gain.disconnect();
|
|
13868
|
+
} catch {
|
|
13869
|
+
}
|
|
13870
|
+
}, totalMs);
|
|
13871
|
+
});
|
|
13872
|
+
}
|
|
13873
|
+
var DTMF_ROWS_HZ = [697, 770, 852, 941];
|
|
13874
|
+
var DTMF_COLS_HZ = [1209, 1336, 1477];
|
|
13875
|
+
var DTMF_LAYOUT = ["123", "456", "789", "*0#"];
|
|
13876
|
+
function dtmfFrequencies(key) {
|
|
13877
|
+
if (key.length !== 1) return null;
|
|
13878
|
+
for (let row = 0; row < DTMF_LAYOUT.length; row++) {
|
|
13879
|
+
const col = DTMF_LAYOUT[row].indexOf(key);
|
|
13880
|
+
if (col !== -1) return [DTMF_ROWS_HZ[row], DTMF_COLS_HZ[col]];
|
|
13881
|
+
}
|
|
13882
|
+
return null;
|
|
13883
|
+
}
|
|
13884
|
+
function playKeyTone(key) {
|
|
13885
|
+
const hz = dtmfFrequencies(key);
|
|
13886
|
+
if (!hz) return;
|
|
13887
|
+
const ctx = sharedContext();
|
|
13888
|
+
if (!ctx) return;
|
|
13889
|
+
const LEVEL = 0.2;
|
|
13890
|
+
const RAMP = 6e-3;
|
|
13891
|
+
const DURATION = 0.12;
|
|
13892
|
+
const LEAD = 0.01;
|
|
13893
|
+
const gain = ctx.createGain();
|
|
13894
|
+
gain.gain.value = 0;
|
|
13895
|
+
gain.connect(ctx.destination);
|
|
13896
|
+
const oscillators = hz.map((freq) => {
|
|
13897
|
+
const osc = ctx.createOscillator();
|
|
13898
|
+
osc.type = "sine";
|
|
13899
|
+
osc.frequency.value = freq;
|
|
13900
|
+
osc.connect(gain);
|
|
13901
|
+
osc.start();
|
|
13902
|
+
return osc;
|
|
13903
|
+
});
|
|
13904
|
+
const fire = () => {
|
|
13905
|
+
const at = ctx.currentTime + LEAD;
|
|
13906
|
+
gain.gain.setValueAtTime(0, at);
|
|
13907
|
+
gain.gain.linearRampToValueAtTime(LEVEL, at + RAMP);
|
|
13908
|
+
gain.gain.setValueAtTime(LEVEL, at + DURATION - RAMP);
|
|
13909
|
+
gain.gain.linearRampToValueAtTime(0, at + DURATION);
|
|
13910
|
+
for (const osc of oscillators) osc.stop(at + DURATION);
|
|
13911
|
+
oscillators[0].onended = () => gain.disconnect();
|
|
13912
|
+
};
|
|
13913
|
+
if (ctx.state === "running") fire();
|
|
13914
|
+
else void Promise.resolve(ctx.resume?.()).catch(() => {
|
|
13915
|
+
}).finally(fire);
|
|
13916
|
+
}
|
|
13917
|
+
var ringbackUrl = null;
|
|
13918
|
+
function ringbackSource() {
|
|
13919
|
+
if (ringbackUrl) return ringbackUrl;
|
|
13920
|
+
const sampleRate = 48e3;
|
|
13921
|
+
const channels = 2;
|
|
13922
|
+
const bytesPerSample = 2;
|
|
13923
|
+
const amp = 0.5;
|
|
13924
|
+
const toneSec = 1.5;
|
|
13925
|
+
const silenceSec = 3;
|
|
13926
|
+
const totalFrames = Math.round((toneSec + silenceSec) * sampleRate);
|
|
13927
|
+
const toneFrames = Math.round(toneSec * sampleRate);
|
|
13928
|
+
const ramp = Math.round(0.02 * sampleRate);
|
|
13929
|
+
const blockAlign = channels * bytesPerSample;
|
|
13930
|
+
const dataSize = totalFrames * blockAlign;
|
|
13931
|
+
const buffer = new ArrayBuffer(44 + dataSize);
|
|
13932
|
+
const view = new DataView(buffer);
|
|
13933
|
+
const str = (off, s) => {
|
|
13934
|
+
for (let i = 0; i < s.length; i++) view.setUint8(off + i, s.charCodeAt(i));
|
|
13935
|
+
};
|
|
13936
|
+
str(0, "RIFF");
|
|
13937
|
+
view.setUint32(4, 36 + dataSize, true);
|
|
13938
|
+
str(8, "WAVE");
|
|
13939
|
+
str(12, "fmt ");
|
|
13940
|
+
view.setUint32(16, 16, true);
|
|
13941
|
+
view.setUint16(20, 1, true);
|
|
13942
|
+
view.setUint16(22, channels, true);
|
|
13943
|
+
view.setUint32(24, sampleRate, true);
|
|
13944
|
+
view.setUint32(28, sampleRate * blockAlign, true);
|
|
13945
|
+
view.setUint16(32, blockAlign, true);
|
|
13946
|
+
view.setUint16(34, bytesPerSample * 8, true);
|
|
13947
|
+
str(36, "data");
|
|
13948
|
+
view.setUint32(40, dataSize, true);
|
|
13949
|
+
for (let i = 0; i < totalFrames; i++) {
|
|
13950
|
+
let sample = 0;
|
|
13951
|
+
if (i < toneFrames) {
|
|
13952
|
+
let env = 1;
|
|
13953
|
+
if (i < ramp) env = i / ramp;
|
|
13954
|
+
else if (i > toneFrames - ramp) env = Math.max(0, (toneFrames - i) / ramp);
|
|
13955
|
+
sample = Math.sin(2 * Math.PI * TONE_HZ * (i / sampleRate)) * amp * env;
|
|
13956
|
+
}
|
|
13957
|
+
const s16 = Math.round(sample * 32767);
|
|
13958
|
+
const off = 44 + i * blockAlign;
|
|
13959
|
+
for (let c = 0; c < channels; c++) view.setInt16(off + c * bytesPerSample, s16, true);
|
|
13960
|
+
}
|
|
13961
|
+
ringbackUrl = URL.createObjectURL(new Blob([buffer], { type: "audio/wav" }));
|
|
13962
|
+
return ringbackUrl;
|
|
13963
|
+
}
|
|
13964
|
+
function playRingback() {
|
|
13965
|
+
const el = document.createElement("audio");
|
|
13966
|
+
el.src = ringbackSource();
|
|
13967
|
+
el.loop = true;
|
|
13968
|
+
el.style.display = "none";
|
|
13969
|
+
document.body.appendChild(el);
|
|
13970
|
+
void el.play().catch(() => {
|
|
13971
|
+
});
|
|
13972
|
+
return {
|
|
13973
|
+
stop() {
|
|
13974
|
+
el.pause();
|
|
13975
|
+
el.remove();
|
|
13976
|
+
}
|
|
13977
|
+
};
|
|
13978
|
+
}
|
|
13979
|
+
var NOT_DIALABLE = /* @__PURE__ */ new Set(["*", "#"]);
|
|
13980
|
+
function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
13739
13981
|
const softphone = useSoftphone();
|
|
13740
13982
|
const state = useSoftphoneState();
|
|
13741
13983
|
const [value, setValue] = React.useState("");
|
|
@@ -13756,6 +13998,7 @@ function Dialpad({ outcome = null, onIntent } = {}) {
|
|
|
13756
13998
|
const append = (key) => {
|
|
13757
13999
|
onIntent?.();
|
|
13758
14000
|
setValue((v) => v + key);
|
|
14001
|
+
if (keyTones) playKeyTone(key);
|
|
13759
14002
|
};
|
|
13760
14003
|
const backspace = () => {
|
|
13761
14004
|
onIntent?.();
|
|
@@ -13800,22 +14043,25 @@ function Dialpad({ outcome = null, onIntent } = {}) {
|
|
|
13800
14043
|
)
|
|
13801
14044
|
] }),
|
|
13802
14045
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-dial-outcome", role: "status", part: "error", "data-shown": outcome !== null, children: outcome?.text ?? "" }),
|
|
13803
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-keypad", children:
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
13818
|
-
|
|
14046
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-keypad", children: KEYPAD_KEYS.map(([key, letters]) => {
|
|
14047
|
+
const dialable = !NOT_DIALABLE.has(key);
|
|
14048
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14049
|
+
"button",
|
|
14050
|
+
{
|
|
14051
|
+
type: "button",
|
|
14052
|
+
className: "cv-key",
|
|
14053
|
+
"data-dialable": dialable,
|
|
14054
|
+
onClick: () => dialable && append(key),
|
|
14055
|
+
"aria-disabled": !dialable,
|
|
14056
|
+
title: dialable ? void 0 : "Solo disponible durante una llamada",
|
|
14057
|
+
children: [
|
|
14058
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: key }),
|
|
14059
|
+
letters ? /* @__PURE__ */ jsxRuntime.jsx("small", { children: letters }) : null
|
|
14060
|
+
]
|
|
14061
|
+
},
|
|
14062
|
+
key
|
|
14063
|
+
);
|
|
14064
|
+
}) }),
|
|
13819
14065
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-dial-foot", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
13820
14066
|
"button",
|
|
13821
14067
|
{
|
|
@@ -14055,7 +14301,9 @@ function Control({
|
|
|
14055
14301
|
label,
|
|
14056
14302
|
pressed = false,
|
|
14057
14303
|
tone,
|
|
14058
|
-
onClick
|
|
14304
|
+
onClick,
|
|
14305
|
+
disabled = false,
|
|
14306
|
+
title
|
|
14059
14307
|
}) {
|
|
14060
14308
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-ctrl-item", children: [
|
|
14061
14309
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -14067,6 +14315,8 @@ function Control({
|
|
|
14067
14315
|
"aria-pressed": pressed,
|
|
14068
14316
|
"aria-label": label,
|
|
14069
14317
|
onClick,
|
|
14318
|
+
disabled,
|
|
14319
|
+
title,
|
|
14070
14320
|
children: /* @__PURE__ */ jsxRuntime.jsx(Icon3, { size: 19 })
|
|
14071
14321
|
}
|
|
14072
14322
|
),
|
|
@@ -14205,18 +14455,22 @@ function TransferPicker({
|
|
|
14205
14455
|
}
|
|
14206
14456
|
function CallScreen({
|
|
14207
14457
|
call,
|
|
14208
|
-
incomingCallAction
|
|
14458
|
+
incomingCallAction,
|
|
14459
|
+
keyTones = true
|
|
14209
14460
|
}) {
|
|
14210
14461
|
const view = useCall(call);
|
|
14211
14462
|
const softphone = useSoftphone();
|
|
14212
|
-
const [
|
|
14463
|
+
const [panel, setPanel] = React.useState(null);
|
|
14464
|
+
const showTransfer = panel === "transfer";
|
|
14465
|
+
const showKeypad = panel === "keypad";
|
|
14466
|
+
const showDevices = panel === "devices";
|
|
14467
|
+
const togglePanel = (p) => setPanel((cur) => cur === p ? null : p);
|
|
14213
14468
|
const [transferTo, setTransferTo] = React.useState("");
|
|
14214
14469
|
const [consultCall, setConsultCall] = React.useState(null);
|
|
14215
14470
|
const [consultOutcome, setConsultOutcome] = React.useState(null);
|
|
14216
14471
|
const [myUserId, setMyUserId] = React.useState(null);
|
|
14217
14472
|
const consultView = useCall(consultCall);
|
|
14218
14473
|
const consultIntentRef = React.useRef(null);
|
|
14219
|
-
const [showDevices, setShowDevices] = React.useState(false);
|
|
14220
14474
|
const controlsRef = React.useRef(null);
|
|
14221
14475
|
const now = useTicker(view?.state === "active");
|
|
14222
14476
|
const consultNow = useTicker(consultView?.state === "active");
|
|
@@ -14273,20 +14527,31 @@ function CallScreen({
|
|
|
14273
14527
|
React.useEffect(() => {
|
|
14274
14528
|
if (!showDevices) return;
|
|
14275
14529
|
const onKey = (e) => {
|
|
14276
|
-
if (e.key === "Escape")
|
|
14530
|
+
if (e.key === "Escape") setPanel(null);
|
|
14531
|
+
};
|
|
14532
|
+
document.addEventListener("keydown", onKey);
|
|
14533
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
14534
|
+
}, [showDevices]);
|
|
14535
|
+
React.useEffect(() => {
|
|
14536
|
+
if (!showKeypad) return;
|
|
14537
|
+
const onKey = (e) => {
|
|
14538
|
+
if (e.key === "Escape") setPanel(null);
|
|
14277
14539
|
};
|
|
14278
14540
|
document.addEventListener("keydown", onKey);
|
|
14279
14541
|
if (!isConnected) return () => document.removeEventListener("keydown", onKey);
|
|
14280
14542
|
const root = controlsRef.current?.getRootNode() ?? document;
|
|
14281
14543
|
const onDown = (e) => {
|
|
14282
|
-
if (!controlsRef.current?.contains(e.target))
|
|
14544
|
+
if (!controlsRef.current?.contains(e.target)) setPanel(null);
|
|
14283
14545
|
};
|
|
14284
14546
|
root.addEventListener("pointerdown", onDown);
|
|
14285
14547
|
return () => {
|
|
14286
14548
|
document.removeEventListener("keydown", onKey);
|
|
14287
14549
|
root.removeEventListener("pointerdown", onDown);
|
|
14288
14550
|
};
|
|
14289
|
-
}, [
|
|
14551
|
+
}, [showKeypad, isConnected]);
|
|
14552
|
+
React.useEffect(() => {
|
|
14553
|
+
if (view && view.state !== "active") setPanel((cur) => cur === "keypad" ? null : cur);
|
|
14554
|
+
}, [view?.state]);
|
|
14290
14555
|
if (!view || view.state === "ended") return null;
|
|
14291
14556
|
const s = view.state;
|
|
14292
14557
|
const isRinging = s === "ringing";
|
|
@@ -14312,9 +14577,9 @@ function CallScreen({
|
|
|
14312
14577
|
const [rawM = "0", ss = "00"] = formatDuration(elapsedSeconds(view.answeredAt, now)).split(":");
|
|
14313
14578
|
const mm = rawM.padStart(2, "0");
|
|
14314
14579
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-view", children: [
|
|
14315
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-call", children:
|
|
14580
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-call", children: showDevices ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-call-devices-inline", children: [
|
|
14316
14581
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-call-devices-header", children: [
|
|
14317
|
-
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "cv-call-devices-chip", onClick: () =>
|
|
14582
|
+
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "cv-call-devices-chip", onClick: () => setPanel(null), children: [
|
|
14318
14583
|
/* @__PURE__ */ jsxRuntime.jsx(ArrowLeft, { size: 13, "aria-hidden": true }),
|
|
14319
14584
|
"Volver"
|
|
14320
14585
|
] }),
|
|
@@ -14322,7 +14587,7 @@ function CallScreen({
|
|
|
14322
14587
|
] }),
|
|
14323
14588
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "cv-call-devices-context", children: [
|
|
14324
14589
|
showNumber ? `${number} \xB7 ` : "",
|
|
14325
|
-
STATUS_LABEL[s] ?? ""
|
|
14590
|
+
s === "active" ? `${mm}:${ss}` : STATUS_LABEL[s] ?? ""
|
|
14326
14591
|
] }),
|
|
14327
14592
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-settings-section-body", children: /* @__PURE__ */ jsxRuntime.jsx(DevicePickers, {}) })
|
|
14328
14593
|
] }) : transferPickerActive ? (
|
|
@@ -14410,7 +14675,7 @@ function CallScreen({
|
|
|
14410
14675
|
/* @__PURE__ */ jsxRuntime.jsx("i", { children: ":" }),
|
|
14411
14676
|
/* @__PURE__ */ jsxRuntime.jsx("b", { children: ss })
|
|
14412
14677
|
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-call-status", children: s === "held" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-badge-hold", children: STATUS_LABEL[s] }) : STATUS_LABEL[s] ?? "" }),
|
|
14413
|
-
|
|
14678
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-call-chips", children: [
|
|
14414
14679
|
incomingAction ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14415
14680
|
"button",
|
|
14416
14681
|
{
|
|
@@ -14423,11 +14688,11 @@ function CallScreen({
|
|
|
14423
14688
|
]
|
|
14424
14689
|
}
|
|
14425
14690
|
) : null,
|
|
14426
|
-
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "cv-call-devices-chip", onClick: () =>
|
|
14691
|
+
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "cv-call-devices-chip", onClick: () => setPanel("devices"), children: [
|
|
14427
14692
|
/* @__PURE__ */ jsxRuntime.jsx(Headphones, { size: 13, "aria-hidden": true }),
|
|
14428
14693
|
"Dispositivos"
|
|
14429
14694
|
] })
|
|
14430
|
-
] })
|
|
14695
|
+
] })
|
|
14431
14696
|
] }) }),
|
|
14432
14697
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-controls", ref: controlsRef, children: isRinging ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-controls-primary", children: [
|
|
14433
14698
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-ctrl-item", children: [
|
|
@@ -14486,17 +14751,19 @@ function CallScreen({
|
|
|
14486
14751
|
label: "Transferir",
|
|
14487
14752
|
pressed: showTransfer,
|
|
14488
14753
|
tone: "accent",
|
|
14489
|
-
onClick: () =>
|
|
14754
|
+
onClick: () => togglePanel("transfer")
|
|
14490
14755
|
}
|
|
14491
14756
|
),
|
|
14492
14757
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14493
14758
|
Control,
|
|
14494
14759
|
{
|
|
14495
|
-
icon:
|
|
14496
|
-
label: "
|
|
14497
|
-
pressed:
|
|
14760
|
+
icon: Grid3x3,
|
|
14761
|
+
label: "Teclado",
|
|
14762
|
+
pressed: showKeypad,
|
|
14498
14763
|
tone: "accent",
|
|
14499
|
-
|
|
14764
|
+
disabled: s !== "active",
|
|
14765
|
+
title: s === "held" ? "Reanuda la llamada para usar el teclado" : s === "reconnecting" ? "No disponible mientras se reconecta" : void 0,
|
|
14766
|
+
onClick: () => togglePanel("keypad")
|
|
14500
14767
|
}
|
|
14501
14768
|
)
|
|
14502
14769
|
] }) : null,
|
|
@@ -14514,7 +14781,22 @@ function CallScreen({
|
|
|
14514
14781
|
),
|
|
14515
14782
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-ctrl-label", "aria-hidden": true, children: "Colgar" })
|
|
14516
14783
|
] }) }),
|
|
14517
|
-
inCall &&
|
|
14784
|
+
inCall && showKeypad ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-keypad-popover cv-keypad-popover--above", role: "dialog", "aria-label": "Teclado", children: KEYPAD_KEYS.map(([key, letters]) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14785
|
+
"button",
|
|
14786
|
+
{
|
|
14787
|
+
type: "button",
|
|
14788
|
+
className: "cv-key",
|
|
14789
|
+
onClick: () => {
|
|
14790
|
+
call.sendDigits(key);
|
|
14791
|
+
if (keyTones) playKeyTone(key);
|
|
14792
|
+
},
|
|
14793
|
+
children: [
|
|
14794
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: key }),
|
|
14795
|
+
letters ? /* @__PURE__ */ jsxRuntime.jsx("small", { children: letters }) : null
|
|
14796
|
+
]
|
|
14797
|
+
},
|
|
14798
|
+
key
|
|
14799
|
+
)) }) : null
|
|
14518
14800
|
] }) })
|
|
14519
14801
|
] });
|
|
14520
14802
|
}
|
|
@@ -15289,203 +15571,6 @@ function useCallOutcome() {
|
|
|
15289
15571
|
clear
|
|
15290
15572
|
};
|
|
15291
15573
|
}
|
|
15292
|
-
|
|
15293
|
-
// src/ui/audio/audio.ts
|
|
15294
|
-
var TONE_HZ = 425;
|
|
15295
|
-
function createRemoteAudio() {
|
|
15296
|
-
const el = document.createElement("audio");
|
|
15297
|
-
el.autoplay = true;
|
|
15298
|
-
el.style.display = "none";
|
|
15299
|
-
document.body.appendChild(el);
|
|
15300
|
-
return {
|
|
15301
|
-
attach(stream) {
|
|
15302
|
-
el.srcObject = stream;
|
|
15303
|
-
void el.play().catch(() => {
|
|
15304
|
-
});
|
|
15305
|
-
},
|
|
15306
|
-
detach() {
|
|
15307
|
-
el.srcObject = null;
|
|
15308
|
-
},
|
|
15309
|
-
destroy() {
|
|
15310
|
-
el.srcObject = null;
|
|
15311
|
-
el.remove();
|
|
15312
|
-
},
|
|
15313
|
-
async setOutputDevice(deviceId) {
|
|
15314
|
-
if (typeof el.setSinkId !== "function") return;
|
|
15315
|
-
try {
|
|
15316
|
-
await el.setSinkId(deviceId ?? "");
|
|
15317
|
-
} catch {
|
|
15318
|
-
}
|
|
15319
|
-
}
|
|
15320
|
-
};
|
|
15321
|
-
}
|
|
15322
|
-
var shared = null;
|
|
15323
|
-
function sharedContext() {
|
|
15324
|
-
if (shared) return shared;
|
|
15325
|
-
const Ctor = typeof AudioContext !== "undefined" ? AudioContext : globalThis.webkitAudioContext;
|
|
15326
|
-
if (!Ctor) return null;
|
|
15327
|
-
shared = new Ctor();
|
|
15328
|
-
return shared;
|
|
15329
|
-
}
|
|
15330
|
-
function primeAudio() {
|
|
15331
|
-
const ctx = sharedContext();
|
|
15332
|
-
if (ctx && ctx.state === "suspended") void ctx.resume?.().catch(() => {
|
|
15333
|
-
});
|
|
15334
|
-
}
|
|
15335
|
-
function playRingtone() {
|
|
15336
|
-
const ctx = sharedContext();
|
|
15337
|
-
if (!ctx) return { stop() {
|
|
15338
|
-
} };
|
|
15339
|
-
const gain = ctx.createGain();
|
|
15340
|
-
gain.gain.value = 0;
|
|
15341
|
-
gain.connect(ctx.destination);
|
|
15342
|
-
const osc = ctx.createOscillator();
|
|
15343
|
-
osc.type = "sine";
|
|
15344
|
-
osc.frequency.value = TONE_HZ;
|
|
15345
|
-
osc.connect(gain);
|
|
15346
|
-
osc.start();
|
|
15347
|
-
const LEVEL = 0.8;
|
|
15348
|
-
const RAMP = 0.02;
|
|
15349
|
-
const burst = (at, dur) => {
|
|
15350
|
-
gain.gain.setValueAtTime(0, at);
|
|
15351
|
-
gain.gain.linearRampToValueAtTime(LEVEL, at + RAMP);
|
|
15352
|
-
gain.gain.setValueAtTime(LEVEL, at + dur - RAMP);
|
|
15353
|
-
gain.gain.linearRampToValueAtTime(0, at + dur);
|
|
15354
|
-
};
|
|
15355
|
-
const scheduleCycle = (at) => {
|
|
15356
|
-
burst(at, 0.4);
|
|
15357
|
-
burst(at + 0.6, 0.4);
|
|
15358
|
-
return 3;
|
|
15359
|
-
};
|
|
15360
|
-
const LOOKAHEAD = 0.5;
|
|
15361
|
-
const TICK_MS = 150;
|
|
15362
|
-
const LEAD = 0.08;
|
|
15363
|
-
let stopped = false;
|
|
15364
|
-
let timer = null;
|
|
15365
|
-
let nextAt = 0;
|
|
15366
|
-
const pump = () => {
|
|
15367
|
-
while (nextAt < ctx.currentTime + LOOKAHEAD) nextAt += scheduleCycle(nextAt);
|
|
15368
|
-
};
|
|
15369
|
-
void Promise.resolve(ctx.resume?.()).catch(() => {
|
|
15370
|
-
}).finally(() => {
|
|
15371
|
-
if (stopped) return;
|
|
15372
|
-
nextAt = ctx.currentTime + LEAD;
|
|
15373
|
-
pump();
|
|
15374
|
-
timer = setInterval(pump, TICK_MS);
|
|
15375
|
-
});
|
|
15376
|
-
return {
|
|
15377
|
-
stop() {
|
|
15378
|
-
stopped = true;
|
|
15379
|
-
if (timer) clearInterval(timer);
|
|
15380
|
-
try {
|
|
15381
|
-
osc.stop();
|
|
15382
|
-
} catch {
|
|
15383
|
-
}
|
|
15384
|
-
try {
|
|
15385
|
-
gain.disconnect();
|
|
15386
|
-
} catch {
|
|
15387
|
-
}
|
|
15388
|
-
}
|
|
15389
|
-
};
|
|
15390
|
-
}
|
|
15391
|
-
function playCallEndedTone() {
|
|
15392
|
-
const ctx = sharedContext();
|
|
15393
|
-
if (!ctx) return;
|
|
15394
|
-
const gain = ctx.createGain();
|
|
15395
|
-
gain.gain.value = 0;
|
|
15396
|
-
gain.connect(ctx.destination);
|
|
15397
|
-
const osc = ctx.createOscillator();
|
|
15398
|
-
osc.type = "sine";
|
|
15399
|
-
osc.frequency.value = TONE_HZ;
|
|
15400
|
-
osc.connect(gain);
|
|
15401
|
-
osc.start();
|
|
15402
|
-
const LEVEL = 0.55;
|
|
15403
|
-
const RAMP = 8e-3;
|
|
15404
|
-
const BEEP = 0.25;
|
|
15405
|
-
void Promise.resolve(ctx.resume?.()).catch(() => {
|
|
15406
|
-
}).finally(() => {
|
|
15407
|
-
const at = ctx.currentTime + 0.02;
|
|
15408
|
-
gain.gain.setValueAtTime(0, at);
|
|
15409
|
-
gain.gain.linearRampToValueAtTime(LEVEL, at + RAMP);
|
|
15410
|
-
gain.gain.setValueAtTime(LEVEL, at + BEEP - RAMP);
|
|
15411
|
-
gain.gain.linearRampToValueAtTime(0, at + BEEP);
|
|
15412
|
-
const totalMs = (at + BEEP - ctx.currentTime) * 1e3;
|
|
15413
|
-
setTimeout(() => {
|
|
15414
|
-
try {
|
|
15415
|
-
osc.stop();
|
|
15416
|
-
} catch {
|
|
15417
|
-
}
|
|
15418
|
-
try {
|
|
15419
|
-
gain.disconnect();
|
|
15420
|
-
} catch {
|
|
15421
|
-
}
|
|
15422
|
-
}, totalMs);
|
|
15423
|
-
});
|
|
15424
|
-
}
|
|
15425
|
-
var ringbackUrl = null;
|
|
15426
|
-
function ringbackSource() {
|
|
15427
|
-
if (ringbackUrl) return ringbackUrl;
|
|
15428
|
-
const sampleRate = 48e3;
|
|
15429
|
-
const channels = 2;
|
|
15430
|
-
const bytesPerSample = 2;
|
|
15431
|
-
const amp = 0.5;
|
|
15432
|
-
const toneSec = 1.5;
|
|
15433
|
-
const silenceSec = 3;
|
|
15434
|
-
const totalFrames = Math.round((toneSec + silenceSec) * sampleRate);
|
|
15435
|
-
const toneFrames = Math.round(toneSec * sampleRate);
|
|
15436
|
-
const ramp = Math.round(0.02 * sampleRate);
|
|
15437
|
-
const blockAlign = channels * bytesPerSample;
|
|
15438
|
-
const dataSize = totalFrames * blockAlign;
|
|
15439
|
-
const buffer = new ArrayBuffer(44 + dataSize);
|
|
15440
|
-
const view = new DataView(buffer);
|
|
15441
|
-
const str = (off, s) => {
|
|
15442
|
-
for (let i = 0; i < s.length; i++) view.setUint8(off + i, s.charCodeAt(i));
|
|
15443
|
-
};
|
|
15444
|
-
str(0, "RIFF");
|
|
15445
|
-
view.setUint32(4, 36 + dataSize, true);
|
|
15446
|
-
str(8, "WAVE");
|
|
15447
|
-
str(12, "fmt ");
|
|
15448
|
-
view.setUint32(16, 16, true);
|
|
15449
|
-
view.setUint16(20, 1, true);
|
|
15450
|
-
view.setUint16(22, channels, true);
|
|
15451
|
-
view.setUint32(24, sampleRate, true);
|
|
15452
|
-
view.setUint32(28, sampleRate * blockAlign, true);
|
|
15453
|
-
view.setUint16(32, blockAlign, true);
|
|
15454
|
-
view.setUint16(34, bytesPerSample * 8, true);
|
|
15455
|
-
str(36, "data");
|
|
15456
|
-
view.setUint32(40, dataSize, true);
|
|
15457
|
-
for (let i = 0; i < totalFrames; i++) {
|
|
15458
|
-
let sample = 0;
|
|
15459
|
-
if (i < toneFrames) {
|
|
15460
|
-
let env = 1;
|
|
15461
|
-
if (i < ramp) env = i / ramp;
|
|
15462
|
-
else if (i > toneFrames - ramp) env = Math.max(0, (toneFrames - i) / ramp);
|
|
15463
|
-
sample = Math.sin(2 * Math.PI * TONE_HZ * (i / sampleRate)) * amp * env;
|
|
15464
|
-
}
|
|
15465
|
-
const s16 = Math.round(sample * 32767);
|
|
15466
|
-
const off = 44 + i * blockAlign;
|
|
15467
|
-
for (let c = 0; c < channels; c++) view.setInt16(off + c * bytesPerSample, s16, true);
|
|
15468
|
-
}
|
|
15469
|
-
ringbackUrl = URL.createObjectURL(new Blob([buffer], { type: "audio/wav" }));
|
|
15470
|
-
return ringbackUrl;
|
|
15471
|
-
}
|
|
15472
|
-
function playRingback() {
|
|
15473
|
-
const el = document.createElement("audio");
|
|
15474
|
-
el.src = ringbackSource();
|
|
15475
|
-
el.loop = true;
|
|
15476
|
-
el.style.display = "none";
|
|
15477
|
-
document.body.appendChild(el);
|
|
15478
|
-
void el.play().catch(() => {
|
|
15479
|
-
});
|
|
15480
|
-
return {
|
|
15481
|
-
stop() {
|
|
15482
|
-
el.pause();
|
|
15483
|
-
el.remove();
|
|
15484
|
-
}
|
|
15485
|
-
};
|
|
15486
|
-
}
|
|
15487
|
-
|
|
15488
|
-
// src/ui/audio/useCallAudio.ts
|
|
15489
15574
|
function useCallAudio(enabled) {
|
|
15490
15575
|
const softphone = useSoftphone();
|
|
15491
15576
|
const remote = React.useRef(null);
|
|
@@ -15703,7 +15788,7 @@ function SoftphoneWidget(props) {
|
|
|
15703
15788
|
...theme === "system" ? {} : { "data-cv-theme": theme },
|
|
15704
15789
|
children: [
|
|
15705
15790
|
screenIsFree ? /* @__PURE__ */ jsxRuntime.jsx(TransferNotices, { notices: transferNotices, dismiss: dismissTransferNotice }) : null,
|
|
15706
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ jsxRuntime.jsx(LeaderGate, {}) : blocked ? /* @__PURE__ */ jsxRuntime.jsx(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ jsxRuntime.jsx(CallScreen, { call, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ jsxRuntime.jsx(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ jsxRuntime.jsx(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ jsxRuntime.jsx(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ jsxRuntime.jsx(SettingsTab, {}) : /* @__PURE__ */ jsxRuntime.jsx(Dialpad, { outcome, onIntent: clearOutcome }) }),
|
|
15791
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-body", children: isFollower ? /* @__PURE__ */ jsxRuntime.jsx(LeaderGate, {}) : blocked ? /* @__PURE__ */ jsxRuntime.jsx(MicPrompt, { state: mic.state, onRequest: () => void mic.request() }) : call ? /* @__PURE__ */ jsxRuntime.jsx(CallScreen, { call, keyTones: sounds, ...incomingCallAction ? { incomingCallAction } : {} }) : fatal ? /* @__PURE__ */ jsxRuntime.jsx(ErrorGate, { error: fatal, onRetry: retry }) : tab === "contacts" && contactsSource ? /* @__PURE__ */ jsxRuntime.jsx(ContactsTab, { source: contactsSource, ...contactAction ? { contactAction } : {} }) : tab === "history" && historySource ? /* @__PURE__ */ jsxRuntime.jsx(HistoryTab, { source: historySource }) : tab === "settings" ? /* @__PURE__ */ jsxRuntime.jsx(SettingsTab, {}) : /* @__PURE__ */ jsxRuntime.jsx(Dialpad, { outcome, onIntent: clearOutcome, keyTones: sounds }) }),
|
|
15707
15792
|
screenIsFree ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-tabs", role: "tablist", children: tabs.map(({ id, icon: Icon3, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
15708
15793
|
"button",
|
|
15709
15794
|
{
|