@frontmcp/utils 1.12.0 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/escape/escape.mjs +25 -0
- package/browser/escape/index.mjs +9 -1
- package/browser/http/index.mjs +4 -0
- package/browser/http/limited-body.mjs +74 -0
- package/browser/index.mjs +28 -2
- package/browser/optional-module/index.mjs +6 -0
- package/browser/optional-module/optional-module.mjs +18 -0
- package/browser/redaction/redaction.mjs +6 -4
- package/browser/stdio-bridge/sse-frames.mjs +3 -1
- package/browser/stdio-bridge/stdio-framer.mjs +4 -1
- package/browser/storage/adapters/filesystem.mjs +31 -9
- package/browser/storage/adapters/memory.mjs +25 -26
- package/browser/storage/factory.mjs +2 -1
- package/browser/storage/index.mjs +2 -1
- package/browser/storage/utils/index.mjs +2 -1
- package/browser/storage/utils/pattern.mjs +71 -24
- package/browser/uri/uri-template.mjs +93 -15
- package/crypto/key-persistence/types.d.ts +4 -1
- package/escape/escape.d.ts +19 -0
- package/escape/index.d.ts +1 -1
- package/esm/index.mjs +1 -1
- package/http/index.d.ts +2 -0
- package/http/limited-body.d.ts +56 -0
- package/index.d.ts +5 -4
- package/index.js +1 -1
- package/optional-module/index.d.ts +1 -0
- package/optional-module/optional-module.d.ts +12 -0
- package/package.json +3 -3
- package/redaction/redaction.d.ts +1 -1
- package/storage/adapters/filesystem.d.ts +11 -0
- package/storage/adapters/memory.d.ts +6 -2
- package/storage/index.d.ts +1 -1
- package/storage/utils/index.d.ts +1 -1
- package/storage/utils/pattern.d.ts +31 -4
|
@@ -2,38 +2,85 @@
|
|
|
2
2
|
import { StoragePatternError } from "../errors.mjs";
|
|
3
3
|
var MAX_PATTERN_LENGTH = 500;
|
|
4
4
|
var MAX_WILDCARDS = 20;
|
|
5
|
-
var REGEX_SPECIAL_CHARS = /[
|
|
6
|
-
function
|
|
5
|
+
var REGEX_SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g;
|
|
6
|
+
function tokenizeGlob(pattern) {
|
|
7
7
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
8
8
|
throw new StoragePatternError(
|
|
9
9
|
pattern.substring(0, 50) + "...",
|
|
10
10
|
`Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`
|
|
11
11
|
);
|
|
12
12
|
}
|
|
13
|
-
const
|
|
13
|
+
const tokens = [];
|
|
14
|
+
let wildcardCount = 0;
|
|
15
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
16
|
+
const char = pattern[i];
|
|
17
|
+
if (char === "\\" && i + 1 < pattern.length) {
|
|
18
|
+
i++;
|
|
19
|
+
tokens.push({ kind: "literal", char: pattern[i] });
|
|
20
|
+
} else if (char === "*") {
|
|
21
|
+
wildcardCount++;
|
|
22
|
+
if (tokens[tokens.length - 1]?.kind !== "star") tokens.push({ kind: "star" });
|
|
23
|
+
} else if (char === "?") {
|
|
24
|
+
wildcardCount++;
|
|
25
|
+
tokens.push({ kind: "one" });
|
|
26
|
+
} else {
|
|
27
|
+
tokens.push({ kind: "literal", char });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
14
30
|
if (wildcardCount > MAX_WILDCARDS) {
|
|
15
31
|
throw new StoragePatternError(pattern, `Pattern has too many wildcards (max: ${MAX_WILDCARDS})`);
|
|
16
32
|
}
|
|
17
|
-
|
|
18
|
-
|
|
33
|
+
return tokens;
|
|
34
|
+
}
|
|
35
|
+
function matchesEverything(tokens) {
|
|
36
|
+
return tokens.length === 0 || tokens.length === 1 && tokens[0].kind === "star";
|
|
37
|
+
}
|
|
38
|
+
function matchTokens(tokens, key) {
|
|
39
|
+
let k = 0;
|
|
40
|
+
let t = 0;
|
|
41
|
+
let starToken = -1;
|
|
42
|
+
let starKey = 0;
|
|
43
|
+
while (k < key.length) {
|
|
44
|
+
const token = tokens[t];
|
|
45
|
+
if (token !== void 0 && (token.kind === "one" || token.kind === "literal" && token.char === key[k])) {
|
|
46
|
+
t++;
|
|
47
|
+
k++;
|
|
48
|
+
} else if (token !== void 0 && token.kind === "star") {
|
|
49
|
+
starToken = t;
|
|
50
|
+
starKey = k;
|
|
51
|
+
t++;
|
|
52
|
+
} else if (starToken !== -1) {
|
|
53
|
+
t = starToken + 1;
|
|
54
|
+
starKey++;
|
|
55
|
+
k = starKey;
|
|
56
|
+
} else {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
while (t < tokens.length && tokens[t].kind === "star") t++;
|
|
61
|
+
return t === tokens.length;
|
|
62
|
+
}
|
|
63
|
+
function createGlobMatcher(pattern) {
|
|
64
|
+
const tokens = tokenizeGlob(pattern);
|
|
65
|
+
if (matchesEverything(tokens)) {
|
|
66
|
+
return () => true;
|
|
67
|
+
}
|
|
68
|
+
return (key) => matchTokens(tokens, key);
|
|
69
|
+
}
|
|
70
|
+
function globToRegex(pattern) {
|
|
71
|
+
const tokens = tokenizeGlob(pattern);
|
|
72
|
+
if (matchesEverything(tokens)) {
|
|
73
|
+
return /^[\s\S]*$/;
|
|
19
74
|
}
|
|
20
75
|
let regexStr = "^";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
break;
|
|
30
|
-
case "?":
|
|
31
|
-
regexStr += ".";
|
|
32
|
-
break;
|
|
33
|
-
default:
|
|
34
|
-
regexStr += char.replace(REGEX_SPECIAL_CHARS, "\\$&");
|
|
76
|
+
for (const token of tokens) {
|
|
77
|
+
if (token.kind === "star") {
|
|
78
|
+
regexStr += "[\\s\\S]*";
|
|
79
|
+
} else if (token.kind === "one") {
|
|
80
|
+
regexStr += "[\\s\\S]";
|
|
81
|
+
} else {
|
|
82
|
+
regexStr += token.char.replace(REGEX_SPECIAL_CHARS, "\\$&");
|
|
35
83
|
}
|
|
36
|
-
prevChar = char;
|
|
37
84
|
}
|
|
38
85
|
regexStr += "$";
|
|
39
86
|
try {
|
|
@@ -43,12 +90,11 @@ function globToRegex(pattern) {
|
|
|
43
90
|
}
|
|
44
91
|
}
|
|
45
92
|
function matchesPattern(key, pattern) {
|
|
46
|
-
|
|
47
|
-
return regex.test(key);
|
|
93
|
+
return createGlobMatcher(pattern)(key);
|
|
48
94
|
}
|
|
49
95
|
function validatePattern(pattern) {
|
|
50
96
|
try {
|
|
51
|
-
|
|
97
|
+
tokenizeGlob(pattern);
|
|
52
98
|
return { valid: true };
|
|
53
99
|
} catch (e) {
|
|
54
100
|
return {
|
|
@@ -58,9 +104,10 @@ function validatePattern(pattern) {
|
|
|
58
104
|
}
|
|
59
105
|
}
|
|
60
106
|
function escapeGlob(literal) {
|
|
61
|
-
return literal.replace(/[
|
|
107
|
+
return literal.replace(/[*?[\]\\]/g, "\\$&");
|
|
62
108
|
}
|
|
63
109
|
export {
|
|
110
|
+
createGlobMatcher,
|
|
64
111
|
escapeGlob,
|
|
65
112
|
globToRegex,
|
|
66
113
|
matchesPattern,
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
// libs/utils/src/uri/uri-template.ts
|
|
2
2
|
function parseUriTemplate(template) {
|
|
3
|
+
const parts = templateParts(template);
|
|
4
|
+
return {
|
|
5
|
+
pattern: new RegExp(`^${parts.map(regexSourceOf).join("")}$`),
|
|
6
|
+
paramNames: paramNamesOf(parts)
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function templateParts(template) {
|
|
3
10
|
if (template.length > 1e3) {
|
|
4
11
|
throw new Error("URI template too long (max 1000 characters)");
|
|
5
12
|
}
|
|
@@ -7,34 +14,105 @@ function parseUriTemplate(template) {
|
|
|
7
14
|
if (paramCount > 50) {
|
|
8
15
|
throw new Error("URI template has too many parameters (max 50)");
|
|
9
16
|
}
|
|
10
|
-
const
|
|
17
|
+
const parts = [];
|
|
11
18
|
const placeholder = /\{([^{}]+)\}/g;
|
|
12
|
-
let regexStr = "";
|
|
13
19
|
let lastIndex = 0;
|
|
14
20
|
for (let m = placeholder.exec(template); m !== null; m = placeholder.exec(template)) {
|
|
15
|
-
|
|
21
|
+
parts.push({ kind: "literal", text: template.slice(lastIndex, m.index) });
|
|
16
22
|
const raw = m[1];
|
|
17
23
|
const reserved = raw.startsWith("+");
|
|
18
|
-
|
|
19
|
-
regexStr += reserved ? "(.+?)" : "([^/]+)";
|
|
24
|
+
parts.push({ kind: "param", name: reserved ? raw.slice(1) : raw, reserved });
|
|
20
25
|
lastIndex = m.index + m[0].length;
|
|
21
26
|
}
|
|
22
|
-
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
parts.push({ kind: "literal", text: template.slice(lastIndex) });
|
|
28
|
+
return parts;
|
|
29
|
+
}
|
|
30
|
+
function regexSourceOf(part) {
|
|
31
|
+
if (part.kind === "literal") return escapeRegex(part.text);
|
|
32
|
+
return part.reserved ? "(.+?)" : "([^/]+)";
|
|
33
|
+
}
|
|
34
|
+
function paramNamesOf(parts) {
|
|
35
|
+
return parts.flatMap((part) => part.kind === "param" ? [part.name] : []);
|
|
27
36
|
}
|
|
28
37
|
function escapeRegex(literal) {
|
|
29
38
|
return literal.replace(/[.*+?^${}()|[\]\\]/g, (c) => `\\${c}`);
|
|
30
39
|
}
|
|
40
|
+
function matchTemplateParts(parts, uri) {
|
|
41
|
+
const fitsFrom = new Array(parts.length + 1);
|
|
42
|
+
fitsFrom[parts.length] = new PositionSet(uri.length);
|
|
43
|
+
fitsFrom[parts.length].add(uri.length);
|
|
44
|
+
for (let index = parts.length - 1; index >= 0; index--) {
|
|
45
|
+
fitsFrom[index] = positionsWherePartFits(parts[index], uri, fitsFrom[index + 1]);
|
|
46
|
+
if (parts[index].kind !== "param") fitsFrom[index + 1] = EMPTY_POSITIONS;
|
|
47
|
+
}
|
|
48
|
+
if (!fitsFrom[0].has(0)) return null;
|
|
49
|
+
const values = [];
|
|
50
|
+
let position = 0;
|
|
51
|
+
parts.forEach((part, index) => {
|
|
52
|
+
if (part.kind === "literal") {
|
|
53
|
+
position += part.text.length;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const valueEnd = firstPositionNotHeld(part, uri, position);
|
|
57
|
+
const rest = fitsFrom[index + 1];
|
|
58
|
+
let end = part.reserved ? position + 1 : valueEnd;
|
|
59
|
+
while (!rest.has(end)) end += part.reserved ? 1 : -1;
|
|
60
|
+
values.push(uri.slice(position, end));
|
|
61
|
+
position = end;
|
|
62
|
+
});
|
|
63
|
+
return values;
|
|
64
|
+
}
|
|
65
|
+
function positionsWherePartFits(part, uri, rest) {
|
|
66
|
+
const fits = new PositionSet(uri.length);
|
|
67
|
+
if (part.kind === "literal") {
|
|
68
|
+
for (let position = 0; position + part.text.length <= uri.length; position++) {
|
|
69
|
+
if (rest.has(position + part.text.length) && uri.startsWith(part.text, position)) fits.add(position);
|
|
70
|
+
}
|
|
71
|
+
return fits;
|
|
72
|
+
}
|
|
73
|
+
let valueEnd = uri.length;
|
|
74
|
+
let nearestRest = Infinity;
|
|
75
|
+
for (let position = uri.length - 1; position >= 0; position--) {
|
|
76
|
+
if (rest.has(position + 1)) nearestRest = position + 1;
|
|
77
|
+
if (!canHold(part, uri.charCodeAt(position))) valueEnd = position;
|
|
78
|
+
if (nearestRest <= valueEnd) fits.add(position);
|
|
79
|
+
}
|
|
80
|
+
return fits;
|
|
81
|
+
}
|
|
82
|
+
function firstPositionNotHeld(part, uri, position) {
|
|
83
|
+
let end = position;
|
|
84
|
+
while (end < uri.length && canHold(part, uri.charCodeAt(end))) end++;
|
|
85
|
+
return end;
|
|
86
|
+
}
|
|
87
|
+
function canHold(part, charCode) {
|
|
88
|
+
if (part.kind !== "param") return false;
|
|
89
|
+
if (!part.reserved) return charCode !== SLASH;
|
|
90
|
+
return !LINE_TERMINATORS.has(charCode);
|
|
91
|
+
}
|
|
92
|
+
var SLASH = 47;
|
|
93
|
+
var LINE_TERMINATORS = /* @__PURE__ */ new Set([10, 13, 8232, 8233]);
|
|
94
|
+
var PositionSet = class {
|
|
95
|
+
bits;
|
|
96
|
+
constructor(length) {
|
|
97
|
+
this.bits = new Uint8Array((length >> 3) + 1);
|
|
98
|
+
}
|
|
99
|
+
/** Add `position`. */
|
|
100
|
+
add(position) {
|
|
101
|
+
this.bits[position >> 3] |= 1 << (position & 7);
|
|
102
|
+
}
|
|
103
|
+
/** Whether `position` was added. */
|
|
104
|
+
has(position) {
|
|
105
|
+
return position >= 0 && ((this.bits[position >> 3] ?? 0) & 1 << (position & 7)) !== 0;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var EMPTY_POSITIONS = new PositionSet(0);
|
|
31
109
|
function matchUriTemplate(template, uri) {
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
if (!
|
|
110
|
+
const parts = templateParts(template);
|
|
111
|
+
const values = matchTemplateParts(parts, uri);
|
|
112
|
+
if (!values) return null;
|
|
35
113
|
const params = {};
|
|
36
|
-
|
|
37
|
-
params[name] = decodeURIComponent(
|
|
114
|
+
paramNamesOf(parts).forEach((name, index) => {
|
|
115
|
+
params[name] = decodeURIComponent(values[index]);
|
|
38
116
|
});
|
|
39
117
|
return params;
|
|
40
118
|
}
|
|
@@ -102,7 +102,10 @@ export interface CreateKeyPersistenceOptions {
|
|
|
102
102
|
/**
|
|
103
103
|
* Optional 32-byte AES-256-GCM key for encrypting values at rest in localStorage.
|
|
104
104
|
* Only used when type is 'localstorage' or 'auto' with localStorage fallback.
|
|
105
|
-
* If not provided, HKDF-SHA256 derives a key from a fixed IKM
|
|
105
|
+
* If not provided, HKDF-SHA256 derives a key from a fixed IKM, a random salt kept in the same
|
|
106
|
+
* localStorage and the page origin: values are not readable at a glance, but anyone who can read
|
|
107
|
+
* this origin's localStorage can derive the key and decrypt them. To protect them from such a
|
|
108
|
+
* reader, pass a key kept outside localStorage.
|
|
106
109
|
*/
|
|
107
110
|
encryptionKey?: Uint8Array;
|
|
108
111
|
}
|
package/escape/escape.d.ts
CHANGED
|
@@ -99,3 +99,22 @@ export declare function escapeScriptClose(jsonString: string): string;
|
|
|
99
99
|
* ```
|
|
100
100
|
*/
|
|
101
101
|
export declare function safeJsonForScript(value: unknown): string;
|
|
102
|
+
/**
|
|
103
|
+
* Escape control characters so that a value always stays on one line of text output.
|
|
104
|
+
*
|
|
105
|
+
* Use it on values that come from callers (names, URIs, identifiers, messages from a remote peer) before they go into
|
|
106
|
+
* a log line or a terminal: a raw line break would let the value forge a separate log record, and an escape character
|
|
107
|
+
* could rewrite the terminal. Line feed, carriage return, backspace, form feed and vertical tab become a backslash
|
|
108
|
+
* and `n`, `r`, `b`, `f` or `v`; other C0 and C1 controls, DEL, U+2028 and U+2029 become a backslash, `u` and four hex
|
|
109
|
+
* digits. Tabs and every other character are kept.
|
|
110
|
+
*
|
|
111
|
+
* @param value - Value to escape (converted to a string first; null and undefined become an empty string)
|
|
112
|
+
* @returns The value with every control character replaced by a visible escape
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* escapeControlCharacters('tool\nINFO forged entry'); // 'tool\\nINFO forged entry'
|
|
117
|
+
* escapeControlCharacters('\u001b[31mred'); // '\\u001b[31mred'
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export declare function escapeControlCharacters(value: unknown): string;
|
package/escape/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { escapeHtml, escapeHtmlAttr, escapeJsString, escapeScriptClose, safeJsonForScript } from './escape';
|
|
1
|
+
export { escapeHtml, escapeHtmlAttr, escapeJsString, escapeScriptClose, safeJsonForScript, escapeControlCharacters, } from './escape';
|