@auxilium/datalynk-client 1.5.2 → 1.6.0
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/api.d.ts +25 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/chat-entry.d.ts +3 -0
- package/dist/chat-entry.d.ts.map +1 -0
- package/dist/chat-gui.d.ts +64 -0
- package/dist/chat-gui.d.ts.map +1 -0
- package/dist/chat-gui.view.d.ts +3 -0
- package/dist/chat-gui.view.d.ts.map +1 -0
- package/dist/chat.cjs +2202 -0
- package/dist/chat.d.ts +75 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.mjs +2198 -0
- package/dist/index.cjs +235 -7
- package/dist/index.mjs +235 -7
- package/dist/socket.d.ts +14 -1
- package/dist/socket.d.ts.map +1 -1
- package/package.json +7 -2
package/dist/index.cjs
CHANGED
|
@@ -3106,6 +3106,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3106
3106
|
// [ Callback, Re-subscribe ]
|
|
3107
3107
|
__publicField(this, "retry");
|
|
3108
3108
|
__publicField(this, "socket");
|
|
3109
|
+
__publicField(this, "connectGeneration", 0);
|
|
3109
3110
|
__publicField(this, "open", false);
|
|
3110
3111
|
this.api = api;
|
|
3111
3112
|
this.options = options;
|
|
@@ -3114,7 +3115,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3114
3115
|
this.options.url = origin.replace("http", "ws").replace(/:\d+/g, "") + `/s/`;
|
|
3115
3116
|
}
|
|
3116
3117
|
if (this.options.url !== false)
|
|
3117
|
-
api.token$.pipe(filter((u) => u !== void 0), distinctUntilChanged()).subscribe(() => this.connect());
|
|
3118
|
+
api.token$.pipe(filter((u) => u !== void 0), distinctUntilChanged()).subscribe(() => void this.connect());
|
|
3118
3119
|
}
|
|
3119
3120
|
/**
|
|
3120
3121
|
* Add listener for all socket events
|
|
@@ -3136,6 +3137,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3136
3137
|
*/
|
|
3137
3138
|
close() {
|
|
3138
3139
|
var _a;
|
|
3140
|
+
if (this.options.authentication === "ticket") this.connectGeneration++;
|
|
3139
3141
|
if (this.open) console.debug("Datalynk socket: disconnected");
|
|
3140
3142
|
this.open = false;
|
|
3141
3143
|
(_a = this.socket) == null ? void 0 : _a.close();
|
|
@@ -3148,6 +3150,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3148
3150
|
* @param {number} timeout Retry to connect every x seconds
|
|
3149
3151
|
*/
|
|
3150
3152
|
connect(timeout = 30) {
|
|
3153
|
+
if (this.options.authentication === "ticket") return this.connectWithTicket(timeout);
|
|
3151
3154
|
if (this.options.url === false) return console.warn("Datalynk socket disabled");
|
|
3152
3155
|
if (this.open) this.close();
|
|
3153
3156
|
this.retry = setTimeout(() => {
|
|
@@ -3177,6 +3180,80 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3177
3180
|
};
|
|
3178
3181
|
}
|
|
3179
3182
|
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Ticket-authenticated connection path, used only once a chat-capable host switches
|
|
3185
|
+
* this instance to `authentication: 'ticket'` (see Api.openChat). Entirely separate
|
|
3186
|
+
* from the token path above so existing non-chat consumers are never affected.
|
|
3187
|
+
*/
|
|
3188
|
+
async connectWithTicket(timeout = 30) {
|
|
3189
|
+
var _a;
|
|
3190
|
+
if (this.options.url === false) return console.warn("Datalynk socket disabled");
|
|
3191
|
+
const generation = ++this.connectGeneration;
|
|
3192
|
+
if (this.open || this.socket) {
|
|
3193
|
+
this.open = false;
|
|
3194
|
+
(_a = this.socket) == null ? void 0 : _a.close();
|
|
3195
|
+
this.socket = void 0;
|
|
3196
|
+
}
|
|
3197
|
+
if (this.retry) clearTimeout(this.retry);
|
|
3198
|
+
this.retry = setTimeout(() => {
|
|
3199
|
+
if (this.open || generation !== this.connectGeneration) return;
|
|
3200
|
+
void this.connect(timeout);
|
|
3201
|
+
}, timeout * 1e3);
|
|
3202
|
+
if (!this.api.online || !this.api.token) return;
|
|
3203
|
+
let socketUrl = this.normalizeUrl(String(this.options.url || ""));
|
|
3204
|
+
try {
|
|
3205
|
+
const response = await this.api.request({ "$/chat/socketTicket": {} }, { noOptimize: true });
|
|
3206
|
+
if (generation !== this.connectGeneration) return;
|
|
3207
|
+
if (response == null ? void 0 : response.ticket) socketUrl += `${socketUrl.includes("?") ? "&" : "?"}ticket=${encodeURIComponent(response.ticket)}`;
|
|
3208
|
+
else throw new Error("Missing socket ticket");
|
|
3209
|
+
} catch (_) {
|
|
3210
|
+
if (generation !== this.connectGeneration) return;
|
|
3211
|
+
this.scheduleReconnect(generation, timeout);
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3214
|
+
if (generation !== this.connectGeneration) return;
|
|
3215
|
+
this.socket = new WebSocket(socketUrl);
|
|
3216
|
+
this.socket.onopen = () => void 0;
|
|
3217
|
+
this.socket.onclose = () => {
|
|
3218
|
+
this.open = false;
|
|
3219
|
+
this.socket = void 0;
|
|
3220
|
+
this.scheduleReconnect(generation, timeout);
|
|
3221
|
+
};
|
|
3222
|
+
this.socket.onerror = () => {
|
|
3223
|
+
};
|
|
3224
|
+
this.socket.onmessage = (message) => {
|
|
3225
|
+
var _a2;
|
|
3226
|
+
let payload;
|
|
3227
|
+
try {
|
|
3228
|
+
payload = JSON.parse(message.data);
|
|
3229
|
+
} catch {
|
|
3230
|
+
return;
|
|
3231
|
+
}
|
|
3232
|
+
if (payload.connected != void 0) {
|
|
3233
|
+
if (payload.connected) {
|
|
3234
|
+
this.open = true;
|
|
3235
|
+
if (this.retry) clearTimeout(this.retry);
|
|
3236
|
+
this.retry = null;
|
|
3237
|
+
console.debug("Datalynk socket: connected");
|
|
3238
|
+
this.listeners.forEach((l) => l[1]());
|
|
3239
|
+
} else {
|
|
3240
|
+
this.open = false;
|
|
3241
|
+
console.warn(`Datalynk socket failed: ${payload.error || "authentication failed"}`);
|
|
3242
|
+
(_a2 = this.socket) == null ? void 0 : _a2.close();
|
|
3243
|
+
}
|
|
3244
|
+
} else {
|
|
3245
|
+
this.listeners.forEach((l) => l[0](payload));
|
|
3246
|
+
}
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
scheduleReconnect(generation, timeout) {
|
|
3250
|
+
if (generation !== this.connectGeneration || this.options.url === false) return;
|
|
3251
|
+
if (this.retry) clearTimeout(this.retry);
|
|
3252
|
+
this.retry = setTimeout(() => {
|
|
3253
|
+
if (generation !== this.connectGeneration) return;
|
|
3254
|
+
void this.connect(timeout);
|
|
3255
|
+
}, timeout * 1e3);
|
|
3256
|
+
}
|
|
3180
3257
|
/**
|
|
3181
3258
|
* Send data to socket server
|
|
3182
3259
|
*
|
|
@@ -3203,6 +3280,17 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3203
3280
|
unsubscribe();
|
|
3204
3281
|
};
|
|
3205
3282
|
}
|
|
3283
|
+
normalizeUrl(value) {
|
|
3284
|
+
let url = value.replace(/^http:\/\//i, "ws://").replace(/^https:\/\//i, "wss://");
|
|
3285
|
+
try {
|
|
3286
|
+
const parsed = new URL(url);
|
|
3287
|
+
if (parsed.pathname === "/" || parsed.pathname === "") parsed.pathname = "/s/";
|
|
3288
|
+
else if (parsed.pathname === "/s") parsed.pathname = "/s/";
|
|
3289
|
+
url = parsed.toString();
|
|
3290
|
+
} catch {
|
|
3291
|
+
}
|
|
3292
|
+
return url;
|
|
3293
|
+
}
|
|
3206
3294
|
}
|
|
3207
3295
|
class Superuser {
|
|
3208
3296
|
constructor(api) {
|
|
@@ -3234,7 +3322,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3234
3322
|
} });
|
|
3235
3323
|
}
|
|
3236
3324
|
}
|
|
3237
|
-
const version = "1.
|
|
3325
|
+
const version = "1.6.0";
|
|
3238
3326
|
class WebRtc {
|
|
3239
3327
|
constructor(api) {
|
|
3240
3328
|
__publicField(this, "ice");
|
|
@@ -3801,6 +3889,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3801
3889
|
__publicField(this, "tokenStorageListener", null);
|
|
3802
3890
|
/** Pending requests cache */
|
|
3803
3891
|
__publicField(this, "pending", {});
|
|
3892
|
+
__publicField(this, "chatModulePromise", null);
|
|
3893
|
+
__publicField(this, "chatInstance", null);
|
|
3894
|
+
__publicField(this, "chatLauncherHost", null);
|
|
3895
|
+
__publicField(this, "chatLauncherButton", null);
|
|
3804
3896
|
/** Helpers */
|
|
3805
3897
|
/** Authentication */
|
|
3806
3898
|
__publicField(this, "auth");
|
|
@@ -3855,6 +3947,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3855
3947
|
offline: [],
|
|
3856
3948
|
origin: typeof location !== "undefined" ? location.host : "Unknown",
|
|
3857
3949
|
saveSession: true,
|
|
3950
|
+
manageSession: true,
|
|
3951
|
+
chat: false,
|
|
3858
3952
|
replayOfflineQueue: true,
|
|
3859
3953
|
watchTokenExpiry: true,
|
|
3860
3954
|
serviceWorker: "/service.worker.mjs",
|
|
@@ -3886,9 +3980,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3886
3980
|
window.addEventListener("storage", this.tokenStorageListener);
|
|
3887
3981
|
}
|
|
3888
3982
|
}
|
|
3889
|
-
if (this.options.watchTokenExpiry)
|
|
3983
|
+
if (this.options.manageSession !== false && this.options.watchTokenExpiry)
|
|
3890
3984
|
this.token$.pipe(distinctUntilChanged()).subscribe((token) => this.scheduleTokenExpiry(token));
|
|
3891
|
-
this.socket = new Socket(this, { url: options.socket });
|
|
3985
|
+
this.socket = new Socket(this, { url: options.socket, authentication: options.socketAuthentication, realm: options.socketRealm });
|
|
3892
3986
|
this.gps = new Gps(this, options.gps);
|
|
3893
3987
|
this.auth = new Auth(this);
|
|
3894
3988
|
this.files = new Files(this);
|
|
@@ -3929,6 +4023,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
3929
4023
|
})();
|
|
3930
4024
|
}
|
|
3931
4025
|
}
|
|
4026
|
+
this.setupChatLauncher();
|
|
3932
4027
|
}
|
|
3933
4028
|
/** Is token expired */
|
|
3934
4029
|
get expired() {
|
|
@@ -4000,7 +4095,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4000
4095
|
var _a;
|
|
4001
4096
|
if (this.recovery && token !== this.recovery.token)
|
|
4002
4097
|
this.cancelRecovery(errorFromCode(401, "Session changed during API recovery"));
|
|
4003
|
-
if (token && this.isTokenExpired(token)) {
|
|
4098
|
+
if (this.options.manageSession !== false && token && this.isTokenExpired(token)) {
|
|
4004
4099
|
if (this.adoptStoredToken(token)) return;
|
|
4005
4100
|
if (this.shouldDeferLocalExpiry()) {
|
|
4006
4101
|
this.deferOfflineExpiry(token);
|
|
@@ -4018,6 +4113,138 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4018
4113
|
}
|
|
4019
4114
|
if (token && ((_a = this.options) == null ? void 0 : _a.replayOfflineQueue) !== false) void this.replayPendingQueue();
|
|
4020
4115
|
}
|
|
4116
|
+
/** Mount the optional launcher without loading or initializing the chat bundle. */
|
|
4117
|
+
setupChatLauncher() {
|
|
4118
|
+
if (!this.options.chat || typeof document === "undefined") return;
|
|
4119
|
+
const mount = () => {
|
|
4120
|
+
if (this.chatLauncherHost || !document.body || document.querySelector("[data-datalynk-chat-launcher]")) return;
|
|
4121
|
+
const host = document.createElement("div");
|
|
4122
|
+
host.setAttribute("data-datalynk-chat-launcher", "");
|
|
4123
|
+
const root = host.attachShadow({ mode: "open" });
|
|
4124
|
+
root.innerHTML = `
|
|
4125
|
+
<style>
|
|
4126
|
+
:host {
|
|
4127
|
+
all: initial;
|
|
4128
|
+
position: fixed;
|
|
4129
|
+
right: 20px;
|
|
4130
|
+
bottom: 20px;
|
|
4131
|
+
z-index: 2147482999;
|
|
4132
|
+
pointer-events: none;
|
|
4133
|
+
}
|
|
4134
|
+
button {
|
|
4135
|
+
display: grid;
|
|
4136
|
+
place-items: center;
|
|
4137
|
+
width: 52px;
|
|
4138
|
+
height: 52px;
|
|
4139
|
+
padding: 0;
|
|
4140
|
+
border: 1px solid #2f596d;
|
|
4141
|
+
border-radius: 50%;
|
|
4142
|
+
background: #3f6f85;
|
|
4143
|
+
color: #fff;
|
|
4144
|
+
box-shadow: 0 4px 14px rgb(15 42 54 / 24%);
|
|
4145
|
+
cursor: pointer;
|
|
4146
|
+
pointer-events: auto;
|
|
4147
|
+
transition: background-color 120ms ease-out, box-shadow 120ms ease-out, transform 120ms ease-out;
|
|
4148
|
+
}
|
|
4149
|
+
button:hover { background: #315d71; box-shadow: 0 6px 18px rgb(15 42 54 / 30%); }
|
|
4150
|
+
button:active { transform: translateY(1px); }
|
|
4151
|
+
button:focus-visible { outline: 3px solid #f2c94c; outline-offset: 3px; }
|
|
4152
|
+
button:disabled { cursor: wait; opacity: .72; }
|
|
4153
|
+
.status {
|
|
4154
|
+
position: absolute;
|
|
4155
|
+
right: 62px;
|
|
4156
|
+
bottom: 7px;
|
|
4157
|
+
width: max-content;
|
|
4158
|
+
max-width: min(260px, calc(100vw - 94px));
|
|
4159
|
+
padding: 9px 11px;
|
|
4160
|
+
border: 1px solid #c6d3d9;
|
|
4161
|
+
border-radius: 6px;
|
|
4162
|
+
background: #fff;
|
|
4163
|
+
box-shadow: 0 3px 12px rgb(15 42 54 / 16%);
|
|
4164
|
+
color: #263942;
|
|
4165
|
+
font: 600 13px/1.35 "Segoe UI", Arial, sans-serif;
|
|
4166
|
+
pointer-events: none;
|
|
4167
|
+
}
|
|
4168
|
+
.status:empty { display: none; }
|
|
4169
|
+
svg { width: 25px; height: 25px; }
|
|
4170
|
+
@media (max-width: 600px) { :host { right: 12px; bottom: 12px; } }
|
|
4171
|
+
@media (prefers-reduced-motion: reduce) { button { transition: none; } }
|
|
4172
|
+
</style>
|
|
4173
|
+
<button type="button" aria-label="Open Datalynk Chat" title="Chat">
|
|
4174
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
4175
|
+
<path fill="currentColor" d="M4 4h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9l-5 3v-3a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Zm2.5 8.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Zm5.5 0a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Zm5.5 0a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z"/>
|
|
4176
|
+
</svg>
|
|
4177
|
+
</button>
|
|
4178
|
+
<span class="status" role="status" aria-live="polite"></span>`;
|
|
4179
|
+
const button = root.querySelector("button");
|
|
4180
|
+
button.addEventListener("click", () => void this.openChatFromLauncher());
|
|
4181
|
+
document.body.appendChild(host);
|
|
4182
|
+
this.chatLauncherHost = host;
|
|
4183
|
+
this.chatLauncherButton = button;
|
|
4184
|
+
};
|
|
4185
|
+
if (document.body) mount();
|
|
4186
|
+
else document.addEventListener("DOMContentLoaded", mount, { once: true });
|
|
4187
|
+
}
|
|
4188
|
+
async openChatFromLauncher() {
|
|
4189
|
+
const button = this.chatLauncherButton;
|
|
4190
|
+
if (!button) return;
|
|
4191
|
+
const status = button.getRootNode().querySelector(".status");
|
|
4192
|
+
button.disabled = true;
|
|
4193
|
+
button.setAttribute("aria-busy", "true");
|
|
4194
|
+
status.textContent = "Opening chat…";
|
|
4195
|
+
try {
|
|
4196
|
+
await this.openChat();
|
|
4197
|
+
status.textContent = "";
|
|
4198
|
+
} catch (error) {
|
|
4199
|
+
button.disabled = false;
|
|
4200
|
+
button.removeAttribute("aria-busy");
|
|
4201
|
+
status.textContent = "Chat is unavailable for this session.";
|
|
4202
|
+
console.error("Unable to open Datalynk Chat.", error);
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
restoreChatLauncher() {
|
|
4206
|
+
if (!this.chatLauncherHost || !this.chatLauncherButton) return;
|
|
4207
|
+
this.chatLauncherHost.hidden = false;
|
|
4208
|
+
this.chatLauncherButton.disabled = false;
|
|
4209
|
+
this.chatLauncherButton.removeAttribute("aria-busy");
|
|
4210
|
+
this.chatLauncherButton.focus();
|
|
4211
|
+
}
|
|
4212
|
+
/** Lazily load and construct chat. No chat code or requests run before this call. */
|
|
4213
|
+
async chat() {
|
|
4214
|
+
if (this.chatInstance) return this.chatInstance;
|
|
4215
|
+
const client = await this.loadChatModule();
|
|
4216
|
+
return this.chatInstance || (this.chatInstance = new client.Chat(this));
|
|
4217
|
+
}
|
|
4218
|
+
/** Lazily load, authorize, and open the reusable chat window. */
|
|
4219
|
+
async openChat(options = {}) {
|
|
4220
|
+
const client = await this.loadChatModule();
|
|
4221
|
+
const chat = await this.chat();
|
|
4222
|
+
if (!await chat.available()) throw new Error("Chat is disabled or this session is not authorized.");
|
|
4223
|
+
if (this.socket.options.authentication !== "ticket") {
|
|
4224
|
+
this.socket.options.authentication = "ticket";
|
|
4225
|
+
void this.socket.connect();
|
|
4226
|
+
}
|
|
4227
|
+
const onClose = options.onClose;
|
|
4228
|
+
const chatWindow = client.openChat(chat, {
|
|
4229
|
+
...options,
|
|
4230
|
+
onClose: () => {
|
|
4231
|
+
this.restoreChatLauncher();
|
|
4232
|
+
onClose == null ? void 0 : onClose();
|
|
4233
|
+
}
|
|
4234
|
+
});
|
|
4235
|
+
if (this.chatLauncherHost) this.chatLauncherHost.hidden = true;
|
|
4236
|
+
return chatWindow;
|
|
4237
|
+
}
|
|
4238
|
+
loadChatModule() {
|
|
4239
|
+
if (!this.chatModulePromise) {
|
|
4240
|
+
const moduleUrl = this.options.chatModule || "./chat.mjs";
|
|
4241
|
+
this.chatModulePromise = import(
|
|
4242
|
+
/* @vite-ignore */
|
|
4243
|
+
moduleUrl
|
|
4244
|
+
);
|
|
4245
|
+
}
|
|
4246
|
+
return this.chatModulePromise;
|
|
4247
|
+
}
|
|
4021
4248
|
async _request(req, options = {}) {
|
|
4022
4249
|
if (this.recovery) throw errorFromCode(503, "Datalynk is unavailable");
|
|
4023
4250
|
const retryRequest = deepCopy(req);
|
|
@@ -4034,7 +4261,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4034
4261
|
/** Execute exactly one HTTP API attempt. Recovery uses this directly to avoid recursive retry loops. */
|
|
4035
4262
|
async _requestOnce(req, options = {}, recoveryAttempt = false) {
|
|
4036
4263
|
const token = options.token || this.token;
|
|
4037
|
-
if (token && this.isTokenExpired(token)) {
|
|
4264
|
+
if (this.options.manageSession !== false && token && this.isTokenExpired(token)) {
|
|
4038
4265
|
if (token === this.token) this.expireToken(token);
|
|
4039
4266
|
throw errorFromCode(401, "Session token expired");
|
|
4040
4267
|
}
|
|
@@ -4201,7 +4428,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4201
4428
|
}
|
|
4202
4429
|
return;
|
|
4203
4430
|
}
|
|
4204
|
-
if (this.token && this.isTokenExpired(this.token)) {
|
|
4431
|
+
if (this.options.manageSession !== false && this.token && this.isTokenExpired(this.token)) {
|
|
4205
4432
|
this.expireToken(this.token);
|
|
4206
4433
|
return;
|
|
4207
4434
|
}
|
|
@@ -4320,6 +4547,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4320
4547
|
}
|
|
4321
4548
|
async finishUnauthorizedLogout(preservePending = false) {
|
|
4322
4549
|
var _a;
|
|
4550
|
+
if (this.options.manageSession === false) return;
|
|
4323
4551
|
if (typeof localStorage != "undefined") {
|
|
4324
4552
|
localStorage.removeItem(this.localStorageKey);
|
|
4325
4553
|
localStorage.removeItem("datalynk-user");
|