|
@@ -0,0 +1,294 @@
|
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
|
+<html lang="zh-CN">
|
|
|
|
|
+<head>
|
|
|
|
|
+ <meta charset="UTF-8">
|
|
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
+ <title>JsSIP WSS Register Test</title>
|
|
|
|
|
+ <style>
|
|
|
|
|
+ body {
|
|
|
|
|
+ font-family: Arial, sans-serif;
|
|
|
|
|
+ margin: 24px;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ color: #222;
|
|
|
|
|
+ }
|
|
|
|
|
+ .card {
|
|
|
|
|
+ max-width: 900px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
|
|
|
+ }
|
|
|
|
|
+ .row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ }
|
|
|
|
|
+ .field {
|
|
|
|
|
+ flex: 1 1 280px;
|
|
|
|
|
+ }
|
|
|
|
|
+ label {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ }
|
|
|
|
|
+ input {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ border: 1px solid #ccc;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+ button {
|
|
|
|
|
+ padding: 10px 16px;
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ background: #1677ff;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ }
|
|
|
|
|
+ button.secondary {
|
|
|
|
|
+ background: #666;
|
|
|
|
|
+ }
|
|
|
|
|
+ pre {
|
|
|
|
|
+ margin-top: 16px;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ min-height: 260px;
|
|
|
|
|
+ overflow: auto;
|
|
|
|
|
+ background: #0d1117;
|
|
|
|
|
+ color: #d7e3f4;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ white-space: pre-wrap;
|
|
|
|
|
+ word-break: break-word;
|
|
|
|
|
+ }
|
|
|
|
|
+ .hint {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .ok {
|
|
|
|
|
+ color: #0a7f2e;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ }
|
|
|
|
|
+ .err {
|
|
|
|
|
+ color: #c62828;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ }
|
|
|
|
|
+ </style>
|
|
|
|
|
+</head>
|
|
|
|
|
+<body>
|
|
|
|
|
+ <div class="card">
|
|
|
|
|
+ <h2>JsSIP WSS Register Test</h2>
|
|
|
|
|
+ <div class="row">
|
|
|
|
|
+ <div class="field">
|
|
|
|
|
+ <label for="wsUrl">WSS</label>
|
|
|
|
|
+ <input id="wsUrl" value="wss://sip.ylrzcloud.com:8443">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="field">
|
|
|
|
|
+ <label for="sipDomain">SIP Domain</label>
|
|
|
|
|
+ <input id="sipDomain" value="sip.ylrzcloud.com">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="row">
|
|
|
|
|
+ <div class="field">
|
|
|
|
|
+ <label for="username">分机号</label>
|
|
|
|
|
+ <input id="username" value="1228">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="field">
|
|
|
|
|
+ <label for="password">分机密码</label>
|
|
|
|
|
+ <input id="password" type="password" placeholder="请输入分机密码">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="row">
|
|
|
|
|
+ <div class="field">
|
|
|
|
|
+ <label for="registrarServer">Registrar</label>
|
|
|
|
|
+ <input id="registrarServer" value="sip:sip.ylrzcloud.com">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="field">
|
|
|
|
|
+ <label for="realm">Realm</label>
|
|
|
|
|
+ <input id="realm" value="sip.ylrzcloud.com">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="row">
|
|
|
|
|
+ <button id="startBtn" disabled>开始注册</button>
|
|
|
|
|
+ <button id="stopBtn" class="secondary">停止</button>
|
|
|
|
|
+ <button id="clearBtn" class="secondary">清空日志</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div id="status">状态:未启动</div>
|
|
|
|
|
+ <div class="hint">
|
|
|
|
|
+ 打开页面后先输入分机密码,再点击“开始注册”。如果浏览器证书未信任,请先在浏览器中访问一次
|
|
|
|
|
+ <code>wss://sip.ylrzcloud.com:8443</code> 对应域名页面,确认无证书拦截。
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <pre id="log"></pre>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <script src="./jssip.min.js"></script>
|
|
|
|
|
+ <script>
|
|
|
|
|
+ let ua = null;
|
|
|
|
|
+ let jssipReady = !!window.JsSIP;
|
|
|
|
|
+
|
|
|
|
|
+ function log(message, data) {
|
|
|
|
|
+ const logEl = document.getElementById("log");
|
|
|
|
|
+ const now = new Date().toLocaleTimeString();
|
|
|
|
|
+ let line = "[" + now + "] " + message;
|
|
|
|
|
+ if (typeof data !== "undefined") {
|
|
|
|
|
+ try {
|
|
|
|
|
+ line += "\n" + JSON.stringify(data, null, 2);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ line += "\n" + String(data);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ logEl.textContent += line + "\n\n";
|
|
|
|
|
+ logEl.scrollTop = logEl.scrollHeight;
|
|
|
|
|
+ console.log(message, data || "");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function setStatus(text, className) {
|
|
|
|
|
+ const el = document.getElementById("status");
|
|
|
|
|
+ el.textContent = "状态:" + text;
|
|
|
|
|
+ el.className = className || "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function setStartButtonEnabled(enabled) {
|
|
|
|
|
+ document.getElementById("startBtn").disabled = !enabled;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function ensureJsSIPLoaded() {
|
|
|
|
|
+ if (window.JsSIP) {
|
|
|
|
|
+ jssipReady = true;
|
|
|
|
|
+ setStartButtonEnabled(true);
|
|
|
|
|
+ setStatus("JsSIP 已就绪", "ok");
|
|
|
|
|
+ log("已从本地文件加载 JsSIP", "./jssip.min.js");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setStatus("JsSIP 加载失败", "err");
|
|
|
|
|
+ log("无法从本地加载 JsSIP,请确认当前页面与 ./jssip.min.js 在同一目录,并通过 HTTP 服务访问该页面。");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function stopUA() {
|
|
|
|
|
+ if (ua) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ua.stop();
|
|
|
|
|
+ log("已调用 ua.stop()");
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ log("停止 UA 异常", e.message || String(e));
|
|
|
|
|
+ }
|
|
|
|
|
+ ua = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ setStatus("已停止");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function buildConfig() {
|
|
|
|
|
+ const wsUrl = document.getElementById("wsUrl").value.trim();
|
|
|
|
|
+ const sipDomain = document.getElementById("sipDomain").value.trim();
|
|
|
|
|
+ const username = document.getElementById("username").value.trim();
|
|
|
|
|
+ const password = document.getElementById("password").value;
|
|
|
|
|
+ const registrarServer = document.getElementById("registrarServer").value.trim();
|
|
|
|
|
+ const realm = document.getElementById("realm").value.trim();
|
|
|
|
|
+
|
|
|
|
|
+ if (!wsUrl || !sipDomain || !username || !password || !registrarServer || !realm) {
|
|
|
|
|
+ throw new Error("WSS、SIP 域名、分机号、密码、Registrar、Realm 都不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const socket = new JsSIP.WebSocketInterface(wsUrl);
|
|
|
|
|
+ const configuration = {
|
|
|
|
|
+ sockets: [socket],
|
|
|
|
|
+ uri: "sip:" + username + "@" + sipDomain,
|
|
|
|
|
+ authorization_user: username,
|
|
|
|
|
+ password: password,
|
|
|
|
|
+ registrar_server: registrarServer,
|
|
|
|
|
+ realm: realm,
|
|
|
|
|
+ register: true,
|
|
|
|
|
+ session_timers: false
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return configuration;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function bindUAEvents(instance) {
|
|
|
|
|
+ instance.on("connecting", function(data) {
|
|
|
|
|
+ setStatus("正在连接 WSS...");
|
|
|
|
|
+ log("[SIP] connecting", data);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ instance.on("connected", function(data) {
|
|
|
|
|
+ setStatus("WSS 已连接");
|
|
|
|
|
+ log("[SIP] connected", data);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ instance.on("disconnected", function(data) {
|
|
|
|
|
+ setStatus("WSS 已断开", "err");
|
|
|
|
|
+ log("[SIP] disconnected", {
|
|
|
|
|
+ code: data && data.code,
|
|
|
|
|
+ reason: data && data.reason
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ instance.on("registered", function(data) {
|
|
|
|
|
+ setStatus("注册成功", "ok");
|
|
|
|
|
+ log("[SIP] 注册成功", data);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ instance.on("unregistered", function(data) {
|
|
|
|
|
+ setStatus("已注销");
|
|
|
|
|
+ log("[SIP] unregistered", data);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ instance.on("registrationFailed", function(data) {
|
|
|
|
|
+ setStatus("注册失败", "err");
|
|
|
|
|
+ log("[SIP] 注册失败", {
|
|
|
|
|
+ cause: data && data.cause,
|
|
|
|
|
+ status_code: data && data.response && data.response.status_code,
|
|
|
|
|
+ reason_phrase: data && data.response && data.response.reason_phrase
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ instance.on("newRTCSession", function(data) {
|
|
|
|
|
+ log("[SIP] newRTCSession", {
|
|
|
|
|
+ direction: data && data.session && data.session.direction
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById("startBtn").addEventListener("click", function() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!jssipReady || !window.JsSIP) {
|
|
|
|
|
+ throw new Error("JsSIP 库还没有加载成功");
|
|
|
|
|
+ }
|
|
|
|
|
+ stopUA();
|
|
|
|
|
+ const configuration = buildConfig();
|
|
|
|
|
+ log("准备启动 JsSIP", {
|
|
|
|
|
+ sockets: configuration.sockets.map(function(item) { return item.url; }),
|
|
|
|
|
+ uri: configuration.uri,
|
|
|
|
|
+ authorization_user: configuration.authorization_user,
|
|
|
|
|
+ registrar_server: configuration.registrar_server,
|
|
|
|
|
+ realm: configuration.realm,
|
|
|
|
|
+ register: configuration.register
|
|
|
|
|
+ });
|
|
|
|
|
+ ua = new JsSIP.UA(configuration);
|
|
|
|
|
+ bindUAEvents(ua);
|
|
|
|
|
+ ua.start();
|
|
|
|
|
+ setStatus("已启动,等待连接...");
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ setStatus("启动失败", "err");
|
|
|
|
|
+ log("启动异常", e.message || String(e));
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById("stopBtn").addEventListener("click", function() {
|
|
|
|
|
+ stopUA();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById("clearBtn").addEventListener("click", function() {
|
|
|
|
|
+ document.getElementById("log").textContent = "";
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ ensureJsSIPLoaded();
|
|
|
|
|
+ </script>
|
|
|
|
|
+</body>
|
|
|
|
|
+</html>
|