@ztimson/momentum 1.5.6 → 1.5.8

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/index.js CHANGED
@@ -1137,34 +1137,88 @@
1137
1137
  };
1138
1138
  var PromiseProgress = class PromiseProgress extends Promise {
1139
1139
  listeners = [];
1140
- _progress = 0;
1140
+ _meta = {
1141
+ start: Date.now(),
1142
+ end: null,
1143
+ elapsed: 0,
1144
+ speed: 0,
1145
+ progress: 0,
1146
+ size: 0,
1147
+ total: 0,
1148
+ eta: null
1149
+ };
1150
+ get meta() {
1151
+ return this._meta;
1152
+ }
1153
+ get start() {
1154
+ return this._meta.start;
1155
+ }
1156
+ get end() {
1157
+ return this._meta.end;
1158
+ }
1159
+ get elapsed() {
1160
+ return this._meta.elapsed;
1161
+ }
1162
+ get speed() {
1163
+ return this._meta.speed;
1164
+ }
1141
1165
  get progress() {
1142
- return this._progress;
1166
+ return this._meta.progress;
1143
1167
  }
1144
- set progress(p) {
1145
- if (p == this._progress) return;
1146
- this._progress = p;
1147
- this.listeners.forEach((l) => l(p));
1168
+ get size() {
1169
+ return this._meta.size;
1170
+ }
1171
+ get total() {
1172
+ return this._meta.total;
1173
+ }
1174
+ get eta() {
1175
+ return this._meta.eta;
1176
+ }
1177
+ update(size, total) {
1178
+ const elapsed = (Date.now() - this._meta.start) / 1e3;
1179
+ const speed = elapsed ? size / elapsed : 0;
1180
+ const progress = total ? size / total : this._meta.progress;
1181
+ this._meta = {
1182
+ ...this._meta,
1183
+ size,
1184
+ total,
1185
+ progress,
1186
+ elapsed,
1187
+ speed,
1188
+ eta: speed && total ? (total - size) / speed : null
1189
+ };
1190
+ (this.listeners ||= []).forEach((l) => l(this._meta));
1148
1191
  }
1149
1192
  constructor(executor) {
1150
- super((resolve, reject) => executor((value) => resolve(value), (reason) => reject(reason), (progress) => this.progress = progress));
1193
+ super((resolve, reject) => executor((value) => {
1194
+ this._meta = {
1195
+ ...this._meta,
1196
+ end: Date.now()
1197
+ };
1198
+ resolve(value);
1199
+ }, (reason) => {
1200
+ this._meta = {
1201
+ ...this._meta,
1202
+ end: Date.now()
1203
+ };
1204
+ reject(reason);
1205
+ }, (size, total) => this.update(size, total ?? this._meta.total)));
1151
1206
  }
1152
1207
  static from(promise) {
1153
1208
  if (promise instanceof PromiseProgress) return promise;
1154
- return new PromiseProgress((res, rej) => promise.then((...args) => res(...args)).catch((...args) => rej(...args)));
1209
+ return new PromiseProgress((res, rej) => promise.then(res).catch(rej));
1155
1210
  }
1156
1211
  from(promise) {
1157
1212
  const newPromise = PromiseProgress.from(promise);
1158
- this.onProgress((p) => newPromise.progress = p);
1213
+ this.onProgress((m) => newPromise.update(m.size, m.total));
1159
1214
  return newPromise;
1160
1215
  }
1161
1216
  onProgress(callback) {
1162
- this.listeners.push(callback);
1217
+ (this.listeners ||= []).push(callback);
1163
1218
  return this;
1164
1219
  }
1165
1220
  then(res, rej) {
1166
- const resp = super.then(res, rej);
1167
- return this.from(resp);
1221
+ return this.from(super.then(res, rej));
1168
1222
  }
1169
1223
  catch(rej) {
1170
1224
  return this.from(super.catch(rej));
@@ -1213,7 +1267,7 @@
1213
1267
  const formData2 = new FormData();
1214
1268
  options.files.forEach((f) => formData2.append("files", f));
1215
1269
  xhr.withCredentials = !!options.withCredentials;
1216
- xhr.upload.addEventListener("progress", (event) => event.lengthComputable ? prog(event.loaded / event.total) : null);
1270
+ xhr.upload.addEventListener("progress", (event) => event.lengthComputable ? prog(event.loaded, event.total) : null);
1217
1271
  xhr.addEventListener("loadend", () => res(JSONAttemptParse(xhr.responseText)));
1218
1272
  xhr.addEventListener("error", () => rej(JSONAttemptParse(xhr.responseText)));
1219
1273
  xhr.addEventListener("timeout", () => rej({ error: "Request timed out" }));
@@ -2050,6 +2104,24 @@
2050
2104
  };
2051
2105
  //#endregion
2052
2106
  //#region src/ai.ts
2107
+ var TEXT_FILE = /^text\/|\.(txt|md|csv|json|xml|ya?ml|html?|js|ts|py|log)$/i;
2108
+ async function toFileUpload(f) {
2109
+ if (TEXT_FILE.test(f.type) || TEXT_FILE.test(f.name)) return {
2110
+ name: f.name,
2111
+ mime: f.type,
2112
+ content: await f.text()
2113
+ };
2114
+ const dataUrl = await new Promise((res) => {
2115
+ const reader = new FileReader();
2116
+ reader.onload = () => res(reader.result);
2117
+ reader.readAsDataURL(f);
2118
+ });
2119
+ return {
2120
+ name: f.name,
2121
+ mime: f.type,
2122
+ content: dataUrl.split(",")[1]
2123
+ };
2124
+ }
2053
2125
  /** Manage and render templates */
2054
2126
  var Agents = class extends AssetController {
2055
2127
  momentum;
@@ -2063,6 +2135,103 @@
2063
2135
  }
2064
2136
  };
2065
2137
  /** Manage and render templates */
2138
+ var Conversations = class extends AssetController {
2139
+ momentum;
2140
+ constructor(momentum) {
2141
+ super(momentum, {
2142
+ module: "ai",
2143
+ path: "ai/conversations",
2144
+ key: "_id"
2145
+ });
2146
+ this.momentum = momentum;
2147
+ }
2148
+ /** List all conversations; omit owner to list the current session's own */
2149
+ list(owner, reload) {
2150
+ if (!owner) return super.all(reload);
2151
+ return this.momentum.api.request({ url: `api/${this.opts.path}/${owner}` }).then((resp) => {
2152
+ this.emit(PES`${this.opts.module}/${owner}:r`, resp);
2153
+ return resp;
2154
+ });
2155
+ }
2156
+ /** Fetch a single conversation, including full history */
2157
+ read(id, reload) {
2158
+ const cached = !reload ? this.cache.find({ _id: id }) : null;
2159
+ if (cached) return Promise.resolve(cached);
2160
+ return this.momentum.api.request({ url: `api/${this.opts.path}/${id}/history` }).then((resp) => {
2161
+ this.cache.add(resp);
2162
+ this.emit(PES`${this.opts.module}/${id}:r`, resp);
2163
+ return resp;
2164
+ });
2165
+ }
2166
+ /** Create a new conversation; pass owner to create on behalf of another user */
2167
+ create(value = {}, owner) {
2168
+ const url = owner ? `api/${this.opts.path}/${owner}` : `api/${this.opts.path}`;
2169
+ return this.momentum.api.request({
2170
+ url,
2171
+ method: "POST",
2172
+ body: { title: value.title }
2173
+ }).then((resp) => {
2174
+ this.cache.add(resp);
2175
+ this.emit(PES`${this.opts.module}/${resp._id}:c`, resp);
2176
+ return resp;
2177
+ });
2178
+ }
2179
+ /** Delete a conversation */
2180
+ async delete(id) {
2181
+ await this.momentum.api.request({
2182
+ url: `api/${this.opts.path}/${id}`,
2183
+ method: "DELETE"
2184
+ });
2185
+ this.cache.delete(id);
2186
+ this.emit(PES`${this.opts.module}/${id}:d`, id);
2187
+ return 1;
2188
+ }
2189
+ /** Rename and/or pin a conversation */
2190
+ rename(id, title) {
2191
+ return this.update({
2192
+ _id: id,
2193
+ title
2194
+ });
2195
+ }
2196
+ /** Pin/unpin a conversation */
2197
+ pin(id, pinned = true) {
2198
+ return this.update({
2199
+ _id: id,
2200
+ pinned
2201
+ });
2202
+ }
2203
+ /** Cancel any in-flight request for this conversation */
2204
+ abort(id) {
2205
+ return this.momentum.api.request({
2206
+ url: `api/${this.opts.path}/${id}/abort`,
2207
+ method: "POST"
2208
+ });
2209
+ }
2210
+ /** Delete a message and everything that follows it from a conversation's history (no LLM call) */
2211
+ deleteMessage(id, index) {
2212
+ return this.momentum.api.request({
2213
+ url: `api/${this.opts.path}/${id}/${index}`,
2214
+ method: "DELETE"
2215
+ }).then((resp) => {
2216
+ this.cache.add(resp);
2217
+ this.emit(PES`${this.opts.module}/${id}:u`, resp);
2218
+ return resp;
2219
+ });
2220
+ }
2221
+ /** Fork a conversation's history up to `index` into a new conversation titled `Forked: <title>` */
2222
+ fork(id, index) {
2223
+ return this.momentum.api.request({
2224
+ url: `api/${this.opts.path}/${id}/fork`,
2225
+ method: "POST",
2226
+ body: { index }
2227
+ }).then((resp) => {
2228
+ this.cache.add(resp);
2229
+ this.emit(PES`${this.opts.module}/${resp._id}:c`, resp);
2230
+ return resp;
2231
+ });
2232
+ }
2233
+ };
2234
+ /** Manage and render templates */
2066
2235
  var Skills = class extends AssetController {
2067
2236
  momentum;
2068
2237
  constructor(momentum) {
@@ -2078,49 +2247,41 @@
2078
2247
  var Ai = class extends PathEventEmitter {
2079
2248
  momentum;
2080
2249
  agents;
2250
+ conversations;
2081
2251
  skills;
2082
2252
  constructor(momentum) {
2083
2253
  super("ai");
2084
2254
  this.momentum = momentum;
2085
2255
  this.agents = new Agents(momentum);
2256
+ this.conversations = new Conversations(momentum);
2086
2257
  this.skills = new Skills(momentum);
2087
2258
  this.relayEvents(this.agents);
2259
+ this.relayEvents(this.conversations);
2088
2260
  this.relayEvents(this.skills);
2089
2261
  }
2090
- /** Cancel current AI requests */
2091
- abort() {
2092
- return this.momentum.api.request({
2093
- url: "/api/ai/abort",
2094
- method: "POST"
2095
- });
2096
- }
2097
2262
  /**
2098
2263
  * Ask the AI assistant a question
2099
2264
  * @param {string} question Users question
2100
- * @param {AiOptions & {stream: Function}} options context - Any hidden context information. stream - Receive response in chunks
2265
+ * @param {AiOptions & {stream: Function}} options Pass a conversationId to persist/continue a thread; omit for a one-off.
2266
+ * Pass messageIndex to edit (new text) or retry (same text) a prior exchange - history is truncated to that point first.
2101
2267
  * @return {Promise<string>} AI's response
2102
2268
  */
2103
2269
  async ask(question, options = {}) {
2104
2270
  if (!question) throw new Error("Cannot ask AI, missing question");
2105
- const files = (await Promise.all((options?.files || []).map((f) => new Promise((res) => {
2106
- const reader = new FileReader();
2107
- reader.onload = () => res([f.name, reader.result]);
2108
- reader.readAsDataURL(f);
2109
- })))).reduce((acc, f) => ({
2110
- ...acc,
2111
- [f[0]]: f[1]
2112
- }), {});
2271
+ const { stream, files: rawFiles, ...rest } = options;
2272
+ const files = await Promise.all((rawFiles || []).map(toFileUpload));
2113
2273
  const q = {
2114
- ...options,
2274
+ ...rest,
2115
2275
  question: question.trim(),
2116
- files: Object.keys(files).length ? files : void 0
2276
+ files: files.length ? files : void 0
2117
2277
  };
2118
- if (options.stream && this.momentum.socket?.connected) {
2278
+ if (!q.conversationId) delete q.conversationId;
2279
+ if (stream && this.momentum.socket?.connected) {
2119
2280
  let unsub = () => {};
2120
2281
  return new Promise((res) => {
2121
2282
  let buffer = "";
2122
2283
  unsub = this.momentum.socket?.on("llm", (event, chunk) => {
2123
- options.stream?.(chunk);
2284
+ stream(chunk);
2124
2285
  if (chunk.text) buffer += chunk.text;
2125
2286
  if (chunk.done) res(buffer);
2126
2287
  });
@@ -2136,34 +2297,190 @@
2136
2297
  });
2137
2298
  }
2138
2299
  /**
2139
- * Clear AI assistant memory & context
2140
- * @return {Promise<void>} Resolves once complete
2141
- */
2142
- clear() {
2143
- return this.momentum.api.request({
2144
- url: "api/ai",
2145
- method: "DELETE"
2146
- }).then(() => this.emit(PES`:d`, this.momentum.api.token));
2147
- }
2148
- /**
2149
- * Current chat history
2150
- * @return {Promise<AiMessage[]>}
2151
- */
2152
- history() {
2153
- return this.momentum.api.request({ url: "api/ai" }).then((resp) => {
2154
- this.emit(PES`:r`, resp);
2155
- return resp;
2156
- });
2157
- }
2158
- /**
2159
2300
  * Get model info
2160
- * @return {AiInfo>} Model Info
2301
+ * @return {Promise<{[name: string]: string}>} Model Info
2161
2302
  */
2162
2303
  tools() {
2163
2304
  return this.momentum.api.request({ url: "api/ai/tools" });
2164
2305
  }
2165
2306
  };
2166
2307
  //#endregion
2308
+ //#region src/captcha.ts
2309
+ var Captcha = class {
2310
+ start = Date.now();
2311
+ events = {
2312
+ mouse: [],
2313
+ keys: [],
2314
+ clicks: [],
2315
+ scroll: [],
2316
+ focus: []
2317
+ };
2318
+ lastMouse = null;
2319
+ velocities = [];
2320
+ _score = .5;
2321
+ onScoreChange;
2322
+ set score(v) {
2323
+ const next = +Math.max(0, Math.min(1, Number.isFinite(v) ? v : .5)).toFixed(2);
2324
+ if (next == this._score) return;
2325
+ this._score = next;
2326
+ this.onScoreChange?.(next);
2327
+ }
2328
+ get score() {
2329
+ return this._score;
2330
+ }
2331
+ get verdict() {
2332
+ if (this.score >= .45 && this.score <= .55) return "unknown";
2333
+ if (this.score > .5) return "human";
2334
+ return "bot";
2335
+ }
2336
+ constructor(onScoreChange) {
2337
+ this.onScoreChange = onScoreChange;
2338
+ if (typeof window === "undefined" || !document) {
2339
+ this._score = 0;
2340
+ return;
2341
+ }
2342
+ document.addEventListener("pointermove", (e) => {
2343
+ const now = Date.now();
2344
+ const curr = {
2345
+ x: e.clientX,
2346
+ y: e.clientY,
2347
+ t: now
2348
+ };
2349
+ this.events.mouse.push(curr);
2350
+ if (this.lastMouse) {
2351
+ const dt = now - this.lastMouse.t;
2352
+ const dx = curr.x - this.lastMouse.x;
2353
+ const dy = curr.y - this.lastMouse.y;
2354
+ const dist = Math.sqrt(dx * dx + dy * dy);
2355
+ if (dt > 0 && dt < 1e3) this.velocities.push(dist / dt);
2356
+ }
2357
+ this.lastMouse = curr;
2358
+ if (this.events.mouse.length > 80) this.events.mouse.shift();
2359
+ if (this.velocities.length > 40) this.velocities.shift();
2360
+ }, { passive: true });
2361
+ document.addEventListener("keydown", () => {
2362
+ this.events.keys.push(Date.now());
2363
+ if (this.events.keys.length > 30) this.events.keys.shift();
2364
+ this.update();
2365
+ });
2366
+ document.addEventListener("pointerdown", (e) => {
2367
+ this.events.clicks.push({
2368
+ x: e.clientX,
2369
+ y: e.clientY,
2370
+ t: Date.now()
2371
+ });
2372
+ if (this.events.clicks.length > 20) this.events.clicks.shift();
2373
+ this.update();
2374
+ }, { passive: true });
2375
+ window.addEventListener("scroll", () => {
2376
+ this.events.scroll.push({
2377
+ x: window.scrollX,
2378
+ y: window.scrollY,
2379
+ t: Date.now()
2380
+ });
2381
+ if (this.events.scroll.length > 20) this.events.scroll.shift();
2382
+ }, { passive: true });
2383
+ document.addEventListener("visibilitychange", () => {
2384
+ this.events.focus.push({
2385
+ visible: document.visibilityState == "visible",
2386
+ t: Date.now()
2387
+ });
2388
+ if (this.events.focus.length > 20) this.events.focus.shift();
2389
+ this.update();
2390
+ });
2391
+ window.addEventListener("focus", () => {
2392
+ this.events.focus.push({
2393
+ visible: true,
2394
+ t: Date.now()
2395
+ });
2396
+ this.update();
2397
+ });
2398
+ window.addEventListener("blur", () => {
2399
+ this.events.focus.push({
2400
+ visible: false,
2401
+ t: Date.now()
2402
+ });
2403
+ this.update();
2404
+ });
2405
+ setInterval(() => this.update(), 2e3);
2406
+ }
2407
+ update() {
2408
+ if (navigator?.webdriver) {
2409
+ this._score = 0;
2410
+ this.onScoreChange?.(0);
2411
+ return;
2412
+ }
2413
+ let delta = 0;
2414
+ const elapsed = (Date.now() - this.start) / 1e3;
2415
+ delta += elapsed > 30 ? .08 : elapsed > 10 ? .04 : elapsed > 3 ? .01 : -.05;
2416
+ if (this.events.mouse.length >= 6) {
2417
+ const recent = this.events.mouse.slice(-30);
2418
+ const buckets = new Array(8).fill(0);
2419
+ for (let i = 1; i < recent.length; i++) {
2420
+ const dx = recent[i].x - recent[i - 1].x;
2421
+ const dy = recent[i].y - recent[i - 1].y;
2422
+ if (dx == 0 && dy == 0) continue;
2423
+ let angle = Math.atan2(dy, dx);
2424
+ if (angle < 0) angle += Math.PI * 2;
2425
+ buckets[Math.min(7, Math.floor(angle / (Math.PI * 2) * 8))]++;
2426
+ }
2427
+ const total = buckets.reduce((a, b) => a + b, 0);
2428
+ if (total >= 4) {
2429
+ let entropy = 0;
2430
+ for (const count of buckets) {
2431
+ if (!count) continue;
2432
+ const p = count / total;
2433
+ entropy -= p * Math.log2(p);
2434
+ }
2435
+ entropy /= Math.log2(8);
2436
+ if (entropy > .65) delta += .12;
2437
+ else if (entropy > .4) delta += .05;
2438
+ else delta -= .07;
2439
+ }
2440
+ } else if (this.events.mouse.length == 0) delta -= .01;
2441
+ if (this.velocities.length >= 5) {
2442
+ const avg = this.velocities.reduce((a, b) => a + b, 0) / this.velocities.length;
2443
+ const variance = this.velocities.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / this.velocities.length;
2444
+ const cv = avg > 0 ? Math.sqrt(variance) / avg : 0;
2445
+ if (cv > .45) delta += .1;
2446
+ else if (cv > .18) delta += .04;
2447
+ else if (avg > 0) delta -= .08;
2448
+ }
2449
+ if (this.events.clicks.length > 0) {
2450
+ const lastClick = this.events.clicks.at(-1);
2451
+ const mouseBeforeClick = this.events.mouse.some((m) => m.t < lastClick.t && lastClick.t - m.t < 2e3);
2452
+ delta += mouseBeforeClick ? .06 : -.08;
2453
+ const suspiciousClicks = this.events.clicks.filter((c) => Number.isInteger(c.x) && Number.isInteger(c.y) && c.x % 10 == 0 && c.y % 10 == 0).length;
2454
+ if (this.events.clicks.length >= 4 && suspiciousClicks / this.events.clicks.length > .5) delta -= .06;
2455
+ }
2456
+ if (this.events.keys.length >= 4) {
2457
+ const intervals = [];
2458
+ for (let i = 1; i < this.events.keys.length; i++) intervals.push(this.events.keys[i] - this.events.keys[i - 1]);
2459
+ const avg = intervals.reduce((a, b) => a + b, 0) / intervals.length;
2460
+ const stdDev = Math.sqrt(intervals.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / intervals.length);
2461
+ if (avg < 20) delta -= .15;
2462
+ else if (stdDev < 5) delta -= .1;
2463
+ else if (avg < 50) delta -= .04;
2464
+ else if (avg < 1e3 && stdDev > 20) delta += .08;
2465
+ }
2466
+ if (this.events.scroll.length >= 4) {
2467
+ let directionChanges = 0;
2468
+ let previousDirection = 0;
2469
+ for (let i = 1; i < this.events.scroll.length; i++) {
2470
+ const direction = Math.sign(this.events.scroll[i].y - this.events.scroll[i - 1].y);
2471
+ if (direction && previousDirection && direction != previousDirection) directionChanges++;
2472
+ if (direction) previousDirection = direction;
2473
+ }
2474
+ if (directionChanges >= 2) delta += .04;
2475
+ }
2476
+ if (this.events.focus.length >= 2) {
2477
+ const visible = this.events.focus.filter((x) => x.visible).length;
2478
+ delta += visible / this.events.focus.length > .4 ? .02 : -.02;
2479
+ }
2480
+ this.score = this.score + delta * .25;
2481
+ }
2482
+ };
2483
+ //#endregion
2167
2484
  //#region src/dialog.ts
2168
2485
  var dialogCount = 0;
2169
2486
  var closed = 0;
@@ -2376,6 +2693,16 @@
2376
2693
  var Analytics = class extends AssetController {
2377
2694
  momentum;
2378
2695
  lastPage;
2696
+ captcha = new Captcha((score) => this.updateCaptcha(score));
2697
+ captchaSecret;
2698
+ captchaSequence = 0;
2699
+ captchaUpdate;
2700
+ get captchaScore() {
2701
+ return this.captcha.score;
2702
+ }
2703
+ get actorType() {
2704
+ return this.captcha.verdict;
2705
+ }
2379
2706
  _ready;
2380
2707
  ready = new Promise((res) => this._ready = res);
2381
2708
  _performance = {
@@ -2454,6 +2781,9 @@
2454
2781
  this._id = "";
2455
2782
  this._consent = void 0;
2456
2783
  this._new = true;
2784
+ this.captchaSecret = void 0;
2785
+ this.captchaSequence = 0;
2786
+ delete this.momentum.api.headers["X-Captcha"];
2457
2787
  });
2458
2788
  this.momentum.auth.on("login:c", async () => {
2459
2789
  if (this.consent) await this.init();
@@ -2530,7 +2860,7 @@
2530
2860
  async init() {
2531
2861
  if (!this.momentum.opts.analytics) return;
2532
2862
  const performance = await this.performance;
2533
- this.id = await this.momentum.api.request({
2863
+ const response = await this.momentum.api.request({
2534
2864
  url: `api/analytics`,
2535
2865
  body: [
2536
2866
  navigator?.language,
@@ -2548,6 +2878,10 @@
2548
2878
  performance.total
2549
2879
  ]
2550
2880
  });
2881
+ this.id = response.id;
2882
+ this.captchaSecret = response.captchaSecret;
2883
+ this.captchaSequence = response.captchaSequence;
2884
+ await this.updateCaptcha(this.captcha.score, true);
2551
2885
  if (this._ready) {
2552
2886
  this._ready();
2553
2887
  this._ready = null;
@@ -2557,6 +2891,25 @@
2557
2891
  window.addEventListener("beforeunload", () => this.page("CLOSED"));
2558
2892
  return this.id;
2559
2893
  }
2894
+ updateCaptcha(score, initial = false) {
2895
+ if (!this.captchaSecret || !this.id || !globalThis.crypto?.subtle) return Promise.resolve();
2896
+ const update = this.captchaUpdate?.catch(() => {}) || Promise.resolve();
2897
+ this.captchaUpdate = update.then(() => this.attestCaptcha(score, initial)).catch(() => {});
2898
+ return this.captchaUpdate;
2899
+ }
2900
+ async attestCaptcha(score, initial) {
2901
+ if (!this.captchaSecret || !this.id) return;
2902
+ if (!initial) this.captchaSequence++;
2903
+ const canonical = Math.max(0, Math.min(1, Number.isFinite(score) ? score : .5)).toFixed(2);
2904
+ const payload = `${canonical}:${this.captchaSequence}`;
2905
+ const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(this.captchaSecret), {
2906
+ name: "HMAC",
2907
+ hash: "SHA-256"
2908
+ }, false, ["sign"]);
2909
+ const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload));
2910
+ const proof = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2911
+ this.momentum.api.headers["X-Captcha"] = `${canonical},${this.captchaSequence},${proof}`;
2912
+ }
2560
2913
  async consentPrompt(content) {
2561
2914
  const settings = await this.momentum.settings.map();
2562
2915
  const { close, dialog, result } = createDialog(`
@@ -2930,19 +3283,29 @@
2930
3283
  loginRedirect(host = location.origin) {
2931
3284
  return new Promise((res, rej) => {
2932
3285
  let listener, win;
3286
+ const loginOrigin = new URL(this.momentum.opts.loginUrl, location.href).origin;
3287
+ const hostOrigin = new URL(host, location.href).origin;
2933
3288
  window.addEventListener("message", listener = async (event) => {
3289
+ if (event.origin !== loginOrigin || event?.data?.sender !== loginOrigin) return;
2934
3290
  const data = event?.data || {};
2935
- if (event.origin != origin || data.sender != origin) return;
2936
- if (!data.token) return rej("Unknown response from login");
3291
+ if (!data.token) {
3292
+ window.removeEventListener("message", listener);
3293
+ return rej(/* @__PURE__ */ new Error("Unknown response from login"));
3294
+ }
2937
3295
  window.removeEventListener("message", listener);
2938
- await this.readSession(data.token, true);
2939
- win.close();
2940
- res(data.token);
3296
+ try {
3297
+ await this.readSession(data.token, true);
3298
+ win?.close();
3299
+ res(data.token);
3300
+ } catch (err) {
3301
+ win?.close();
3302
+ rej(err);
3303
+ }
2941
3304
  });
2942
- win = window.open(`${this.momentum.opts.loginUrl}?redirect=postmessage&host=${encodeURI(host)}`, "_blank");
3305
+ win = window.open(`${this.momentum.opts.loginUrl}?redirect=postmessage&host=${encodeURIComponent(hostOrigin)}`, "_blank");
2943
3306
  if (!win) {
2944
3307
  window.removeEventListener("message", listener);
2945
- return rej("Unable to open login");
3308
+ return rej(/* @__PURE__ */ new Error("Unable to open login"));
2946
3309
  }
2947
3310
  });
2948
3311
  }
@@ -3068,174 +3431,20 @@
3068
3431
  }
3069
3432
  };
3070
3433
  //#endregion
3071
- //#region src/captcha.ts
3072
- var Captcha = class {
3073
- start = Date.now();
3074
- events = {
3075
- mouse: [],
3076
- keys: [],
3077
- clicks: []
3078
- };
3079
- lastMouse = null;
3080
- velocities = [];
3081
- _score = .5;
3082
- set score(v) {
3083
- this._score = v;
3084
- }
3085
- get score() {
3086
- return this._score;
3087
- }
3088
- get verdict() {
3089
- if (this.score == .5) return "unknown";
3090
- if (this.score > .5) return "human";
3091
- return "bot";
3092
- }
3093
- constructor() {
3094
- if (typeof window === "undefined" || !window.innerWidth || !this.validUserAgent()) {
3095
- this.score = 0;
3096
- return;
3097
- }
3098
- document.addEventListener("mousemove", (e) => {
3099
- const now = Date.now();
3100
- const curr = {
3101
- x: e.clientX,
3102
- y: e.clientY,
3103
- t: now
3104
- };
3105
- this.events.mouse.push(curr);
3106
- if (this.lastMouse) {
3107
- const dt = now - this.lastMouse.t;
3108
- const dx = curr.x - this.lastMouse.x;
3109
- const dy = curr.y - this.lastMouse.y;
3110
- const dist = Math.sqrt(dx * dx + dy * dy);
3111
- if (dt > 0) this.velocities.push(dist / dt);
3112
- }
3113
- this.lastMouse = curr;
3114
- if (this.events.mouse.length > 50) this.events.mouse.shift();
3115
- if (this.velocities.length > 30) this.velocities.shift();
3116
- });
3117
- document.addEventListener("keydown", (e) => {
3118
- this.events.keys.push(Date.now());
3119
- if (this.events.keys.length > 20) this.events.keys.shift();
3120
- });
3121
- document.addEventListener("click", (e) => {
3122
- this.events.clicks.push({
3123
- x: e.clientX,
3124
- y: e.clientY,
3125
- t: Date.now()
3126
- });
3127
- if (this.events.clicks.length > 15) this.events.clicks.shift();
3128
- });
3129
- setInterval(() => this.update(), 1e3);
3130
- }
3131
- validUserAgent() {
3132
- if (!navigator?.userAgent) return false;
3133
- const ua = navigator.userAgent.toLowerCase();
3134
- if ([
3135
- "bot",
3136
- "crawler",
3137
- "spider",
3138
- "scraper",
3139
- "headless",
3140
- "phantom",
3141
- "selenium",
3142
- "webdriver",
3143
- "puppeteer",
3144
- "playwright",
3145
- "cypress",
3146
- "automation"
3147
- ].some((pattern) => ua.includes(pattern))) return false;
3148
- if (![
3149
- "chrome",
3150
- "firefox",
3151
- "safari",
3152
- "edge",
3153
- "opera"
3154
- ].some((browser) => ua.includes(browser))) return false;
3155
- if (![
3156
- "windows",
3157
- "mac",
3158
- "linux",
3159
- "android",
3160
- "ios"
3161
- ].some((os) => ua.includes(os))) return false;
3162
- return ua.length > 20 && ua.length < 1e3;
3163
- }
3164
- update() {
3165
- if (navigator.webdriver) {
3166
- this.score = 0;
3167
- return;
3168
- }
3169
- let delta = 0;
3170
- const elapsed = (Date.now() - this.start) / 1e3;
3171
- delta += elapsed > 30 ? .08 : elapsed > 10 ? .04 : elapsed > 3 ? .01 : -.05;
3172
- if (this.events.mouse.length >= 5) {
3173
- const recent = this.events.mouse.slice(-20);
3174
- let changes = 0;
3175
- let maxStraightRun = 0;
3176
- let currentRun = 0;
3177
- for (let i = 2; i < recent.length; i++) {
3178
- const a1 = Math.atan2(recent[i - 1].y - recent[i - 2].y, recent[i - 1].x - recent[i - 2].x);
3179
- const a2 = Math.atan2(recent[i].y - recent[i - 1].y, recent[i].x - recent[i - 1].x);
3180
- if (Math.abs(a1 - a2) > .3) {
3181
- changes++;
3182
- currentRun = 0;
3183
- } else {
3184
- currentRun++;
3185
- maxStraightRun = Math.max(maxStraightRun, currentRun);
3186
- }
3187
- }
3188
- const entropy = changes / (recent.length - 2);
3189
- if (entropy > .4) delta += .12;
3190
- else if (entropy > .2) delta += .05;
3191
- else delta -= .08;
3192
- if (maxStraightRun > 8) delta -= .1;
3193
- } else if (this.events.mouse.length === 0) delta -= .03;
3194
- if (this.velocities.length >= 5) {
3195
- const avg = this.velocities.reduce((a, b) => a + b, 0) / this.velocities.length;
3196
- const variance = this.velocities.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / this.velocities.length;
3197
- const cv = avg > 0 ? Math.sqrt(variance) / avg : 0;
3198
- if (cv > .4) delta += .1;
3199
- else if (cv > .15) delta += .04;
3200
- else if (avg > 0) delta -= .1;
3201
- }
3202
- if (this.events.clicks.length > 0) {
3203
- const lastClick = this.events.clicks[this.events.clicks.length - 1];
3204
- const mouseBeforeClick = this.events.mouse.some((m) => m.t < lastClick.t && lastClick.t - m.t < 2e3);
3205
- delta += mouseBeforeClick ? .08 : -.12;
3206
- if (this.events.clicks.filter((c) => Number.isInteger(c.x) && Number.isInteger(c.y) && c.x % 10 === 0 && c.y % 10 === 0).length / this.events.clicks.length > .5) delta -= .08;
3207
- }
3208
- if (this.events.keys.length >= 3) {
3209
- const intervals = [];
3210
- for (let i = 1; i < this.events.keys.length; i++) intervals.push(this.events.keys[i] - this.events.keys[i - 1]);
3211
- const avg = intervals.reduce((a, b) => a + b, 0) / intervals.length;
3212
- const stdDev = Math.sqrt(intervals.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / intervals.length);
3213
- if (avg < 20) delta -= .15;
3214
- else if (stdDev < 5) delta -= .12;
3215
- else if (avg < 50) delta -= .05;
3216
- else if (avg < 1e3 && stdDev > 20) delta += .1;
3217
- }
3218
- const raw = this.score + delta;
3219
- this.score = this.score * .75 + raw * .25;
3220
- this.score = Math.max(.05, Math.min(.95, this.score));
3221
- }
3222
- };
3223
- //#endregion
3224
3434
  //#region src/client.ts
3225
3435
  /** Handle device settings */
3226
3436
  var Client = class Client extends PathEventEmitter {
3227
3437
  momentum;
3228
- captcha = new Captcha();
3229
3438
  installTimeout;
3230
3439
  nativePwaPrompt;
3231
3440
  pendingInstall = null;
3232
3441
  speed;
3233
3442
  updating = false;
3234
3443
  get actorType() {
3235
- return this.captcha.verdict;
3444
+ return this.momentum.analytics.actorType;
3236
3445
  }
3237
3446
  get captchaScore() {
3238
- return this.captcha.score;
3447
+ return this.momentum.analytics.captchaScore;
3239
3448
  }
3240
3449
  /** Check for updates */
3241
3450
  updateReady = new Promise((res) => {
@@ -3381,36 +3590,16 @@
3381
3590
  const t = {
3382
3591
  background: setting?.background,
3383
3592
  backgroundContrast: contrast(setting?.background),
3384
- surface: setting?.darkMode ? "#2e2e2e" : "#ffffff",
3385
- border: setting?.darkMode ? "#374151" : "#E5E7EB",
3386
- text: setting?.darkMode ? "#ffffff" : "#000000",
3387
- muted: setting?.darkMode ? "#cccccc" : "#6c757d",
3388
3593
  primary: setting?.primary,
3389
3594
  primaryContrast: contrast(setting?.primary),
3390
3595
  accent: setting?.accent,
3391
3596
  accentContrast: contrast(setting?.accent),
3392
- info: "#6157cc",
3393
- success: "#65c147",
3394
- warn: "#d88750",
3395
- danger: "#bf4f4f",
3396
- black: "black",
3397
- white: "white",
3398
3597
  font: setting?.font,
3399
- lighter: setting?.darkMode ? "rgba(255, 255, 255, 0.1)" : "rgba(255, 255, 255, 0.05)",
3400
- darker: setting?.darkMode ? "rgba(0, 0, 0, 0.1)" : "rgba(0, 0, 0, 0.05)"
3598
+ radius: "0.5rem"
3401
3599
  };
3402
3600
  if (!css) return t;
3403
- return `:root, :root .theme-${setting?.darkMode ? "dark" : "light"} {
3601
+ return `:root {
3404
3602
  ${Object.entries(t).map(([name, value]) => `--theme-${kebabCase(name)}: ${value} !important;`).join("\n")}
3405
- }
3406
-
3407
- :root .theme-${!setting?.darkMode ? "dark" : "light"} {
3408
- --theme-surface: ${!setting?.darkMode ? "#2e2e2e" : "#ffffff"} !important;
3409
- --theme-border: ${!setting?.darkMode ? "#374151" : "#E5E7EB"} !important;
3410
- --theme-text: ${!setting?.darkMode ? "#ffffff" : "#000000"} !important;
3411
- --theme-muted: ${!setting?.darkMode ? "#cccccc" : "#6c757d"} !important;
3412
- --theme-lighter: ${!setting?.darkMode ? "rgba(255, 255, 255, 0.1)" : "rgba(255, 255, 255, 0.05)"};
3413
- --theme-darker: ${!setting?.darkMode ? "rgba(0, 0, 0, 0.1)" : "rgba(0, 0, 0, 0.05)"};
3414
3603
  }`;
3415
3604
  }
3416
3605
  /**
@@ -4774,7 +4963,7 @@ ${Object.entries(t).map(([name, value]) => `--theme-${kebabCase(name)}: ${value}
4774
4963
  headers: this.momentum.api.headers,
4775
4964
  files: f
4776
4965
  }).onProgress((p) => {
4777
- prog(p);
4966
+ prog(p.progress);
4778
4967
  }).then((resp) => {
4779
4968
  this.emit(PES`${this.module}/${path}:c`, resp);
4780
4969
  res(resp);
@@ -4952,7 +5141,7 @@ ${Object.entries(t).map(([name, value]) => `--theme-${kebabCase(name)}: ${value}
4952
5141
  };
4953
5142
  //#endregion
4954
5143
  //#region package.json
4955
- var version = "1.5.6";
5144
+ var version = "1.5.8";
4956
5145
  //#endregion
4957
5146
  //#region src/webRtc.ts
4958
5147
  var WebRtc = class extends PathEventEmitter {
@@ -5287,6 +5476,7 @@ ${Object.entries(t).map(([name, value]) => `--theme-${kebabCase(name)}: ${value}
5287
5476
  exports.Call = Call;
5288
5477
  exports.Captcha = Captcha;
5289
5478
  exports.Client = Client;
5479
+ exports.Conversations = Conversations;
5290
5480
  exports.Data = Data;
5291
5481
  exports.Discounts = Discounts;
5292
5482
  exports.Email = Email;