claude-threads 1.32.0 → 1.33.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/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.33.0] - 2026-09-02
11
+
12
+ ### Security
13
+ - **fast-uri floored at 4.1.4** (transitive via `ajv`), clearing four fresh high-severity advisories (host confusion and SSRF classes, GHSA-5jgf-p345-68v8 and siblings). Override floor raised from `>=4.1.2`; no direct dependency changes.
14
+
15
+ ### Added
16
+ - **Slack: posts made through the app's own user token count as the person's message** (#527, thanks @kaza). Tooling that posts into the channel via the app's user token (same `app_id` + team, acting user set) previously looked bot-authored and was ignored; such posts now command the bot as the acting user. Authorship is decided only from server-authoritative envelope identity (`app_id` learned from the `hello` frame and per-envelope `api_app_id`) — content-based spoofing cannot trigger it, and events that don't match fail closed to today's behavior. Closes #526.
17
+
18
+ ## [1.32.1] - 2026-09-02
19
+
20
+ ### Changed
21
+ - **Dependency updates.** Production: hono 4.13.5, zod 4.5.4, js-yaml 4.3.2, express-rate-limit 8.7.0 (#522); dev-dependency group refreshed (#521). Validated with the full unit and Slack integration suites.
22
+
10
23
  ## [1.32.0] - 2026-09-01
11
24
 
12
25
  ### Added
package/dist/index.js CHANGED
@@ -71932,6 +71932,8 @@ class SlackClient extends BasePlatformClient {
71932
71932
  userCache = new Map;
71933
71933
  usernameToIdCache = new Map;
71934
71934
  botUserId = null;
71935
+ appId = null;
71936
+ teamId = null;
71935
71937
  botUser = null;
71936
71938
  teamUrl = null;
71937
71939
  lastProcessedTs = null;
@@ -71997,7 +71999,9 @@ class SlackClient extends BasePlatformClient {
71997
71999
  return;
71998
72000
  this.channelClients.delete(channelId);
71999
72001
  }
72000
- _injectSlackEvent(event) {
72002
+ _injectSlackEvent(event, appId) {
72003
+ if (appId)
72004
+ this.appId = appId;
72001
72005
  this.handleSlackEvent(event);
72002
72006
  }
72003
72007
  disconnect() {
@@ -72218,21 +72222,37 @@ class SlackClient extends BasePlatformClient {
72218
72222
  }
72219
72223
  return;
72220
72224
  }
72225
+ if (envelope.type === "hello" && envelope.connection_info?.app_id) {
72226
+ this.appId = envelope.connection_info.app_id;
72227
+ }
72221
72228
  if (envelope.type === "events_api" && envelope.payload?.event) {
72229
+ if (envelope.payload.api_app_id)
72230
+ this.appId = envelope.payload.api_app_id;
72222
72231
  this.handleSlackEvent(envelope.payload.event);
72223
72232
  }
72224
72233
  }
72234
+ isBotAuthored(message) {
72235
+ if (message.user === this.botUserId)
72236
+ return true;
72237
+ if (!message.bot_id)
72238
+ return false;
72239
+ const isOurUserTokenPost = Boolean(this.appId && message.app_id === this.appId && this.teamId && message.team === this.teamId && message.user);
72240
+ if (isOurUserTokenPost) {
72241
+ wsLogger.debug(`Accepting post by ${message.user} made through this app's user token`);
72242
+ }
72243
+ return !isOurUserTokenPost;
72244
+ }
72225
72245
  handleSlackEvent(event) {
72226
72246
  const eventChannel = event.channel || event.item?.channel;
72227
72247
  if (eventChannel && eventChannel !== this.channelId) {
72228
72248
  const secondary = this.channelClients.get(eventChannel);
72229
72249
  if (secondary) {
72230
- secondary._injectSlackEvent(event);
72250
+ secondary._injectSlackEvent(event, this.appId);
72231
72251
  return;
72232
72252
  }
72233
72253
  }
72234
72254
  if (event.type === "message" && (!event.subtype || event.subtype === "file_share")) {
72235
- if (event.user === this.botUserId || event.bot_id) {
72255
+ if (this.isBotAuthored(event)) {
72236
72256
  return;
72237
72257
  }
72238
72258
  if (event.channel !== this.channelId) {
@@ -72332,7 +72352,7 @@ class SlackClient extends BasePlatformClient {
72332
72352
  log40.info(`Recovered ${messages.length} missed message(s)`);
72333
72353
  const sortedMessages = messages.sort((a, b) => parseFloat(a.ts) - parseFloat(b.ts));
72334
72354
  for (const message of sortedMessages) {
72335
- if (message.user === this.botUserId || message.bot_id) {
72355
+ if (this.isBotAuthored(message)) {
72336
72356
  continue;
72337
72357
  }
72338
72358
  this.lastProcessedTs = message.ts;
@@ -72350,6 +72370,7 @@ class SlackClient extends BasePlatformClient {
72350
72370
  async fetchBotUser() {
72351
72371
  const response = await this.api("POST", "auth.test");
72352
72372
  this.botUserId = response.user_id;
72373
+ this.teamId = response.team_id ?? null;
72353
72374
  this.teamUrl = response.url.replace(/\/$/, "");
72354
72375
  const userResponse = await this.api("GET", `users.info?user=${response.user_id}`);
72355
72376
  this.botUser = userResponse.user;
@@ -72559,7 +72580,7 @@ class SlackClient extends BasePlatformClient {
72559
72580
  const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : "";
72560
72581
  const response = await this.api("GET", `conversations.replies?channel=${this.channelId}&ts=${threadId}&limit=1000${cursorParam}`);
72561
72582
  for (const msg of response.messages || []) {
72562
- if (options?.excludeBotMessages && (msg.user === this.botUserId || msg.bot_id))
72583
+ if (options?.excludeBotMessages && this.isBotAuthored(msg))
72563
72584
  continue;
72564
72585
  filtered.push(msg);
72565
72586
  }
@@ -3035,10 +3035,12 @@ var require_data = __commonJS((exports, module) => {
3035
3035
  var require_utils = __commonJS((exports, module) => {
3036
3036
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
3037
3037
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
3038
+ var isPort = RegExp.prototype.test.bind(/^\d*$/u);
3038
3039
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3039
3040
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3040
- var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3041
- var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/?]$/iu);
3041
+ var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
3042
+ var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
3043
+ var isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
3042
3044
  function stringArrayToHexStripped(input) {
3043
3045
  let acc = "";
3044
3046
  let code = 0;
@@ -3063,91 +3065,127 @@ var require_utils = __commonJS((exports, module) => {
3063
3065
  }
3064
3066
  return acc;
3065
3067
  }
3068
+ var isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
3069
+ var isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
3070
+ var isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
3066
3071
  var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
3067
- function consumeIsZone(buffer) {
3068
- buffer.length = 0;
3069
- return true;
3072
+ var nonSimpleDomainChars = RegExp.prototype.test.bind(/[^\d!"$&'()*+,\-.;=_`a-z{}~]/u);
3073
+ var numericLastLabel = RegExp.prototype.test.bind(/(?:^|\.)\d+$/u);
3074
+ function nonSimpleMailtoDomain(domain) {
3075
+ return nonSimpleDomainChars(domain) || numericLastLabel(domain);
3070
3076
  }
3071
- function consumeHextets(buffer, address, output) {
3072
- if (buffer.length) {
3073
- const hex = stringArrayToHexStripped(buffer);
3074
- if (hex !== "") {
3075
- address.push(hex);
3076
- } else {
3077
- output.error = true;
3078
- return false;
3077
+ function isZoneIdentifier(zone) {
3078
+ if (zone.length === 0)
3079
+ return false;
3080
+ for (let i = 0;i < zone.length; i++) {
3081
+ if (isZoneCharacter(zone[i]))
3082
+ continue;
3083
+ if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
3084
+ i += 2;
3085
+ continue;
3079
3086
  }
3080
- buffer.length = 0;
3087
+ return false;
3081
3088
  }
3082
3089
  return true;
3083
3090
  }
3084
- function getIPV6(input) {
3085
- let tokenCount = 0;
3086
- const output = { error: false, address: "", zone: "" };
3087
- const address = [];
3088
- const buffer = [];
3089
- let endipv6Encountered = false;
3090
- let endIpv6 = false;
3091
- let consume = consumeHextets;
3092
- for (let i = 0;i < input.length; i++) {
3093
- const cursor = input[i];
3094
- if (cursor === "[" || cursor === "]") {
3095
- continue;
3096
- }
3097
- if (cursor === ":") {
3098
- if (endipv6Encountered === true) {
3099
- endIpv6 = true;
3100
- }
3101
- if (!consume(buffer, address, output)) {
3102
- break;
3091
+ function compressIPv6ZeroRun(hextets) {
3092
+ let bestStart = -1;
3093
+ let bestLength = 0;
3094
+ let runStart = -1;
3095
+ let runLength = 0;
3096
+ for (let i = 0;i < hextets.length; i++) {
3097
+ if (hextets[i] === "0") {
3098
+ if (runStart === -1)
3099
+ runStart = i;
3100
+ runLength++;
3101
+ if (runLength > bestLength) {
3102
+ bestLength = runLength;
3103
+ bestStart = runStart;
3103
3104
  }
3104
- if (++tokenCount > 7) {
3105
- output.error = true;
3106
- break;
3107
- }
3108
- if (i > 0 && input[i - 1] === ":") {
3109
- endipv6Encountered = true;
3110
- }
3111
- address.push(":");
3112
- continue;
3113
- } else if (cursor === "%") {
3114
- if (!consume(buffer, address, output)) {
3115
- break;
3116
- }
3117
- consume = consumeIsZone;
3118
3105
  } else {
3119
- buffer.push(cursor);
3120
- continue;
3106
+ runStart = -1;
3107
+ runLength = 0;
3121
3108
  }
3122
3109
  }
3123
- if (buffer.length) {
3124
- if (consume === consumeIsZone) {
3125
- output.zone = buffer.join("");
3126
- } else if (endIpv6) {
3127
- address.push(buffer.join(""));
3128
- } else {
3129
- address.push(stringArrayToHexStripped(buffer));
3110
+ if (bestLength < 2)
3111
+ return hextets.join(":");
3112
+ const head = hextets.slice(0, bestStart).join(":");
3113
+ const tail = hextets.slice(bestStart + bestLength).join(":");
3114
+ return head + "::" + tail;
3115
+ }
3116
+ function normalizeIPv6Address(input) {
3117
+ const compression = input.indexOf("::");
3118
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1)
3119
+ return;
3120
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
3121
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
3122
+ if (compression !== -1) {
3123
+ if (left.length === 1 && left[0] === "")
3124
+ left.length = 0;
3125
+ if (right.length === 1 && right[0] === "")
3126
+ right.length = 0;
3127
+ }
3128
+ const parts = left.concat(right);
3129
+ let hextetCount = 0;
3130
+ for (let i = 0;i < parts.length; i++) {
3131
+ const part = parts[i];
3132
+ if (part === "")
3133
+ return;
3134
+ if (part.indexOf(".") !== -1) {
3135
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part))
3136
+ return;
3137
+ hextetCount += 2;
3138
+ continue;
3130
3139
  }
3140
+ if (!isHextet(part))
3141
+ return;
3142
+ parts[i] = parseInt(part, 16).toString(16);
3143
+ hextetCount++;
3131
3144
  }
3132
- output.address = address.join("");
3133
- return output;
3145
+ if (compression === -1) {
3146
+ if (hextetCount !== 8)
3147
+ return;
3148
+ return compressIPv6ZeroRun(parts);
3149
+ }
3150
+ if (hextetCount >= 8)
3151
+ return;
3152
+ const expanded = parts.slice(0, left.length);
3153
+ for (let i = hextetCount;i < 8; i++)
3154
+ expanded.push("0");
3155
+ for (let i = left.length;i < parts.length; i++)
3156
+ expanded.push(parts[i]);
3157
+ return compressIPv6ZeroRun(expanded);
3134
3158
  }
3135
3159
  function normalizeIPv6(host) {
3136
- if (findToken(host, ":") < 2) {
3137
- return { host, isIPV6: false };
3138
- }
3139
- const ipv62 = getIPV6(host);
3140
- if (!ipv62.error) {
3141
- let newHost = ipv62.address;
3142
- let escapedHost = ipv62.address;
3143
- if (ipv62.zone) {
3144
- newHost += "%" + ipv62.zone;
3145
- escapedHost += "%25" + ipv62.zone;
3146
- }
3147
- return { host: newHost, isIPV6: true, escapedHost };
3148
- } else {
3149
- return { host, isIPV6: false };
3150
- }
3160
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
3161
+ const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
3162
+ if (hasBracket && !bracketed)
3163
+ return { host, isIPV6: false, error: true };
3164
+ let input = bracketed ? host.slice(1, -1) : host;
3165
+ if (bracketed && isIPvFuture(input)) {
3166
+ input = input.toLowerCase();
3167
+ return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
3168
+ }
3169
+ if (findToken(input, ":") < 2) {
3170
+ return { host, isIPV6: false, error: bracketed };
3171
+ }
3172
+ let zoneIdentifier = "";
3173
+ const zoneSeparator = input.indexOf("%");
3174
+ if (zoneSeparator !== -1) {
3175
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
3176
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
3177
+ if (!isZoneIdentifier(zoneIdentifier))
3178
+ return { host, isIPV6: false, error: true };
3179
+ input = input.slice(0, zoneSeparator);
3180
+ }
3181
+ const address = normalizeIPv6Address(input);
3182
+ if (address === undefined)
3183
+ return { host, isIPV6: false, error: true };
3184
+ return {
3185
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
3186
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
3187
+ isIPV6: true
3188
+ };
3151
3189
  }
3152
3190
  function findToken(str, token) {
3153
3191
  let ind = 0;
@@ -3324,6 +3362,45 @@ var require_utils = __commonJS((exports, module) => {
3324
3362
  }
3325
3363
  return output;
3326
3364
  }
3365
+ function serializePathEncoding(input, pathNoScheme = false) {
3366
+ let output = "";
3367
+ let firstSegment = pathNoScheme && input[0] !== "/";
3368
+ for (let i = 0;i < input.length; i++) {
3369
+ const ch = input[i];
3370
+ if (ch === "%" && i + 2 < input.length) {
3371
+ const hex = input.slice(i + 1, i + 3);
3372
+ if (isHexPair(hex)) {
3373
+ output += "%" + hex.toUpperCase();
3374
+ i += 2;
3375
+ continue;
3376
+ }
3377
+ }
3378
+ if (ch === "/") {
3379
+ firstSegment = false;
3380
+ }
3381
+ if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) {
3382
+ output += ch;
3383
+ } else {
3384
+ const code = input.charCodeAt(i);
3385
+ if (code < 128) {
3386
+ output += BYTE_HEX[code];
3387
+ } else if (code < 55296 || code > 57343) {
3388
+ output += percentEncodeNonAscii(code);
3389
+ } else if (code <= 56319 && i + 1 < input.length) {
3390
+ const low = input.charCodeAt(i + 1);
3391
+ if (low >= 56320 && low <= 57343) {
3392
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3393
+ i++;
3394
+ } else {
3395
+ output += percentEncodeNonAscii(65533);
3396
+ }
3397
+ } else {
3398
+ output += percentEncodeNonAscii(65533);
3399
+ }
3400
+ }
3401
+ }
3402
+ return output;
3403
+ }
3327
3404
  function normalizeQueryFragmentEncoding(input) {
3328
3405
  let output = "";
3329
3406
  for (let i = 0;i < input.length; i++) {
@@ -3365,6 +3442,50 @@ var require_utils = __commonJS((exports, module) => {
3365
3442
  }
3366
3443
  return output;
3367
3444
  }
3445
+ function encodeComponent(input, isAllowed) {
3446
+ let output = "";
3447
+ for (let i = 0;i < input.length; i++) {
3448
+ const ch = input[i];
3449
+ if (ch === "%" && i + 2 < input.length) {
3450
+ const hex = input.slice(i + 1, i + 3);
3451
+ if (isHexPair(hex)) {
3452
+ output += "%" + hex.toUpperCase();
3453
+ i += 2;
3454
+ continue;
3455
+ }
3456
+ }
3457
+ if (isAllowed(ch)) {
3458
+ output += ch;
3459
+ } else {
3460
+ const code = input.charCodeAt(i);
3461
+ if (code < 128) {
3462
+ output += BYTE_HEX[code];
3463
+ } else if (code < 55296 || code > 57343) {
3464
+ output += percentEncodeNonAscii(code);
3465
+ } else if (code <= 56319 && i + 1 < input.length) {
3466
+ const low = input.charCodeAt(i + 1);
3467
+ if (low >= 56320 && low <= 57343) {
3468
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3469
+ i++;
3470
+ } else {
3471
+ output += percentEncodeNonAscii(65533);
3472
+ }
3473
+ } else {
3474
+ output += percentEncodeNonAscii(65533);
3475
+ }
3476
+ }
3477
+ }
3478
+ return output;
3479
+ }
3480
+ function encodeUserinfo(input) {
3481
+ return encodeComponent(input, isUserinfoCharacter);
3482
+ }
3483
+ function encodeQuery(input) {
3484
+ return encodeComponent(input, isQueryFragmentCharacter);
3485
+ }
3486
+ function encodeFragment(input) {
3487
+ return encodeComponent(input, isQueryFragmentCharacter);
3488
+ }
3368
3489
  function escapePreservingEscapes(input) {
3369
3490
  let output = "";
3370
3491
  for (let i = 0;i < input.length; i++) {
@@ -3399,14 +3520,18 @@ var require_utils = __commonJS((exports, module) => {
3399
3520
  function recomposeAuthority(component) {
3400
3521
  const uriTokens = [];
3401
3522
  if (component.userinfo !== undefined) {
3402
- uriTokens.push(component.userinfo);
3523
+ uriTokens.push(encodeUserinfo(component.userinfo));
3403
3524
  uriTokens.push("@");
3404
3525
  }
3405
3526
  if (component.host !== undefined) {
3406
- let host = unescape(component.host);
3527
+ let host = component.host;
3407
3528
  if (!isIPv4(host)) {
3408
- const ipV6res = normalizeIPv6(host);
3409
- if (ipV6res.isIPV6 === true) {
3529
+ let ipV6res = normalizeIPv6(host);
3530
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
3531
+ host = normalizePercentEncoding(host, true);
3532
+ ipV6res = normalizeIPv6(host);
3533
+ }
3534
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
3410
3535
  host = `[${ipV6res.escapedHost}]`;
3411
3536
  } else {
3412
3537
  host = reescapeHostDelimiters(host, false);
@@ -3415,18 +3540,29 @@ var require_utils = __commonJS((exports, module) => {
3415
3540
  uriTokens.push(host);
3416
3541
  }
3417
3542
  if (typeof component.port === "number" || typeof component.port === "string") {
3543
+ const port = String(component.port);
3544
+ if (!isPort(port)) {
3545
+ throw new TypeError("URI port is malformed.");
3546
+ }
3418
3547
  uriTokens.push(":");
3419
- uriTokens.push(String(component.port));
3548
+ uriTokens.push(port);
3420
3549
  }
3421
3550
  return uriTokens.length ? uriTokens.join("") : undefined;
3422
3551
  }
3423
3552
  module.exports = {
3553
+ BYTE_HEX,
3554
+ percentEncodeNonAscii,
3424
3555
  nonSimpleDomain,
3556
+ nonSimpleMailtoDomain,
3425
3557
  recomposeAuthority,
3426
3558
  reescapeHostDelimiters,
3427
3559
  normalizePercentEncoding,
3428
3560
  normalizePathEncoding,
3561
+ serializePathEncoding,
3429
3562
  normalizeQueryFragmentEncoding,
3563
+ encodeUserinfo,
3564
+ encodeQuery,
3565
+ encodeFragment,
3430
3566
  escapePreservingEscapes,
3431
3567
  removeDotSegments,
3432
3568
  isIPv4,
@@ -3438,15 +3574,16 @@ var require_utils = __commonJS((exports, module) => {
3438
3574
 
3439
3575
  // node_modules/fast-uri/lib/schemes.js
3440
3576
  var require_schemes = __commonJS((exports, module) => {
3441
- var { isUUID } = require_utils();
3442
- var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
3577
+ var { isUUID, BYTE_HEX, percentEncodeNonAscii, nonSimpleMailtoDomain } = require_utils();
3578
+ var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
3443
3579
  var supportedSchemeNames = [
3444
3580
  "http",
3445
3581
  "https",
3446
3582
  "ws",
3447
3583
  "wss",
3448
3584
  "urn",
3449
- "urn:uuid"
3585
+ "urn:uuid",
3586
+ "mailto"
3450
3587
  ];
3451
3588
  function isValidSchemeName(name) {
3452
3589
  return supportedSchemeNames.indexOf(name) !== -1;
@@ -3494,9 +3631,10 @@ var require_schemes = __commonJS((exports, module) => {
3494
3631
  wsComponent.secure = undefined;
3495
3632
  }
3496
3633
  if (wsComponent.resourceName) {
3497
- const [path, query] = wsComponent.resourceName.split("?");
3634
+ const queryIndex = wsComponent.resourceName.indexOf("?");
3635
+ const path = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3498
3636
  wsComponent.path = path && path !== "/" ? path : undefined;
3499
- wsComponent.query = query;
3637
+ wsComponent.query = queryIndex === -1 ? undefined : wsComponent.resourceName.slice(queryIndex + 1);
3500
3638
  wsComponent.resourceName = undefined;
3501
3639
  }
3502
3640
  wsComponent.fragment = undefined;
@@ -3508,7 +3646,7 @@ var require_schemes = __commonJS((exports, module) => {
3508
3646
  return urnComponent;
3509
3647
  }
3510
3648
  const matches = urnComponent.path.match(URN_REG);
3511
- if (matches) {
3649
+ if (matches && matches[0] === urnComponent.path) {
3512
3650
  const scheme = options.scheme || urnComponent.scheme || "urn";
3513
3651
  urnComponent.nid = matches[1].toLowerCase();
3514
3652
  urnComponent.nss = matches[2];
@@ -3590,13 +3728,235 @@ var require_schemes = __commonJS((exports, module) => {
3590
3728
  serialize: urnuuidSerialize,
3591
3729
  skipNormalize: true
3592
3730
  };
3731
+ var LOCAL_PART_ALLOWED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$'()*+=";
3732
+ var HFNAME_ALLOWED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$'()*+,;:@";
3733
+ var DTEXT_ALLOWED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$'()*+;:";
3734
+ function allowSet(chars, allowNonAscii = false) {
3735
+ const table = new Uint8Array(128);
3736
+ for (let i = 0;i < chars.length; i++) {
3737
+ table[chars.charCodeAt(i)] = 1;
3738
+ }
3739
+ const escaped = chars.replace(/[\\\]^-]/gu, "\\$&");
3740
+ const cls = allowNonAscii ? `[${escaped}]|[^\\0-\\x7F\\uD800-\\uDFFF]` : `[${escaped}]`;
3741
+ return { all: new RegExp(`^(?:${cls})*$`, "u"), table, allowNonAscii };
3742
+ }
3743
+ var LOCAL_PART = allowSet(LOCAL_PART_ALLOWED);
3744
+ var HFNAME = allowSet(HFNAME_ALLOWED);
3745
+ var DTEXT = allowSet(DTEXT_ALLOWED);
3746
+ var DTEXT_IRI = allowSet(DTEXT_ALLOWED, true);
3747
+ var HEX_PAIR = /^[\da-f]{2}$/iu;
3748
+ var MAILTO_DOMAIN_LITERAL = /^\[[\x21-\x5A\x5E-\x7E]*\]$/u;
3749
+ var MAILTO_DOMAIN_ERROR = "URI mailto has an invalid recipient domain.";
3750
+ var HAS_SURROGATE = /[\uD800-\uDFFF]/u;
3751
+ function decodeHex(str) {
3752
+ if (typeof str !== "string" || str.indexOf("%") === -1) {
3753
+ return str;
3754
+ }
3755
+ try {
3756
+ return decodeURIComponent(str);
3757
+ } catch {
3758
+ return str;
3759
+ }
3760
+ }
3761
+ function replaceLoneSurrogates(input) {
3762
+ let result = "";
3763
+ for (let i = 0;i < input.length; i++) {
3764
+ const code = input.charCodeAt(i);
3765
+ if (code >= 55296 && code <= 56319 && i + 1 < input.length) {
3766
+ const low = input.charCodeAt(i + 1);
3767
+ if (low >= 56320 && low <= 57343) {
3768
+ result += input[i] + input[i + 1];
3769
+ i++;
3770
+ continue;
3771
+ }
3772
+ }
3773
+ result += code >= 55296 && code <= 57343 ? "�" : input[i];
3774
+ }
3775
+ return result;
3776
+ }
3777
+ function encodeWithAllow(input, set2) {
3778
+ if (set2.all.test(input)) {
3779
+ return input;
3780
+ }
3781
+ const table = set2.table;
3782
+ let result = "";
3783
+ for (let i = 0;i < input.length; i++) {
3784
+ const code = input.charCodeAt(i);
3785
+ if (code < 128) {
3786
+ if (table[code] === 1) {
3787
+ result += input[i];
3788
+ } else if (code === 37 && i + 2 < input.length && HEX_PAIR.test(input.slice(i + 1, i + 3))) {
3789
+ result += "%" + input.slice(i + 1, i + 3).toUpperCase();
3790
+ i += 2;
3791
+ } else {
3792
+ result += BYTE_HEX[code];
3793
+ }
3794
+ continue;
3795
+ }
3796
+ if (code < 55296 || code > 57343) {
3797
+ result += set2.allowNonAscii ? input[i] : percentEncodeNonAscii(code);
3798
+ continue;
3799
+ }
3800
+ if (code <= 56319 && i + 1 < input.length) {
3801
+ const low = input.charCodeAt(i + 1);
3802
+ if (low >= 56320 && low <= 57343) {
3803
+ result += set2.allowNonAscii ? input[i] + input[i + 1] : percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3804
+ i++;
3805
+ continue;
3806
+ }
3807
+ }
3808
+ result += set2.allowNonAscii ? "�" : percentEncodeNonAscii(65533);
3809
+ }
3810
+ return result;
3811
+ }
3812
+ function mailtoNormalizeDomain(domain, options, component) {
3813
+ const normalizedDomain = String(domain).toLowerCase();
3814
+ if (MAILTO_DOMAIN_LITERAL.test(normalizedDomain)) {
3815
+ return normalizedDomain;
3816
+ }
3817
+ if (normalizedDomain !== "" && !nonSimpleMailtoDomain(normalizedDomain)) {
3818
+ return normalizedDomain;
3819
+ }
3820
+ try {
3821
+ const parsedDomain = new URL("http://" + normalizedDomain);
3822
+ if (parsedDomain.username || parsedDomain.password || parsedDomain.port || parsedDomain.pathname !== "/" || parsedDomain.search || parsedDomain.hash || !parsedDomain.hostname) {
3823
+ throw new Error(MAILTO_DOMAIN_ERROR);
3824
+ }
3825
+ return options && options.unicodeSupport ? normalizedDomain : parsedDomain.hostname;
3826
+ } catch {
3827
+ if (component) {
3828
+ component.error = component.error || MAILTO_DOMAIN_ERROR;
3829
+ }
3830
+ return normalizedDomain;
3831
+ }
3832
+ }
3833
+ function mailtoEncodeDomain(domain, options) {
3834
+ const set2 = options && options.unicodeSupport ? DTEXT_IRI : DTEXT;
3835
+ if (domain.length > 1 && domain[0] === "[" && domain[domain.length - 1] === "]") {
3836
+ return "[" + encodeWithAllow(domain.slice(1, -1), set2) + "]";
3837
+ }
3838
+ return encodeWithAllow(domain, set2);
3839
+ }
3840
+ function mailtoParse(component, options) {
3841
+ const mailtoComponent = component;
3842
+ let rawPath = mailtoComponent.path;
3843
+ let rawQuery = mailtoComponent.query;
3844
+ if (rawPath && HAS_SURROGATE.test(rawPath))
3845
+ rawPath = replaceLoneSurrogates(rawPath);
3846
+ if (rawQuery && HAS_SURROGATE.test(rawQuery))
3847
+ rawQuery = replaceLoneSurrogates(rawQuery);
3848
+ const to = rawPath ? rawPath.indexOf(",") === -1 ? [rawPath] : rawPath.split(",") : [];
3849
+ mailtoComponent.path = undefined;
3850
+ if (rawQuery) {
3851
+ let headers = null;
3852
+ let start = 0;
3853
+ while (start <= rawQuery.length) {
3854
+ let end = rawQuery.indexOf("&", start);
3855
+ if (end === -1)
3856
+ end = rawQuery.length;
3857
+ const token = rawQuery.slice(start, end);
3858
+ start = end + 1;
3859
+ const eqIdx = token.indexOf("=");
3860
+ if (eqIdx !== token.lastIndexOf("=")) {
3861
+ mailtoComponent.error = mailtoComponent.error || "URI mailto has malformed header fields.";
3862
+ continue;
3863
+ }
3864
+ const name = eqIdx === -1 ? token : token.slice(0, eqIdx);
3865
+ const value = eqIdx === -1 ? "" : token.slice(eqIdx + 1);
3866
+ if (name === "to") {
3867
+ const addrs = value.split(",");
3868
+ for (let j = 0;j < addrs.length; j++)
3869
+ to.push(addrs[j]);
3870
+ continue;
3871
+ }
3872
+ if (name === "subject") {
3873
+ mailtoComponent.subject = decodeHex(value);
3874
+ continue;
3875
+ }
3876
+ if (name === "body") {
3877
+ mailtoComponent.body = decodeHex(value);
3878
+ continue;
3879
+ }
3880
+ if (headers === null)
3881
+ headers = Object.create(null);
3882
+ headers[decodeHex(name)] = decodeHex(value);
3883
+ }
3884
+ if (headers !== null)
3885
+ mailtoComponent.headers = headers;
3886
+ }
3887
+ mailtoComponent.query = undefined;
3888
+ for (let i = 0;i < to.length; i++) {
3889
+ const rawAddr = to[i];
3890
+ const atIdx = rawAddr.lastIndexOf("@");
3891
+ if (atIdx < 0) {
3892
+ mailtoComponent.error = mailtoComponent.error || MAILTO_DOMAIN_ERROR;
3893
+ to[i] = decodeHex(rawAddr);
3894
+ continue;
3895
+ }
3896
+ const local = decodeHex(rawAddr.slice(0, atIdx));
3897
+ const domain = mailtoNormalizeDomain(decodeHex(rawAddr.slice(atIdx + 1)), options, mailtoComponent);
3898
+ to[i] = local + "@" + domain;
3899
+ }
3900
+ if (to.length)
3901
+ mailtoComponent.to = to;
3902
+ return mailtoComponent;
3903
+ }
3904
+ function mailtoSerialize(component, options) {
3905
+ const mailtoComponent = component;
3906
+ const to = Array.isArray(mailtoComponent.to) ? mailtoComponent.to.slice() : [];
3907
+ if (to.length) {
3908
+ for (let i = 0;i < to.length; i++) {
3909
+ const addr = String(to[i]);
3910
+ const atIdx = addr.lastIndexOf("@");
3911
+ const rawLocal = atIdx >= 0 ? addr.slice(0, atIdx) : addr;
3912
+ const rawDomain = atIdx >= 0 ? addr.slice(atIdx + 1) : "";
3913
+ const local = encodeWithAllow(rawLocal, LOCAL_PART);
3914
+ const decodedDomain = decodeHex(rawDomain);
3915
+ const normalizedDomain = mailtoNormalizeDomain(decodedDomain, options);
3916
+ to[i] = local + "@" + mailtoEncodeDomain(normalizedDomain, options);
3917
+ }
3918
+ mailtoComponent.path = to.join(",");
3919
+ options.skipEscape = true;
3920
+ } else {
3921
+ mailtoComponent.path = undefined;
3922
+ }
3923
+ const headers = mailtoComponent.headers && typeof mailtoComponent.headers === "object" ? Object.assign(Object.create(null), mailtoComponent.headers) : Object.create(null);
3924
+ if (mailtoComponent.subject)
3925
+ headers.subject = mailtoComponent.subject;
3926
+ if (mailtoComponent.body)
3927
+ headers.body = mailtoComponent.body;
3928
+ mailtoComponent.headers = headers;
3929
+ let query = "";
3930
+ let count = 0;
3931
+ for (const name in headers) {
3932
+ if (count++ !== 0)
3933
+ query += "&";
3934
+ query += encodeWithAllow(name, HFNAME) + "=" + encodeWithAllow(String(headers[name]), HFNAME);
3935
+ }
3936
+ if (count !== 0) {
3937
+ mailtoComponent.query = query;
3938
+ } else {
3939
+ mailtoComponent.headers = undefined;
3940
+ }
3941
+ return mailtoComponent;
3942
+ }
3943
+ var mailto = {
3944
+ scheme: "mailto",
3945
+ parse: mailtoParse,
3946
+ serialize: mailtoSerialize,
3947
+ domainHost: false,
3948
+ unicodeSupport: true,
3949
+ skipNormalize: true,
3950
+ absolutePath: true
3951
+ };
3593
3952
  var SCHEMES = {
3594
3953
  http,
3595
3954
  https,
3596
3955
  ws,
3597
3956
  wss,
3598
3957
  urn,
3599
- "urn:uuid": urnuuid
3958
+ "urn:uuid": urnuuid,
3959
+ mailto
3600
3960
  };
3601
3961
  Object.setPrototypeOf(SCHEMES, null);
3602
3962
  function getSchemeHandler(scheme) {
@@ -3612,8 +3972,17 @@ var require_schemes = __commonJS((exports, module) => {
3612
3972
 
3613
3973
  // node_modules/fast-uri/index.js
3614
3974
  var require_fast_uri = __commonJS((exports, module) => {
3615
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, normalizeQueryFragmentEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3975
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3616
3976
  var { SCHEMES, getSchemeHandler } = require_schemes();
3977
+ var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
3978
+ var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
3979
+ function decodeValidScheme(scheme) {
3980
+ const decodedScheme = unescape(String(scheme));
3981
+ if (!VALID_SCHEME.test(decodedScheme)) {
3982
+ throw new TypeError(MALFORMED_SCHEME_ERROR);
3983
+ }
3984
+ return decodedScheme;
3985
+ }
3617
3986
  function normalize(uri, options) {
3618
3987
  if (typeof uri === "string") {
3619
3988
  uri = normalizeString(uri, options);
@@ -3624,12 +3993,34 @@ var require_fast_uri = __commonJS((exports, module) => {
3624
3993
  }
3625
3994
  function resolve(baseURI, relativeURI, options) {
3626
3995
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3627
- const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3628
- const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
3629
- if (baseMalformed || relativeMalformed) {
3996
+ const {
3997
+ parsed: baseParsed,
3998
+ malformedAuthorityOrPort: baseMalformed,
3999
+ malformedPercentEncoding: baseMalformedPercentEncoding,
4000
+ malformedSchemeSpecific: baseMalformedSchemeSpecific,
4001
+ malformedHost: baseMalformedHost,
4002
+ malformedScheme: baseMalformedScheme
4003
+ } = parseWithStatus(baseURI, schemelessOptions);
4004
+ const {
4005
+ parsed: relativeParsed,
4006
+ malformedAuthorityOrPort: relativeMalformed,
4007
+ malformedPercentEncoding: relativeMalformedPercentEncoding,
4008
+ malformedSchemeSpecific: relativeMalformedSchemeSpecific,
4009
+ malformedHost: relativeMalformedHost,
4010
+ malformedScheme: relativeMalformedScheme
4011
+ } = parseWithStatus(relativeURI, schemelessOptions);
4012
+ if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) {
3630
4013
  throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
3631
4014
  }
3632
4015
  const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
4016
+ const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
4017
+ const resolvedHost = resolved.host;
4018
+ const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
4019
+ canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
4020
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
4021
+ if (resolved.error && !encodedASCIIHost) {
4022
+ throw new Error(resolved.error);
4023
+ }
3633
4024
  schemelessOptions.skipEscape = true;
3634
4025
  return serialize(resolved, schemelessOptions);
3635
4026
  }
@@ -3689,7 +4080,7 @@ var require_fast_uri = __commonJS((exports, module) => {
3689
4080
  function equal(uriA, uriB, options) {
3690
4081
  const normalizedA = normalizeComparableURI(uriA, options);
3691
4082
  const normalizedB = normalizeComparableURI(uriB, options);
3692
- return normalizedA !== undefined && normalizedB !== undefined && normalizedA.toLowerCase() === normalizedB.toLowerCase();
4083
+ return normalizedA !== undefined && normalizedB !== undefined && normalizedA === normalizedB;
3693
4084
  }
3694
4085
  function serialize(cmpts, opts) {
3695
4086
  const component = {
@@ -3706,24 +4097,31 @@ var require_fast_uri = __commonJS((exports, module) => {
3706
4097
  reference: cmpts.reference,
3707
4098
  resourceName: cmpts.resourceName,
3708
4099
  secure: cmpts.secure,
4100
+ to: cmpts.to,
4101
+ subject: cmpts.subject,
4102
+ body: cmpts.body,
4103
+ headers: cmpts.headers,
3709
4104
  error: ""
3710
4105
  };
3711
4106
  const options = Object.assign({}, opts);
3712
4107
  const uriTokens = [];
4108
+ if (component.scheme) {
4109
+ component.scheme = decodeValidScheme(component.scheme);
4110
+ }
3713
4111
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
3714
4112
  if (schemeHandler && schemeHandler.serialize)
3715
4113
  schemeHandler.serialize(component, options);
4114
+ const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined;
4115
+ const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority;
3716
4116
  if (component.path !== undefined) {
3717
4117
  if (!options.skipEscape) {
3718
- component.path = escapePreservingEscapes(component.path);
3719
- if (component.scheme !== undefined) {
3720
- component.path = component.path.split("%3A").join(":");
3721
- }
4118
+ component.path = serializePathEncoding(component.path, pathNoScheme);
3722
4119
  } else {
3723
4120
  component.path = normalizePercentEncoding(component.path);
3724
4121
  }
3725
4122
  }
3726
4123
  if (options.reference !== "suffix" && component.scheme) {
4124
+ component.scheme = decodeValidScheme(component.scheme);
3727
4125
  uriTokens.push(component.scheme, ":");
3728
4126
  }
3729
4127
  const authority = recomposeAuthority(component);
@@ -3741,16 +4139,19 @@ var require_fast_uri = __commonJS((exports, module) => {
3741
4139
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
3742
4140
  s = removeDotSegments(s);
3743
4141
  }
4142
+ if (pathNoScheme) {
4143
+ s = serializePathEncoding(s, true);
4144
+ }
3744
4145
  if (authority === undefined && s[0] === "/" && s[1] === "/") {
3745
4146
  s = "/%2F" + s.slice(2);
3746
4147
  }
3747
4148
  uriTokens.push(s);
3748
4149
  }
3749
4150
  if (component.query !== undefined) {
3750
- uriTokens.push("?", component.query);
4151
+ uriTokens.push("?", encodeQuery(component.query));
3751
4152
  }
3752
4153
  if (component.fragment !== undefined) {
3753
- uriTokens.push("#", component.fragment);
4154
+ uriTokens.push("#", encodeFragment(component.fragment));
3754
4155
  }
3755
4156
  return uriTokens.join("");
3756
4157
  }
@@ -3766,6 +4167,37 @@ var require_fast_uri = __commonJS((exports, module) => {
3766
4167
  }
3767
4168
  return;
3768
4169
  }
4170
+ function hasMalformedPercentEncoding(component) {
4171
+ if (component === undefined)
4172
+ return false;
4173
+ let percent = component.indexOf("%");
4174
+ while (percent !== -1) {
4175
+ if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
4176
+ return true;
4177
+ }
4178
+ percent = component.indexOf("%", percent + 3);
4179
+ }
4180
+ return false;
4181
+ }
4182
+ function isIPLiteral(host) {
4183
+ return host[0] === "[" && host[host.length - 1] === "]";
4184
+ }
4185
+ function hasMalformedComponentPercentEncoding(matches) {
4186
+ const host = matches[4];
4187
+ return hasMalformedPercentEncoding(matches[3]) || host !== undefined && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
4188
+ }
4189
+ function canonicalizeHost(parsed, options, schemeHandler, isIP) {
4190
+ const bracketedIPLiteral = parsed.host && parsed.host[0] === "[" && parsed.host[parsed.host.length - 1] === "]";
4191
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && !bracketedIPLiteral && nonSimpleDomain(parsed.host)) {
4192
+ try {
4193
+ parsed.host = new URL("http://" + parsed.host).hostname;
4194
+ } catch (e) {
4195
+ parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
4196
+ return true;
4197
+ }
4198
+ }
4199
+ return false;
4200
+ }
3769
4201
  function parseWithStatus(uri, opts) {
3770
4202
  const options = Object.assign({}, opts);
3771
4203
  const parsed = {
@@ -3778,6 +4210,11 @@ var require_fast_uri = __commonJS((exports, module) => {
3778
4210
  fragment: undefined
3779
4211
  };
3780
4212
  let malformedAuthorityOrPort = false;
4213
+ let malformedPercentEncoding = false;
4214
+ let malformedSchemeSpecific = false;
4215
+ let malformedHost = false;
4216
+ let malformedIPLiteral = false;
4217
+ let malformedScheme = false;
3781
4218
  let isIP = false;
3782
4219
  if (options.reference === "suffix") {
3783
4220
  if (options.scheme) {
@@ -3807,13 +4244,26 @@ var require_fast_uri = __commonJS((exports, module) => {
3807
4244
  }
3808
4245
  const matches = uri.match(URI_PARSE);
3809
4246
  if (matches) {
3810
- parsed.scheme = matches[1] === undefined ? undefined : matches[1].toLowerCase();
4247
+ parsed.scheme = matches[1];
3811
4248
  parsed.userinfo = matches[3];
3812
4249
  parsed.host = matches[4];
3813
4250
  parsed.port = parseInt(matches[5], 10);
3814
4251
  parsed.path = matches[6] || "";
3815
4252
  parsed.query = matches[7];
3816
4253
  parsed.fragment = matches[8];
4254
+ if (parsed.scheme !== undefined) {
4255
+ const decodedScheme = unescape(parsed.scheme);
4256
+ if (VALID_SCHEME.test(decodedScheme)) {
4257
+ parsed.scheme = decodedScheme.toLowerCase();
4258
+ } else {
4259
+ parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
4260
+ malformedScheme = true;
4261
+ }
4262
+ }
4263
+ malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches);
4264
+ if (malformedPercentEncoding) {
4265
+ parsed.error = parsed.error || "URI contains malformed percent-encoding.";
4266
+ }
3817
4267
  if (isNaN(parsed.port)) {
3818
4268
  parsed.port = matches[5];
3819
4269
  }
@@ -3825,9 +4275,16 @@ var require_fast_uri = __commonJS((exports, module) => {
3825
4275
  if (parsed.host) {
3826
4276
  const ipv4result = isIPv4(parsed.host);
3827
4277
  if (ipv4result === false) {
4278
+ const bracketedIPLiteral = isIPLiteral(parsed.host);
4279
+ const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
3828
4280
  const ipv6result = normalizeIPv6(parsed.host);
3829
- parsed.host = ipv6result.host.toLowerCase();
3830
- isIP = ipv6result.isIPV6;
4281
+ isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
4282
+ malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
4283
+ parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
4284
+ if (malformedIPLiteral) {
4285
+ parsed.error = parsed.error || "URI host is malformed.";
4286
+ malformedAuthorityOrPort = true;
4287
+ }
3831
4288
  } else {
3832
4289
  isIP = true;
3833
4290
  }
@@ -3845,22 +4302,14 @@ var require_fast_uri = __commonJS((exports, module) => {
3845
4302
  parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
3846
4303
  }
3847
4304
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
3848
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
3849
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
3850
- try {
3851
- parsed.host = new URL("http://" + parsed.host).hostname;
3852
- } catch (e) {
3853
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
3854
- }
3855
- }
4305
+ if (!malformedIPLiteral) {
4306
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
3856
4307
  }
3857
4308
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
3858
4309
  if (uri.indexOf("%") !== -1) {
3859
- if (parsed.scheme !== undefined) {
3860
- parsed.scheme = unescape(parsed.scheme);
3861
- }
3862
- if (parsed.host !== undefined) {
3863
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
4310
+ if (parsed.host !== undefined && !malformedIPLiteral) {
4311
+ const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4312
+ parsed.host = reescapeHostDelimiters(host, isIP);
3864
4313
  }
3865
4314
  }
3866
4315
  if (parsed.path) {
@@ -3875,11 +4324,14 @@ var require_fast_uri = __commonJS((exports, module) => {
3875
4324
  }
3876
4325
  if (schemeHandler && schemeHandler.parse) {
3877
4326
  schemeHandler.parse(parsed, options);
4327
+ if (schemeHandler === SCHEMES.urn && parsed.nid === undefined) {
4328
+ malformedSchemeSpecific = true;
4329
+ }
3878
4330
  }
3879
4331
  } else {
3880
4332
  parsed.error = parsed.error || "URI can not be parsed.";
3881
4333
  }
3882
- return { parsed, malformedAuthorityOrPort };
4334
+ return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
3883
4335
  }
3884
4336
  function parse6(uri, opts) {
3885
4337
  return parseWithStatus(uri, opts).parsed;
@@ -3888,20 +4340,28 @@ var require_fast_uri = __commonJS((exports, module) => {
3888
4340
  return normalizeStringWithStatus(uri, opts).normalized;
3889
4341
  }
3890
4342
  function normalizeStringWithStatus(uri, opts) {
3891
- const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
4343
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
3892
4344
  return {
3893
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
3894
- malformedAuthorityOrPort
4345
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
4346
+ malformedAuthorityOrPort,
4347
+ malformedPercentEncoding,
4348
+ malformedSchemeSpecific,
4349
+ malformedHost,
4350
+ malformedScheme
3895
4351
  };
3896
4352
  }
3897
4353
  function normalizeComparableURI(uri, opts) {
3898
- if (typeof uri === "string") {
3899
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
3900
- return malformedAuthorityOrPort ? undefined : normalized;
4354
+ if (typeof uri !== "string" && typeof uri !== "object") {
4355
+ return;
3901
4356
  }
3902
- if (typeof uri === "object") {
3903
- return serialize(uri, opts);
4357
+ let value;
4358
+ try {
4359
+ value = typeof uri === "string" ? uri : serialize(uri, opts);
4360
+ } catch {
4361
+ return;
3904
4362
  }
4363
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
4364
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized;
3905
4365
  }
3906
4366
  var fastUri = {
3907
4367
  SCHEMES,
@@ -0,0 +1,79 @@
1
+ # Posts made through the app's own user token are the person's messages
2
+
3
+ Upstream discussion: anneschuth/claude-threads#526.
4
+
5
+ ## What it does
6
+
7
+ A message posted with `chat.postMessage` and a **user token of this app**
8
+ (`xoxp-…`, obtained when a person authorises the app) shows up in Slack as
9
+ that person, and Slack delivers it with `user` = the person **and**
10
+ `bot_id` + `app_id` of the app. Today the Slack client drops every message
11
+ that carries a `bot_id`, so such a post never reaches Claude, silently.
12
+
13
+ With this change the client treats a message as bot-authored only when
14
+
15
+ - its `user` is the bot's own user (the daemon's replies), or
16
+ - it carries a `bot_id` and is **not** a post from this app, from this
17
+ workspace, with a `user`.
18
+
19
+ So a post relayed through this app's own user token is handled exactly like
20
+ the person typing it: same session routing, same allow-list, same everything.
21
+ Other apps' bot posts, classic bot posts without a `user`, the daemon's own
22
+ replies, and a bot copy of this same app installed in another workspace that
23
+ shares a Slack Connect channel (same `app_id`, other `team`) stay ignored,
24
+ as before.
25
+
26
+ The rule is applied in the three places the filter lives: live events,
27
+ missed-message recovery after a reconnect, and thread history with
28
+ `excludeBotMessages`.
29
+
30
+ ## Why
31
+
32
+ Any integration that speaks *for* a person — a voice front desk, a phone
33
+ bridge, an approval UI — posts on the person's behalf with the app's user
34
+ token, because that is the only way to keep attribution honest and the bot's
35
+ consent gate (channel membership, allow-lists) intact. Slack marks every
36
+ API-posted message with the app's `bot_id`; there is no way to post a
37
+ "pure" human message through the API. Without this exception the daemon
38
+ can never be driven by such an integration.
39
+
40
+ Origin: the VVS live voice desk (`voice/` on the `local/voice-desk` branch
41
+ of the kaza fork) relayed a spoken question into a task channel as the
42
+ speaker, and the daemon ACKed the event and dropped it at the `bot_id`
43
+ check with no log line. Seen 2026-09-02, message
44
+ `{user: U…, bot_id: B0BUDMNPD26, app_id: A0BSH7L7N9E, bot_profile: {…}}`.
45
+
46
+ ## How
47
+
48
+ - The client learns its workspace from `auth.test` (`team_id`, already
49
+ called for the bot user) and its own app id from the Socket Mode `hello`
50
+ (`connection_info.app_id`) and, belt and braces, from every `events_api`
51
+ envelope (`payload.api_app_id`). Secondary clients on a shared socket get
52
+ it handed over with each injected event. No new config, no new API call.
53
+ - One predicate, `isBotAuthored(message)`, replaces the three inline
54
+ `user === botUserId || bot_id` checks.
55
+ - `app_id` is added to the `SlackEvent` and `SlackMessage` types; it was
56
+ already on the wire.
57
+ - Until app and team ids are known (only possible before the first `hello`), the
58
+ old rule applies: anything with a `bot_id` is ignored. Conservative, and
59
+ the window is the connection handshake.
60
+
61
+ Why `app_id` and not `bot_id`: the `bot_id` on a user-token post is a
62
+ per-authorisation bot profile (`B0BUDMNPD26` above), different from the
63
+ bot token's own `bot_id` from `auth.test` (`B0BSH828Z6U`). `app_id` is the
64
+ one stable identity.
65
+
66
+ ## Decisions
67
+
68
+ | Decision | Why |
69
+ |---|---|
70
+ | No config flag | with `app_id` and `team` checked, only this installation's own user tokens qualify; nothing to opt out of |
71
+ | `team` must match too (Codex review) | Slack Connect lets the same app be installed on both sides of a shared channel; the other copy's bot shares our `app_id` but not our `team`, and two daemons answering each other is the one loop this must never open |
72
+ | Learn the app id from the socket, not config | zero setup; every install already receives it |
73
+ | Old behaviour until the app id is known | never widen the filter on a guess |
74
+
75
+ ## Lessons learned
76
+
77
+ - A dropped event at the "ignore ourselves" check leaves no trace in the
78
+ log. Reproducing needed `conversations.history` on the exact `ts` to see
79
+ the stored message's fields.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "1.32.0",
3
+ "version": "1.33.0",
4
4
  "description": "Run Claude Code from Slack or Mattermost. Sessions stream live into threads where your whole team can watch and steer.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -76,7 +76,7 @@
76
76
  "commander": "^14.0.2",
77
77
  "diff": "^8.0.3",
78
78
  "express-rate-limit": "^8.3.0",
79
- "hono": "4.13.3",
79
+ "hono": "4.13.5",
80
80
  "ink": "^6.6.0",
81
81
  "ink-scroll-view": "^0.3.5",
82
82
  "js-yaml": "^4.3.1",
@@ -99,7 +99,7 @@
99
99
  "@types/ws": "^8.18.0",
100
100
  "eslint": "^10.7.0",
101
101
  "husky": "^9.1.7",
102
- "knip": "6.32.2",
102
+ "knip": "6.33.0",
103
103
  "lint-staged": "^17.0.4",
104
104
  "prettier": "^3.9.5",
105
105
  "typescript": "^6.0.2",
@@ -122,7 +122,7 @@
122
122
  "flatted": ">=3.4.0",
123
123
  "picomatch": ">=2.3.2",
124
124
  "path-to-regexp": ">=8.4.0",
125
- "fast-uri": ">=4.1.2",
125
+ "fast-uri": ">=4.1.4",
126
126
  "brace-expansion": ">=5.0.9",
127
127
  "ip-address": ">=10.3.1",
128
128
  "ws": "$ws",
@@ -135,7 +135,7 @@
135
135
  "flatted": ">=3.4.0",
136
136
  "picomatch": ">=2.3.2",
137
137
  "path-to-regexp": ">=8.4.0",
138
- "fast-uri": ">=4.1.2",
138
+ "fast-uri": ">=4.1.4",
139
139
  "brace-expansion": ">=5.0.9",
140
140
  "ip-address": ">=10.3.1",
141
141
  "ws": "$ws",