@chrrxs/robloxstudio-mcp-inspector 3.0.2 → 3.0.3
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 +2778 -440
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +1135 -731
package/dist/index.js
CHANGED
|
@@ -1,4 +1,1937 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
|
|
28
|
+
// ../../node_modules/xmlchars/xml/1.0/ed5.js
|
|
29
|
+
var require_ed5 = __commonJS({
|
|
30
|
+
"../../node_modules/xmlchars/xml/1.0/ed5.js"(exports) {
|
|
31
|
+
"use strict";
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.CHAR = " \n\r -\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}";
|
|
34
|
+
exports.S = " \r\n";
|
|
35
|
+
exports.NAME_START_CHAR = ":A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}";
|
|
36
|
+
exports.NAME_CHAR = "-" + exports.NAME_START_CHAR + ".0-9\xB7\u0300-\u036F\u203F-\u2040";
|
|
37
|
+
exports.CHAR_RE = new RegExp("^[" + exports.CHAR + "]$", "u");
|
|
38
|
+
exports.S_RE = new RegExp("^[" + exports.S + "]+$", "u");
|
|
39
|
+
exports.NAME_START_CHAR_RE = new RegExp("^[" + exports.NAME_START_CHAR + "]$", "u");
|
|
40
|
+
exports.NAME_CHAR_RE = new RegExp("^[" + exports.NAME_CHAR + "]$", "u");
|
|
41
|
+
exports.NAME_RE = new RegExp("^[" + exports.NAME_START_CHAR + "][" + exports.NAME_CHAR + "]*$", "u");
|
|
42
|
+
exports.NMTOKEN_RE = new RegExp("^[" + exports.NAME_CHAR + "]+$", "u");
|
|
43
|
+
var TAB = 9;
|
|
44
|
+
var NL = 10;
|
|
45
|
+
var CR = 13;
|
|
46
|
+
var SPACE = 32;
|
|
47
|
+
exports.S_LIST = [SPACE, NL, CR, TAB];
|
|
48
|
+
function isChar(c) {
|
|
49
|
+
return c >= SPACE && c <= 55295 || c === NL || c === CR || c === TAB || c >= 57344 && c <= 65533 || c >= 65536 && c <= 1114111;
|
|
50
|
+
}
|
|
51
|
+
exports.isChar = isChar;
|
|
52
|
+
function isS(c) {
|
|
53
|
+
return c === SPACE || c === NL || c === CR || c === TAB;
|
|
54
|
+
}
|
|
55
|
+
exports.isS = isS;
|
|
56
|
+
function isNameStartChar(c) {
|
|
57
|
+
return c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 58 || c === 95 || c === 8204 || c === 8205 || c >= 192 && c <= 214 || c >= 216 && c <= 246 || c >= 248 && c <= 767 || c >= 880 && c <= 893 || c >= 895 && c <= 8191 || c >= 8304 && c <= 8591 || c >= 11264 && c <= 12271 || c >= 12289 && c <= 55295 || c >= 63744 && c <= 64975 || c >= 65008 && c <= 65533 || c >= 65536 && c <= 983039;
|
|
58
|
+
}
|
|
59
|
+
exports.isNameStartChar = isNameStartChar;
|
|
60
|
+
function isNameChar(c) {
|
|
61
|
+
return isNameStartChar(c) || c >= 48 && c <= 57 || c === 45 || c === 46 || c === 183 || c >= 768 && c <= 879 || c >= 8255 && c <= 8256;
|
|
62
|
+
}
|
|
63
|
+
exports.isNameChar = isNameChar;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ../../node_modules/xmlchars/xml/1.1/ed2.js
|
|
68
|
+
var require_ed2 = __commonJS({
|
|
69
|
+
"../../node_modules/xmlchars/xml/1.1/ed2.js"(exports) {
|
|
70
|
+
"use strict";
|
|
71
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
72
|
+
exports.CHAR = "-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}";
|
|
73
|
+
exports.RESTRICTED_CHAR = "-\b\v\f-\x7F-\x84\x86-\x9F";
|
|
74
|
+
exports.S = " \r\n";
|
|
75
|
+
exports.NAME_START_CHAR = ":A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}";
|
|
76
|
+
exports.NAME_CHAR = "-" + exports.NAME_START_CHAR + ".0-9\xB7\u0300-\u036F\u203F-\u2040";
|
|
77
|
+
exports.CHAR_RE = new RegExp("^[" + exports.CHAR + "]$", "u");
|
|
78
|
+
exports.RESTRICTED_CHAR_RE = new RegExp("^[" + exports.RESTRICTED_CHAR + "]$", "u");
|
|
79
|
+
exports.S_RE = new RegExp("^[" + exports.S + "]+$", "u");
|
|
80
|
+
exports.NAME_START_CHAR_RE = new RegExp("^[" + exports.NAME_START_CHAR + "]$", "u");
|
|
81
|
+
exports.NAME_CHAR_RE = new RegExp("^[" + exports.NAME_CHAR + "]$", "u");
|
|
82
|
+
exports.NAME_RE = new RegExp("^[" + exports.NAME_START_CHAR + "][" + exports.NAME_CHAR + "]*$", "u");
|
|
83
|
+
exports.NMTOKEN_RE = new RegExp("^[" + exports.NAME_CHAR + "]+$", "u");
|
|
84
|
+
var TAB = 9;
|
|
85
|
+
var NL = 10;
|
|
86
|
+
var CR = 13;
|
|
87
|
+
var SPACE = 32;
|
|
88
|
+
exports.S_LIST = [SPACE, NL, CR, TAB];
|
|
89
|
+
function isChar(c) {
|
|
90
|
+
return c >= 1 && c <= 55295 || c >= 57344 && c <= 65533 || c >= 65536 && c <= 1114111;
|
|
91
|
+
}
|
|
92
|
+
exports.isChar = isChar;
|
|
93
|
+
function isRestrictedChar(c) {
|
|
94
|
+
return c >= 1 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31 || c >= 127 && c <= 132 || c >= 134 && c <= 159;
|
|
95
|
+
}
|
|
96
|
+
exports.isRestrictedChar = isRestrictedChar;
|
|
97
|
+
function isCharAndNotRestricted(c) {
|
|
98
|
+
return c === 9 || c === 10 || c === 13 || c > 31 && c < 127 || c === 133 || c > 159 && c <= 55295 || c >= 57344 && c <= 65533 || c >= 65536 && c <= 1114111;
|
|
99
|
+
}
|
|
100
|
+
exports.isCharAndNotRestricted = isCharAndNotRestricted;
|
|
101
|
+
function isS(c) {
|
|
102
|
+
return c === SPACE || c === NL || c === CR || c === TAB;
|
|
103
|
+
}
|
|
104
|
+
exports.isS = isS;
|
|
105
|
+
function isNameStartChar(c) {
|
|
106
|
+
return c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 58 || c === 95 || c === 8204 || c === 8205 || c >= 192 && c <= 214 || c >= 216 && c <= 246 || c >= 248 && c <= 767 || c >= 880 && c <= 893 || c >= 895 && c <= 8191 || c >= 8304 && c <= 8591 || c >= 11264 && c <= 12271 || c >= 12289 && c <= 55295 || c >= 63744 && c <= 64975 || c >= 65008 && c <= 65533 || c >= 65536 && c <= 983039;
|
|
107
|
+
}
|
|
108
|
+
exports.isNameStartChar = isNameStartChar;
|
|
109
|
+
function isNameChar(c) {
|
|
110
|
+
return isNameStartChar(c) || c >= 48 && c <= 57 || c === 45 || c === 46 || c === 183 || c >= 768 && c <= 879 || c >= 8255 && c <= 8256;
|
|
111
|
+
}
|
|
112
|
+
exports.isNameChar = isNameChar;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ../../node_modules/xmlchars/xmlns/1.0/ed3.js
|
|
117
|
+
var require_ed3 = __commonJS({
|
|
118
|
+
"../../node_modules/xmlchars/xmlns/1.0/ed3.js"(exports) {
|
|
119
|
+
"use strict";
|
|
120
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
121
|
+
exports.NC_NAME_START_CHAR = "A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}";
|
|
122
|
+
exports.NC_NAME_CHAR = "-" + exports.NC_NAME_START_CHAR + ".0-9\xB7\u0300-\u036F\u203F-\u2040";
|
|
123
|
+
exports.NC_NAME_START_CHAR_RE = new RegExp("^[" + exports.NC_NAME_START_CHAR + "]$", "u");
|
|
124
|
+
exports.NC_NAME_CHAR_RE = new RegExp("^[" + exports.NC_NAME_CHAR + "]$", "u");
|
|
125
|
+
exports.NC_NAME_RE = new RegExp("^[" + exports.NC_NAME_START_CHAR + "][" + exports.NC_NAME_CHAR + "]*$", "u");
|
|
126
|
+
function isNCNameStartChar(c) {
|
|
127
|
+
return c >= 65 && c <= 90 || c === 95 || c >= 97 && c <= 122 || c >= 192 && c <= 214 || c >= 216 && c <= 246 || c >= 248 && c <= 767 || c >= 880 && c <= 893 || c >= 895 && c <= 8191 || c >= 8204 && c <= 8205 || c >= 8304 && c <= 8591 || c >= 11264 && c <= 12271 || c >= 12289 && c <= 55295 || c >= 63744 && c <= 64975 || c >= 65008 && c <= 65533 || c >= 65536 && c <= 983039;
|
|
128
|
+
}
|
|
129
|
+
exports.isNCNameStartChar = isNCNameStartChar;
|
|
130
|
+
function isNCNameChar(c) {
|
|
131
|
+
return isNCNameStartChar(c) || (c === 45 || c === 46 || c >= 48 && c <= 57 || c === 183 || c >= 768 && c <= 879 || c >= 8255 && c <= 8256);
|
|
132
|
+
}
|
|
133
|
+
exports.isNCNameChar = isNCNameChar;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ../../node_modules/saxes/saxes.js
|
|
138
|
+
var require_saxes = __commonJS({
|
|
139
|
+
"../../node_modules/saxes/saxes.js"(exports) {
|
|
140
|
+
"use strict";
|
|
141
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
142
|
+
exports.SaxesParser = exports.EVENTS = void 0;
|
|
143
|
+
var ed5 = require_ed5();
|
|
144
|
+
var ed2 = require_ed2();
|
|
145
|
+
var NSed3 = require_ed3();
|
|
146
|
+
var isS = ed5.isS;
|
|
147
|
+
var isChar10 = ed5.isChar;
|
|
148
|
+
var isNameStartChar = ed5.isNameStartChar;
|
|
149
|
+
var isNameChar = ed5.isNameChar;
|
|
150
|
+
var S_LIST = ed5.S_LIST;
|
|
151
|
+
var NAME_RE = ed5.NAME_RE;
|
|
152
|
+
var isChar11 = ed2.isChar;
|
|
153
|
+
var isNCNameStartChar = NSed3.isNCNameStartChar;
|
|
154
|
+
var isNCNameChar = NSed3.isNCNameChar;
|
|
155
|
+
var NC_NAME_RE = NSed3.NC_NAME_RE;
|
|
156
|
+
var XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
|
|
157
|
+
var XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
|
|
158
|
+
var rootNS = {
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
|
|
160
|
+
__proto__: null,
|
|
161
|
+
xml: XML_NAMESPACE,
|
|
162
|
+
xmlns: XMLNS_NAMESPACE
|
|
163
|
+
};
|
|
164
|
+
var XML_ENTITIES = {
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
|
|
166
|
+
__proto__: null,
|
|
167
|
+
amp: "&",
|
|
168
|
+
gt: ">",
|
|
169
|
+
lt: "<",
|
|
170
|
+
quot: '"',
|
|
171
|
+
apos: "'"
|
|
172
|
+
};
|
|
173
|
+
var EOC = -1;
|
|
174
|
+
var NL_LIKE = -2;
|
|
175
|
+
var S_BEGIN = 0;
|
|
176
|
+
var S_BEGIN_WHITESPACE = 1;
|
|
177
|
+
var S_DOCTYPE = 2;
|
|
178
|
+
var S_DOCTYPE_QUOTE = 3;
|
|
179
|
+
var S_DTD = 4;
|
|
180
|
+
var S_DTD_QUOTED = 5;
|
|
181
|
+
var S_DTD_OPEN_WAKA = 6;
|
|
182
|
+
var S_DTD_OPEN_WAKA_BANG = 7;
|
|
183
|
+
var S_DTD_COMMENT = 8;
|
|
184
|
+
var S_DTD_COMMENT_ENDING = 9;
|
|
185
|
+
var S_DTD_COMMENT_ENDED = 10;
|
|
186
|
+
var S_DTD_PI = 11;
|
|
187
|
+
var S_DTD_PI_ENDING = 12;
|
|
188
|
+
var S_TEXT = 13;
|
|
189
|
+
var S_ENTITY = 14;
|
|
190
|
+
var S_OPEN_WAKA = 15;
|
|
191
|
+
var S_OPEN_WAKA_BANG = 16;
|
|
192
|
+
var S_COMMENT = 17;
|
|
193
|
+
var S_COMMENT_ENDING = 18;
|
|
194
|
+
var S_COMMENT_ENDED = 19;
|
|
195
|
+
var S_CDATA = 20;
|
|
196
|
+
var S_CDATA_ENDING = 21;
|
|
197
|
+
var S_CDATA_ENDING_2 = 22;
|
|
198
|
+
var S_PI_FIRST_CHAR = 23;
|
|
199
|
+
var S_PI_REST = 24;
|
|
200
|
+
var S_PI_BODY = 25;
|
|
201
|
+
var S_PI_ENDING = 26;
|
|
202
|
+
var S_XML_DECL_NAME_START = 27;
|
|
203
|
+
var S_XML_DECL_NAME = 28;
|
|
204
|
+
var S_XML_DECL_EQ = 29;
|
|
205
|
+
var S_XML_DECL_VALUE_START = 30;
|
|
206
|
+
var S_XML_DECL_VALUE = 31;
|
|
207
|
+
var S_XML_DECL_SEPARATOR = 32;
|
|
208
|
+
var S_XML_DECL_ENDING = 33;
|
|
209
|
+
var S_OPEN_TAG = 34;
|
|
210
|
+
var S_OPEN_TAG_SLASH = 35;
|
|
211
|
+
var S_ATTRIB = 36;
|
|
212
|
+
var S_ATTRIB_NAME = 37;
|
|
213
|
+
var S_ATTRIB_NAME_SAW_WHITE = 38;
|
|
214
|
+
var S_ATTRIB_VALUE = 39;
|
|
215
|
+
var S_ATTRIB_VALUE_QUOTED = 40;
|
|
216
|
+
var S_ATTRIB_VALUE_CLOSED = 41;
|
|
217
|
+
var S_ATTRIB_VALUE_UNQUOTED = 42;
|
|
218
|
+
var S_CLOSE_TAG = 43;
|
|
219
|
+
var S_CLOSE_TAG_SAW_WHITE = 44;
|
|
220
|
+
var TAB = 9;
|
|
221
|
+
var NL = 10;
|
|
222
|
+
var CR = 13;
|
|
223
|
+
var SPACE = 32;
|
|
224
|
+
var BANG = 33;
|
|
225
|
+
var DQUOTE = 34;
|
|
226
|
+
var AMP = 38;
|
|
227
|
+
var SQUOTE = 39;
|
|
228
|
+
var MINUS = 45;
|
|
229
|
+
var FORWARD_SLASH = 47;
|
|
230
|
+
var SEMICOLON = 59;
|
|
231
|
+
var LESS = 60;
|
|
232
|
+
var EQUAL = 61;
|
|
233
|
+
var GREATER = 62;
|
|
234
|
+
var QUESTION = 63;
|
|
235
|
+
var OPEN_BRACKET = 91;
|
|
236
|
+
var CLOSE_BRACKET = 93;
|
|
237
|
+
var NEL = 133;
|
|
238
|
+
var LS = 8232;
|
|
239
|
+
var isQuote = (c) => c === DQUOTE || c === SQUOTE;
|
|
240
|
+
var QUOTES = [DQUOTE, SQUOTE];
|
|
241
|
+
var DOCTYPE_TERMINATOR = [...QUOTES, OPEN_BRACKET, GREATER];
|
|
242
|
+
var DTD_TERMINATOR = [...QUOTES, LESS, CLOSE_BRACKET];
|
|
243
|
+
var XML_DECL_NAME_TERMINATOR = [EQUAL, QUESTION, ...S_LIST];
|
|
244
|
+
var ATTRIB_VALUE_UNQUOTED_TERMINATOR = [...S_LIST, GREATER, AMP, LESS];
|
|
245
|
+
function nsPairCheck(parser, prefix, uri) {
|
|
246
|
+
switch (prefix) {
|
|
247
|
+
case "xml":
|
|
248
|
+
if (uri !== XML_NAMESPACE) {
|
|
249
|
+
parser.fail(`xml prefix must be bound to ${XML_NAMESPACE}.`);
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
case "xmlns":
|
|
253
|
+
if (uri !== XMLNS_NAMESPACE) {
|
|
254
|
+
parser.fail(`xmlns prefix must be bound to ${XMLNS_NAMESPACE}.`);
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
default:
|
|
258
|
+
}
|
|
259
|
+
switch (uri) {
|
|
260
|
+
case XMLNS_NAMESPACE:
|
|
261
|
+
parser.fail(prefix === "" ? `the default namespace may not be set to ${uri}.` : `may not assign a prefix (even "xmlns") to the URI ${XMLNS_NAMESPACE}.`);
|
|
262
|
+
break;
|
|
263
|
+
case XML_NAMESPACE:
|
|
264
|
+
switch (prefix) {
|
|
265
|
+
case "xml":
|
|
266
|
+
break;
|
|
267
|
+
case "":
|
|
268
|
+
parser.fail(`the default namespace may not be set to ${uri}.`);
|
|
269
|
+
break;
|
|
270
|
+
default:
|
|
271
|
+
parser.fail("may not assign the xml namespace to another prefix.");
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
default:
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function nsMappingCheck(parser, mapping) {
|
|
278
|
+
for (const local of Object.keys(mapping)) {
|
|
279
|
+
nsPairCheck(parser, local, mapping[local]);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
var isNCName = (name) => NC_NAME_RE.test(name);
|
|
283
|
+
var isName = (name) => NAME_RE.test(name);
|
|
284
|
+
var FORBIDDEN_START = 0;
|
|
285
|
+
var FORBIDDEN_BRACKET = 1;
|
|
286
|
+
var FORBIDDEN_BRACKET_BRACKET = 2;
|
|
287
|
+
exports.EVENTS = [
|
|
288
|
+
"xmldecl",
|
|
289
|
+
"text",
|
|
290
|
+
"processinginstruction",
|
|
291
|
+
"doctype",
|
|
292
|
+
"comment",
|
|
293
|
+
"opentagstart",
|
|
294
|
+
"attribute",
|
|
295
|
+
"opentag",
|
|
296
|
+
"closetag",
|
|
297
|
+
"cdata",
|
|
298
|
+
"error",
|
|
299
|
+
"end",
|
|
300
|
+
"ready"
|
|
301
|
+
];
|
|
302
|
+
var EVENT_NAME_TO_HANDLER_NAME = {
|
|
303
|
+
xmldecl: "xmldeclHandler",
|
|
304
|
+
text: "textHandler",
|
|
305
|
+
processinginstruction: "piHandler",
|
|
306
|
+
doctype: "doctypeHandler",
|
|
307
|
+
comment: "commentHandler",
|
|
308
|
+
opentagstart: "openTagStartHandler",
|
|
309
|
+
attribute: "attributeHandler",
|
|
310
|
+
opentag: "openTagHandler",
|
|
311
|
+
closetag: "closeTagHandler",
|
|
312
|
+
cdata: "cdataHandler",
|
|
313
|
+
error: "errorHandler",
|
|
314
|
+
end: "endHandler",
|
|
315
|
+
ready: "readyHandler"
|
|
316
|
+
};
|
|
317
|
+
var SaxesParser2 = class {
|
|
318
|
+
/**
|
|
319
|
+
* @param opt The parser options.
|
|
320
|
+
*/
|
|
321
|
+
constructor(opt) {
|
|
322
|
+
this.opt = opt !== null && opt !== void 0 ? opt : {};
|
|
323
|
+
this.fragmentOpt = !!this.opt.fragment;
|
|
324
|
+
const xmlnsOpt = this.xmlnsOpt = !!this.opt.xmlns;
|
|
325
|
+
this.trackPosition = this.opt.position !== false;
|
|
326
|
+
this.fileName = this.opt.fileName;
|
|
327
|
+
if (xmlnsOpt) {
|
|
328
|
+
this.nameStartCheck = isNCNameStartChar;
|
|
329
|
+
this.nameCheck = isNCNameChar;
|
|
330
|
+
this.isName = isNCName;
|
|
331
|
+
this.processAttribs = this.processAttribsNS;
|
|
332
|
+
this.pushAttrib = this.pushAttribNS;
|
|
333
|
+
this.ns = Object.assign({ __proto__: null }, rootNS);
|
|
334
|
+
const additional = this.opt.additionalNamespaces;
|
|
335
|
+
if (additional != null) {
|
|
336
|
+
nsMappingCheck(this, additional);
|
|
337
|
+
Object.assign(this.ns, additional);
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
this.nameStartCheck = isNameStartChar;
|
|
341
|
+
this.nameCheck = isNameChar;
|
|
342
|
+
this.isName = isName;
|
|
343
|
+
this.processAttribs = this.processAttribsPlain;
|
|
344
|
+
this.pushAttrib = this.pushAttribPlain;
|
|
345
|
+
}
|
|
346
|
+
this.stateTable = [
|
|
347
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
348
|
+
this.sBegin,
|
|
349
|
+
this.sBeginWhitespace,
|
|
350
|
+
this.sDoctype,
|
|
351
|
+
this.sDoctypeQuote,
|
|
352
|
+
this.sDTD,
|
|
353
|
+
this.sDTDQuoted,
|
|
354
|
+
this.sDTDOpenWaka,
|
|
355
|
+
this.sDTDOpenWakaBang,
|
|
356
|
+
this.sDTDComment,
|
|
357
|
+
this.sDTDCommentEnding,
|
|
358
|
+
this.sDTDCommentEnded,
|
|
359
|
+
this.sDTDPI,
|
|
360
|
+
this.sDTDPIEnding,
|
|
361
|
+
this.sText,
|
|
362
|
+
this.sEntity,
|
|
363
|
+
this.sOpenWaka,
|
|
364
|
+
this.sOpenWakaBang,
|
|
365
|
+
this.sComment,
|
|
366
|
+
this.sCommentEnding,
|
|
367
|
+
this.sCommentEnded,
|
|
368
|
+
this.sCData,
|
|
369
|
+
this.sCDataEnding,
|
|
370
|
+
this.sCDataEnding2,
|
|
371
|
+
this.sPIFirstChar,
|
|
372
|
+
this.sPIRest,
|
|
373
|
+
this.sPIBody,
|
|
374
|
+
this.sPIEnding,
|
|
375
|
+
this.sXMLDeclNameStart,
|
|
376
|
+
this.sXMLDeclName,
|
|
377
|
+
this.sXMLDeclEq,
|
|
378
|
+
this.sXMLDeclValueStart,
|
|
379
|
+
this.sXMLDeclValue,
|
|
380
|
+
this.sXMLDeclSeparator,
|
|
381
|
+
this.sXMLDeclEnding,
|
|
382
|
+
this.sOpenTag,
|
|
383
|
+
this.sOpenTagSlash,
|
|
384
|
+
this.sAttrib,
|
|
385
|
+
this.sAttribName,
|
|
386
|
+
this.sAttribNameSawWhite,
|
|
387
|
+
this.sAttribValue,
|
|
388
|
+
this.sAttribValueQuoted,
|
|
389
|
+
this.sAttribValueClosed,
|
|
390
|
+
this.sAttribValueUnquoted,
|
|
391
|
+
this.sCloseTag,
|
|
392
|
+
this.sCloseTagSawWhite
|
|
393
|
+
/* eslint-enable @typescript-eslint/unbound-method */
|
|
394
|
+
];
|
|
395
|
+
this._init();
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Indicates whether or not the parser is closed. If ``true``, wait for
|
|
399
|
+
* the ``ready`` event to write again.
|
|
400
|
+
*/
|
|
401
|
+
get closed() {
|
|
402
|
+
return this._closed;
|
|
403
|
+
}
|
|
404
|
+
_init() {
|
|
405
|
+
var _a;
|
|
406
|
+
this.openWakaBang = "";
|
|
407
|
+
this.text = "";
|
|
408
|
+
this.name = "";
|
|
409
|
+
this.piTarget = "";
|
|
410
|
+
this.entity = "";
|
|
411
|
+
this.q = null;
|
|
412
|
+
this.tags = [];
|
|
413
|
+
this.tag = null;
|
|
414
|
+
this.topNS = null;
|
|
415
|
+
this.chunk = "";
|
|
416
|
+
this.chunkPosition = 0;
|
|
417
|
+
this.i = 0;
|
|
418
|
+
this.prevI = 0;
|
|
419
|
+
this.carriedFromPrevious = void 0;
|
|
420
|
+
this.forbiddenState = FORBIDDEN_START;
|
|
421
|
+
this.attribList = [];
|
|
422
|
+
const { fragmentOpt } = this;
|
|
423
|
+
this.state = fragmentOpt ? S_TEXT : S_BEGIN;
|
|
424
|
+
this.reportedTextBeforeRoot = this.reportedTextAfterRoot = this.closedRoot = this.sawRoot = fragmentOpt;
|
|
425
|
+
this.xmlDeclPossible = !fragmentOpt;
|
|
426
|
+
this.xmlDeclExpects = ["version"];
|
|
427
|
+
this.entityReturnState = void 0;
|
|
428
|
+
let { defaultXMLVersion } = this.opt;
|
|
429
|
+
if (defaultXMLVersion === void 0) {
|
|
430
|
+
if (this.opt.forceXMLVersion === true) {
|
|
431
|
+
throw new Error("forceXMLVersion set but defaultXMLVersion is not set");
|
|
432
|
+
}
|
|
433
|
+
defaultXMLVersion = "1.0";
|
|
434
|
+
}
|
|
435
|
+
this.setXMLVersion(defaultXMLVersion);
|
|
436
|
+
this.positionAtNewLine = 0;
|
|
437
|
+
this.doctype = false;
|
|
438
|
+
this._closed = false;
|
|
439
|
+
this.xmlDecl = {
|
|
440
|
+
version: void 0,
|
|
441
|
+
encoding: void 0,
|
|
442
|
+
standalone: void 0
|
|
443
|
+
};
|
|
444
|
+
this.line = 1;
|
|
445
|
+
this.column = 0;
|
|
446
|
+
this.ENTITIES = Object.create(XML_ENTITIES);
|
|
447
|
+
(_a = this.readyHandler) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* The stream position the parser is currently looking at. This field is
|
|
451
|
+
* zero-based.
|
|
452
|
+
*
|
|
453
|
+
* This field is not based on counting Unicode characters but is to be
|
|
454
|
+
* interpreted as a plain index into a JavaScript string.
|
|
455
|
+
*/
|
|
456
|
+
get position() {
|
|
457
|
+
return this.chunkPosition + this.i;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* The column number of the next character to be read by the parser. *
|
|
461
|
+
* This field is zero-based. (The first column in a line is 0.)
|
|
462
|
+
*
|
|
463
|
+
* This field reports the index at which the next character would be in the
|
|
464
|
+
* line if the line were represented as a JavaScript string. Note that this
|
|
465
|
+
* *can* be different to a count based on the number of *Unicode characters*
|
|
466
|
+
* due to how JavaScript handles astral plane characters.
|
|
467
|
+
*
|
|
468
|
+
* See [[column]] for a number that corresponds to a count of Unicode
|
|
469
|
+
* characters.
|
|
470
|
+
*/
|
|
471
|
+
get columnIndex() {
|
|
472
|
+
return this.position - this.positionAtNewLine;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Set an event listener on an event. The parser supports one handler per
|
|
476
|
+
* event type. If you try to set an event handler over an existing handler,
|
|
477
|
+
* the old handler is silently overwritten.
|
|
478
|
+
*
|
|
479
|
+
* @param name The event to listen to.
|
|
480
|
+
*
|
|
481
|
+
* @param handler The handler to set.
|
|
482
|
+
*/
|
|
483
|
+
on(name, handler) {
|
|
484
|
+
this[EVENT_NAME_TO_HANDLER_NAME[name]] = handler;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Unset an event handler.
|
|
488
|
+
*
|
|
489
|
+
* @parma name The event to stop listening to.
|
|
490
|
+
*/
|
|
491
|
+
off(name) {
|
|
492
|
+
this[EVENT_NAME_TO_HANDLER_NAME[name]] = void 0;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Make an error object. The error object will have a message that contains
|
|
496
|
+
* the ``fileName`` option passed at the creation of the parser. If position
|
|
497
|
+
* tracking was turned on, it will also have line and column number
|
|
498
|
+
* information.
|
|
499
|
+
*
|
|
500
|
+
* @param message The message describing the error to report.
|
|
501
|
+
*
|
|
502
|
+
* @returns An error object with a properly formatted message.
|
|
503
|
+
*/
|
|
504
|
+
makeError(message) {
|
|
505
|
+
var _a;
|
|
506
|
+
let msg = (_a = this.fileName) !== null && _a !== void 0 ? _a : "";
|
|
507
|
+
if (this.trackPosition) {
|
|
508
|
+
if (msg.length > 0) {
|
|
509
|
+
msg += ":";
|
|
510
|
+
}
|
|
511
|
+
msg += `${this.line}:${this.column}`;
|
|
512
|
+
}
|
|
513
|
+
if (msg.length > 0) {
|
|
514
|
+
msg += ": ";
|
|
515
|
+
}
|
|
516
|
+
return new Error(msg + message);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Report a parsing error. This method is made public so that client code may
|
|
520
|
+
* check for issues that are outside the scope of this project and can report
|
|
521
|
+
* errors.
|
|
522
|
+
*
|
|
523
|
+
* @param message The error to report.
|
|
524
|
+
*
|
|
525
|
+
* @returns this
|
|
526
|
+
*/
|
|
527
|
+
fail(message) {
|
|
528
|
+
const err2 = this.makeError(message);
|
|
529
|
+
const handler = this.errorHandler;
|
|
530
|
+
if (handler === void 0) {
|
|
531
|
+
throw err2;
|
|
532
|
+
} else {
|
|
533
|
+
handler(err2);
|
|
534
|
+
}
|
|
535
|
+
return this;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Write a XML data to the parser.
|
|
539
|
+
*
|
|
540
|
+
* @param chunk The XML data to write.
|
|
541
|
+
*
|
|
542
|
+
* @returns this
|
|
543
|
+
*/
|
|
544
|
+
// We do need object for the type here. Yes, it often causes problems
|
|
545
|
+
// but not in this case.
|
|
546
|
+
write(chunk) {
|
|
547
|
+
if (this.closed) {
|
|
548
|
+
return this.fail("cannot write after close; assign an onready handler.");
|
|
549
|
+
}
|
|
550
|
+
let end = false;
|
|
551
|
+
if (chunk === null) {
|
|
552
|
+
end = true;
|
|
553
|
+
chunk = "";
|
|
554
|
+
} else if (typeof chunk === "object") {
|
|
555
|
+
chunk = chunk.toString();
|
|
556
|
+
}
|
|
557
|
+
if (this.carriedFromPrevious !== void 0) {
|
|
558
|
+
chunk = `${this.carriedFromPrevious}${chunk}`;
|
|
559
|
+
this.carriedFromPrevious = void 0;
|
|
560
|
+
}
|
|
561
|
+
let limit = chunk.length;
|
|
562
|
+
const lastCode = chunk.charCodeAt(limit - 1);
|
|
563
|
+
if (!end && // A trailing CR or surrogate must be carried over to the next
|
|
564
|
+
// chunk.
|
|
565
|
+
(lastCode === CR || lastCode >= 55296 && lastCode <= 56319)) {
|
|
566
|
+
this.carriedFromPrevious = chunk[limit - 1];
|
|
567
|
+
limit--;
|
|
568
|
+
chunk = chunk.slice(0, limit);
|
|
569
|
+
}
|
|
570
|
+
const { stateTable } = this;
|
|
571
|
+
this.chunk = chunk;
|
|
572
|
+
this.i = 0;
|
|
573
|
+
while (this.i < limit) {
|
|
574
|
+
stateTable[this.state].call(this);
|
|
575
|
+
}
|
|
576
|
+
this.chunkPosition += limit;
|
|
577
|
+
return end ? this.end() : this;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Close the current stream. Perform final well-formedness checks and reset
|
|
581
|
+
* the parser tstate.
|
|
582
|
+
*
|
|
583
|
+
* @returns this
|
|
584
|
+
*/
|
|
585
|
+
close() {
|
|
586
|
+
return this.write(null);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Get a single code point out of the current chunk. This updates the current
|
|
590
|
+
* position if we do position tracking.
|
|
591
|
+
*
|
|
592
|
+
* This is the algorithm to use for XML 1.0.
|
|
593
|
+
*
|
|
594
|
+
* @returns The character read.
|
|
595
|
+
*/
|
|
596
|
+
getCode10() {
|
|
597
|
+
const { chunk, i } = this;
|
|
598
|
+
this.prevI = i;
|
|
599
|
+
this.i = i + 1;
|
|
600
|
+
if (i >= chunk.length) {
|
|
601
|
+
return EOC;
|
|
602
|
+
}
|
|
603
|
+
const code = chunk.charCodeAt(i);
|
|
604
|
+
this.column++;
|
|
605
|
+
if (code < 55296) {
|
|
606
|
+
if (code >= SPACE || code === TAB) {
|
|
607
|
+
return code;
|
|
608
|
+
}
|
|
609
|
+
switch (code) {
|
|
610
|
+
case NL:
|
|
611
|
+
this.line++;
|
|
612
|
+
this.column = 0;
|
|
613
|
+
this.positionAtNewLine = this.position;
|
|
614
|
+
return NL;
|
|
615
|
+
case CR:
|
|
616
|
+
if (chunk.charCodeAt(i + 1) === NL) {
|
|
617
|
+
this.i = i + 2;
|
|
618
|
+
}
|
|
619
|
+
this.line++;
|
|
620
|
+
this.column = 0;
|
|
621
|
+
this.positionAtNewLine = this.position;
|
|
622
|
+
return NL_LIKE;
|
|
623
|
+
default:
|
|
624
|
+
this.fail("disallowed character.");
|
|
625
|
+
return code;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (code > 56319) {
|
|
629
|
+
if (!(code >= 57344 && code <= 65533)) {
|
|
630
|
+
this.fail("disallowed character.");
|
|
631
|
+
}
|
|
632
|
+
return code;
|
|
633
|
+
}
|
|
634
|
+
const final = 65536 + (code - 55296) * 1024 + (chunk.charCodeAt(i + 1) - 56320);
|
|
635
|
+
this.i = i + 2;
|
|
636
|
+
if (final > 1114111) {
|
|
637
|
+
this.fail("disallowed character.");
|
|
638
|
+
}
|
|
639
|
+
return final;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Get a single code point out of the current chunk. This updates the current
|
|
643
|
+
* position if we do position tracking.
|
|
644
|
+
*
|
|
645
|
+
* This is the algorithm to use for XML 1.1.
|
|
646
|
+
*
|
|
647
|
+
* @returns {number} The character read.
|
|
648
|
+
*/
|
|
649
|
+
getCode11() {
|
|
650
|
+
const { chunk, i } = this;
|
|
651
|
+
this.prevI = i;
|
|
652
|
+
this.i = i + 1;
|
|
653
|
+
if (i >= chunk.length) {
|
|
654
|
+
return EOC;
|
|
655
|
+
}
|
|
656
|
+
const code = chunk.charCodeAt(i);
|
|
657
|
+
this.column++;
|
|
658
|
+
if (code < 55296) {
|
|
659
|
+
if (code > 31 && code < 127 || code > 159 && code !== LS || code === TAB) {
|
|
660
|
+
return code;
|
|
661
|
+
}
|
|
662
|
+
switch (code) {
|
|
663
|
+
case NL:
|
|
664
|
+
this.line++;
|
|
665
|
+
this.column = 0;
|
|
666
|
+
this.positionAtNewLine = this.position;
|
|
667
|
+
return NL;
|
|
668
|
+
case CR: {
|
|
669
|
+
const next = chunk.charCodeAt(i + 1);
|
|
670
|
+
if (next === NL || next === NEL) {
|
|
671
|
+
this.i = i + 2;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
/* yes, fall through */
|
|
675
|
+
case NEL:
|
|
676
|
+
// 0x85
|
|
677
|
+
case LS:
|
|
678
|
+
this.line++;
|
|
679
|
+
this.column = 0;
|
|
680
|
+
this.positionAtNewLine = this.position;
|
|
681
|
+
return NL_LIKE;
|
|
682
|
+
default:
|
|
683
|
+
this.fail("disallowed character.");
|
|
684
|
+
return code;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (code > 56319) {
|
|
688
|
+
if (!(code >= 57344 && code <= 65533)) {
|
|
689
|
+
this.fail("disallowed character.");
|
|
690
|
+
}
|
|
691
|
+
return code;
|
|
692
|
+
}
|
|
693
|
+
const final = 65536 + (code - 55296) * 1024 + (chunk.charCodeAt(i + 1) - 56320);
|
|
694
|
+
this.i = i + 2;
|
|
695
|
+
if (final > 1114111) {
|
|
696
|
+
this.fail("disallowed character.");
|
|
697
|
+
}
|
|
698
|
+
return final;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Like ``getCode`` but with the return value normalized so that ``NL`` is
|
|
702
|
+
* returned for ``NL_LIKE``.
|
|
703
|
+
*/
|
|
704
|
+
getCodeNorm() {
|
|
705
|
+
const c = this.getCode();
|
|
706
|
+
return c === NL_LIKE ? NL : c;
|
|
707
|
+
}
|
|
708
|
+
unget() {
|
|
709
|
+
this.i = this.prevI;
|
|
710
|
+
this.column--;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Capture characters into a buffer until encountering one of a set of
|
|
714
|
+
* characters.
|
|
715
|
+
*
|
|
716
|
+
* @param chars An array of codepoints. Encountering a character in the array
|
|
717
|
+
* ends the capture. (``chars`` may safely contain ``NL``.)
|
|
718
|
+
*
|
|
719
|
+
* @return The character code that made the capture end, or ``EOC`` if we hit
|
|
720
|
+
* the end of the chunk. The return value cannot be NL_LIKE: NL is returned
|
|
721
|
+
* instead.
|
|
722
|
+
*/
|
|
723
|
+
captureTo(chars) {
|
|
724
|
+
let { i: start } = this;
|
|
725
|
+
const { chunk } = this;
|
|
726
|
+
while (true) {
|
|
727
|
+
const c = this.getCode();
|
|
728
|
+
const isNLLike = c === NL_LIKE;
|
|
729
|
+
const final = isNLLike ? NL : c;
|
|
730
|
+
if (final === EOC || chars.includes(final)) {
|
|
731
|
+
this.text += chunk.slice(start, this.prevI);
|
|
732
|
+
return final;
|
|
733
|
+
}
|
|
734
|
+
if (isNLLike) {
|
|
735
|
+
this.text += `${chunk.slice(start, this.prevI)}
|
|
736
|
+
`;
|
|
737
|
+
start = this.i;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Capture characters into a buffer until encountering a character.
|
|
743
|
+
*
|
|
744
|
+
* @param char The codepoint that ends the capture. **NOTE ``char`` MAY NOT
|
|
745
|
+
* CONTAIN ``NL``.** Passing ``NL`` will result in buggy behavior.
|
|
746
|
+
*
|
|
747
|
+
* @return ``true`` if we ran into the character. Otherwise, we ran into the
|
|
748
|
+
* end of the current chunk.
|
|
749
|
+
*/
|
|
750
|
+
captureToChar(char) {
|
|
751
|
+
let { i: start } = this;
|
|
752
|
+
const { chunk } = this;
|
|
753
|
+
while (true) {
|
|
754
|
+
let c = this.getCode();
|
|
755
|
+
switch (c) {
|
|
756
|
+
case NL_LIKE:
|
|
757
|
+
this.text += `${chunk.slice(start, this.prevI)}
|
|
758
|
+
`;
|
|
759
|
+
start = this.i;
|
|
760
|
+
c = NL;
|
|
761
|
+
break;
|
|
762
|
+
case EOC:
|
|
763
|
+
this.text += chunk.slice(start);
|
|
764
|
+
return false;
|
|
765
|
+
default:
|
|
766
|
+
}
|
|
767
|
+
if (c === char) {
|
|
768
|
+
this.text += chunk.slice(start, this.prevI);
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Capture characters that satisfy ``isNameChar`` into the ``name`` field of
|
|
775
|
+
* this parser.
|
|
776
|
+
*
|
|
777
|
+
* @return The character code that made the test fail, or ``EOC`` if we hit
|
|
778
|
+
* the end of the chunk. The return value cannot be NL_LIKE: NL is returned
|
|
779
|
+
* instead.
|
|
780
|
+
*/
|
|
781
|
+
captureNameChars() {
|
|
782
|
+
const { chunk, i: start } = this;
|
|
783
|
+
while (true) {
|
|
784
|
+
const c = this.getCode();
|
|
785
|
+
if (c === EOC) {
|
|
786
|
+
this.name += chunk.slice(start);
|
|
787
|
+
return EOC;
|
|
788
|
+
}
|
|
789
|
+
if (!isNameChar(c)) {
|
|
790
|
+
this.name += chunk.slice(start, this.prevI);
|
|
791
|
+
return c === NL_LIKE ? NL : c;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Skip white spaces.
|
|
797
|
+
*
|
|
798
|
+
* @return The character that ended the skip, or ``EOC`` if we hit
|
|
799
|
+
* the end of the chunk. The return value cannot be NL_LIKE: NL is returned
|
|
800
|
+
* instead.
|
|
801
|
+
*/
|
|
802
|
+
skipSpaces() {
|
|
803
|
+
while (true) {
|
|
804
|
+
const c = this.getCodeNorm();
|
|
805
|
+
if (c === EOC || !isS(c)) {
|
|
806
|
+
return c;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
setXMLVersion(version) {
|
|
811
|
+
this.currentXMLVersion = version;
|
|
812
|
+
if (version === "1.0") {
|
|
813
|
+
this.isChar = isChar10;
|
|
814
|
+
this.getCode = this.getCode10;
|
|
815
|
+
} else {
|
|
816
|
+
this.isChar = isChar11;
|
|
817
|
+
this.getCode = this.getCode11;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
// STATE ENGINE METHODS
|
|
821
|
+
// This needs to be a state separate from S_BEGIN_WHITESPACE because we want
|
|
822
|
+
// to be sure never to come back to this state later.
|
|
823
|
+
sBegin() {
|
|
824
|
+
if (this.chunk.charCodeAt(0) === 65279) {
|
|
825
|
+
this.i++;
|
|
826
|
+
this.column++;
|
|
827
|
+
}
|
|
828
|
+
this.state = S_BEGIN_WHITESPACE;
|
|
829
|
+
}
|
|
830
|
+
sBeginWhitespace() {
|
|
831
|
+
const iBefore = this.i;
|
|
832
|
+
const c = this.skipSpaces();
|
|
833
|
+
if (this.prevI !== iBefore) {
|
|
834
|
+
this.xmlDeclPossible = false;
|
|
835
|
+
}
|
|
836
|
+
switch (c) {
|
|
837
|
+
case LESS:
|
|
838
|
+
this.state = S_OPEN_WAKA;
|
|
839
|
+
if (this.text.length !== 0) {
|
|
840
|
+
throw new Error("no-empty text at start");
|
|
841
|
+
}
|
|
842
|
+
break;
|
|
843
|
+
case EOC:
|
|
844
|
+
break;
|
|
845
|
+
default:
|
|
846
|
+
this.unget();
|
|
847
|
+
this.state = S_TEXT;
|
|
848
|
+
this.xmlDeclPossible = false;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
sDoctype() {
|
|
852
|
+
var _a;
|
|
853
|
+
const c = this.captureTo(DOCTYPE_TERMINATOR);
|
|
854
|
+
switch (c) {
|
|
855
|
+
case GREATER: {
|
|
856
|
+
(_a = this.doctypeHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.text);
|
|
857
|
+
this.text = "";
|
|
858
|
+
this.state = S_TEXT;
|
|
859
|
+
this.doctype = true;
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
case EOC:
|
|
863
|
+
break;
|
|
864
|
+
default:
|
|
865
|
+
this.text += String.fromCodePoint(c);
|
|
866
|
+
if (c === OPEN_BRACKET) {
|
|
867
|
+
this.state = S_DTD;
|
|
868
|
+
} else if (isQuote(c)) {
|
|
869
|
+
this.state = S_DOCTYPE_QUOTE;
|
|
870
|
+
this.q = c;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
sDoctypeQuote() {
|
|
875
|
+
const q = this.q;
|
|
876
|
+
if (this.captureToChar(q)) {
|
|
877
|
+
this.text += String.fromCodePoint(q);
|
|
878
|
+
this.q = null;
|
|
879
|
+
this.state = S_DOCTYPE;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
sDTD() {
|
|
883
|
+
const c = this.captureTo(DTD_TERMINATOR);
|
|
884
|
+
if (c === EOC) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
this.text += String.fromCodePoint(c);
|
|
888
|
+
if (c === CLOSE_BRACKET) {
|
|
889
|
+
this.state = S_DOCTYPE;
|
|
890
|
+
} else if (c === LESS) {
|
|
891
|
+
this.state = S_DTD_OPEN_WAKA;
|
|
892
|
+
} else if (isQuote(c)) {
|
|
893
|
+
this.state = S_DTD_QUOTED;
|
|
894
|
+
this.q = c;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
sDTDQuoted() {
|
|
898
|
+
const q = this.q;
|
|
899
|
+
if (this.captureToChar(q)) {
|
|
900
|
+
this.text += String.fromCodePoint(q);
|
|
901
|
+
this.state = S_DTD;
|
|
902
|
+
this.q = null;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
sDTDOpenWaka() {
|
|
906
|
+
const c = this.getCodeNorm();
|
|
907
|
+
this.text += String.fromCodePoint(c);
|
|
908
|
+
switch (c) {
|
|
909
|
+
case BANG:
|
|
910
|
+
this.state = S_DTD_OPEN_WAKA_BANG;
|
|
911
|
+
this.openWakaBang = "";
|
|
912
|
+
break;
|
|
913
|
+
case QUESTION:
|
|
914
|
+
this.state = S_DTD_PI;
|
|
915
|
+
break;
|
|
916
|
+
default:
|
|
917
|
+
this.state = S_DTD;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
sDTDOpenWakaBang() {
|
|
921
|
+
const char = String.fromCodePoint(this.getCodeNorm());
|
|
922
|
+
const owb = this.openWakaBang += char;
|
|
923
|
+
this.text += char;
|
|
924
|
+
if (owb !== "-") {
|
|
925
|
+
this.state = owb === "--" ? S_DTD_COMMENT : S_DTD;
|
|
926
|
+
this.openWakaBang = "";
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
sDTDComment() {
|
|
930
|
+
if (this.captureToChar(MINUS)) {
|
|
931
|
+
this.text += "-";
|
|
932
|
+
this.state = S_DTD_COMMENT_ENDING;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
sDTDCommentEnding() {
|
|
936
|
+
const c = this.getCodeNorm();
|
|
937
|
+
this.text += String.fromCodePoint(c);
|
|
938
|
+
this.state = c === MINUS ? S_DTD_COMMENT_ENDED : S_DTD_COMMENT;
|
|
939
|
+
}
|
|
940
|
+
sDTDCommentEnded() {
|
|
941
|
+
const c = this.getCodeNorm();
|
|
942
|
+
this.text += String.fromCodePoint(c);
|
|
943
|
+
if (c === GREATER) {
|
|
944
|
+
this.state = S_DTD;
|
|
945
|
+
} else {
|
|
946
|
+
this.fail("malformed comment.");
|
|
947
|
+
this.state = S_DTD_COMMENT;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
sDTDPI() {
|
|
951
|
+
if (this.captureToChar(QUESTION)) {
|
|
952
|
+
this.text += "?";
|
|
953
|
+
this.state = S_DTD_PI_ENDING;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
sDTDPIEnding() {
|
|
957
|
+
const c = this.getCodeNorm();
|
|
958
|
+
this.text += String.fromCodePoint(c);
|
|
959
|
+
if (c === GREATER) {
|
|
960
|
+
this.state = S_DTD;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
sText() {
|
|
964
|
+
if (this.tags.length !== 0) {
|
|
965
|
+
this.handleTextInRoot();
|
|
966
|
+
} else {
|
|
967
|
+
this.handleTextOutsideRoot();
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
sEntity() {
|
|
971
|
+
let { i: start } = this;
|
|
972
|
+
const { chunk } = this;
|
|
973
|
+
loop:
|
|
974
|
+
while (true) {
|
|
975
|
+
switch (this.getCode()) {
|
|
976
|
+
case NL_LIKE:
|
|
977
|
+
this.entity += `${chunk.slice(start, this.prevI)}
|
|
978
|
+
`;
|
|
979
|
+
start = this.i;
|
|
980
|
+
break;
|
|
981
|
+
case SEMICOLON: {
|
|
982
|
+
const { entityReturnState } = this;
|
|
983
|
+
const entity = this.entity + chunk.slice(start, this.prevI);
|
|
984
|
+
this.state = entityReturnState;
|
|
985
|
+
let parsed;
|
|
986
|
+
if (entity === "") {
|
|
987
|
+
this.fail("empty entity name.");
|
|
988
|
+
parsed = "&;";
|
|
989
|
+
} else {
|
|
990
|
+
parsed = this.parseEntity(entity);
|
|
991
|
+
this.entity = "";
|
|
992
|
+
}
|
|
993
|
+
if (entityReturnState !== S_TEXT || this.textHandler !== void 0) {
|
|
994
|
+
this.text += parsed;
|
|
995
|
+
}
|
|
996
|
+
break loop;
|
|
997
|
+
}
|
|
998
|
+
case EOC:
|
|
999
|
+
this.entity += chunk.slice(start);
|
|
1000
|
+
break loop;
|
|
1001
|
+
default:
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
sOpenWaka() {
|
|
1006
|
+
const c = this.getCode();
|
|
1007
|
+
if (isNameStartChar(c)) {
|
|
1008
|
+
this.state = S_OPEN_TAG;
|
|
1009
|
+
this.unget();
|
|
1010
|
+
this.xmlDeclPossible = false;
|
|
1011
|
+
} else {
|
|
1012
|
+
switch (c) {
|
|
1013
|
+
case FORWARD_SLASH:
|
|
1014
|
+
this.state = S_CLOSE_TAG;
|
|
1015
|
+
this.xmlDeclPossible = false;
|
|
1016
|
+
break;
|
|
1017
|
+
case BANG:
|
|
1018
|
+
this.state = S_OPEN_WAKA_BANG;
|
|
1019
|
+
this.openWakaBang = "";
|
|
1020
|
+
this.xmlDeclPossible = false;
|
|
1021
|
+
break;
|
|
1022
|
+
case QUESTION:
|
|
1023
|
+
this.state = S_PI_FIRST_CHAR;
|
|
1024
|
+
break;
|
|
1025
|
+
default:
|
|
1026
|
+
this.fail("disallowed character in tag name");
|
|
1027
|
+
this.state = S_TEXT;
|
|
1028
|
+
this.xmlDeclPossible = false;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
sOpenWakaBang() {
|
|
1033
|
+
this.openWakaBang += String.fromCodePoint(this.getCodeNorm());
|
|
1034
|
+
switch (this.openWakaBang) {
|
|
1035
|
+
case "[CDATA[":
|
|
1036
|
+
if (!this.sawRoot && !this.reportedTextBeforeRoot) {
|
|
1037
|
+
this.fail("text data outside of root node.");
|
|
1038
|
+
this.reportedTextBeforeRoot = true;
|
|
1039
|
+
}
|
|
1040
|
+
if (this.closedRoot && !this.reportedTextAfterRoot) {
|
|
1041
|
+
this.fail("text data outside of root node.");
|
|
1042
|
+
this.reportedTextAfterRoot = true;
|
|
1043
|
+
}
|
|
1044
|
+
this.state = S_CDATA;
|
|
1045
|
+
this.openWakaBang = "";
|
|
1046
|
+
break;
|
|
1047
|
+
case "--":
|
|
1048
|
+
this.state = S_COMMENT;
|
|
1049
|
+
this.openWakaBang = "";
|
|
1050
|
+
break;
|
|
1051
|
+
case "DOCTYPE":
|
|
1052
|
+
this.state = S_DOCTYPE;
|
|
1053
|
+
if (this.doctype || this.sawRoot) {
|
|
1054
|
+
this.fail("inappropriately located doctype declaration.");
|
|
1055
|
+
}
|
|
1056
|
+
this.openWakaBang = "";
|
|
1057
|
+
break;
|
|
1058
|
+
default:
|
|
1059
|
+
if (this.openWakaBang.length >= 7) {
|
|
1060
|
+
this.fail("incorrect syntax.");
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
sComment() {
|
|
1065
|
+
if (this.captureToChar(MINUS)) {
|
|
1066
|
+
this.state = S_COMMENT_ENDING;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
sCommentEnding() {
|
|
1070
|
+
var _a;
|
|
1071
|
+
const c = this.getCodeNorm();
|
|
1072
|
+
if (c === MINUS) {
|
|
1073
|
+
this.state = S_COMMENT_ENDED;
|
|
1074
|
+
(_a = this.commentHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.text);
|
|
1075
|
+
this.text = "";
|
|
1076
|
+
} else {
|
|
1077
|
+
this.text += `-${String.fromCodePoint(c)}`;
|
|
1078
|
+
this.state = S_COMMENT;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
sCommentEnded() {
|
|
1082
|
+
const c = this.getCodeNorm();
|
|
1083
|
+
if (c !== GREATER) {
|
|
1084
|
+
this.fail("malformed comment.");
|
|
1085
|
+
this.text += `--${String.fromCodePoint(c)}`;
|
|
1086
|
+
this.state = S_COMMENT;
|
|
1087
|
+
} else {
|
|
1088
|
+
this.state = S_TEXT;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
sCData() {
|
|
1092
|
+
if (this.captureToChar(CLOSE_BRACKET)) {
|
|
1093
|
+
this.state = S_CDATA_ENDING;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
sCDataEnding() {
|
|
1097
|
+
const c = this.getCodeNorm();
|
|
1098
|
+
if (c === CLOSE_BRACKET) {
|
|
1099
|
+
this.state = S_CDATA_ENDING_2;
|
|
1100
|
+
} else {
|
|
1101
|
+
this.text += `]${String.fromCodePoint(c)}`;
|
|
1102
|
+
this.state = S_CDATA;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
sCDataEnding2() {
|
|
1106
|
+
var _a;
|
|
1107
|
+
const c = this.getCodeNorm();
|
|
1108
|
+
switch (c) {
|
|
1109
|
+
case GREATER: {
|
|
1110
|
+
(_a = this.cdataHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.text);
|
|
1111
|
+
this.text = "";
|
|
1112
|
+
this.state = S_TEXT;
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
case CLOSE_BRACKET:
|
|
1116
|
+
this.text += "]";
|
|
1117
|
+
break;
|
|
1118
|
+
default:
|
|
1119
|
+
this.text += `]]${String.fromCodePoint(c)}`;
|
|
1120
|
+
this.state = S_CDATA;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
// We need this separate state to check the first character fo the pi target
|
|
1124
|
+
// with this.nameStartCheck which allows less characters than this.nameCheck.
|
|
1125
|
+
sPIFirstChar() {
|
|
1126
|
+
const c = this.getCodeNorm();
|
|
1127
|
+
if (this.nameStartCheck(c)) {
|
|
1128
|
+
this.piTarget += String.fromCodePoint(c);
|
|
1129
|
+
this.state = S_PI_REST;
|
|
1130
|
+
} else if (c === QUESTION || isS(c)) {
|
|
1131
|
+
this.fail("processing instruction without a target.");
|
|
1132
|
+
this.state = c === QUESTION ? S_PI_ENDING : S_PI_BODY;
|
|
1133
|
+
} else {
|
|
1134
|
+
this.fail("disallowed character in processing instruction name.");
|
|
1135
|
+
this.piTarget += String.fromCodePoint(c);
|
|
1136
|
+
this.state = S_PI_REST;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
sPIRest() {
|
|
1140
|
+
const { chunk, i: start } = this;
|
|
1141
|
+
while (true) {
|
|
1142
|
+
const c = this.getCodeNorm();
|
|
1143
|
+
if (c === EOC) {
|
|
1144
|
+
this.piTarget += chunk.slice(start);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
if (!this.nameCheck(c)) {
|
|
1148
|
+
this.piTarget += chunk.slice(start, this.prevI);
|
|
1149
|
+
const isQuestion = c === QUESTION;
|
|
1150
|
+
if (isQuestion || isS(c)) {
|
|
1151
|
+
if (this.piTarget === "xml") {
|
|
1152
|
+
if (!this.xmlDeclPossible) {
|
|
1153
|
+
this.fail("an XML declaration must be at the start of the document.");
|
|
1154
|
+
}
|
|
1155
|
+
this.state = isQuestion ? S_XML_DECL_ENDING : S_XML_DECL_NAME_START;
|
|
1156
|
+
} else {
|
|
1157
|
+
this.state = isQuestion ? S_PI_ENDING : S_PI_BODY;
|
|
1158
|
+
}
|
|
1159
|
+
} else {
|
|
1160
|
+
this.fail("disallowed character in processing instruction name.");
|
|
1161
|
+
this.piTarget += String.fromCodePoint(c);
|
|
1162
|
+
}
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
sPIBody() {
|
|
1168
|
+
if (this.text.length === 0) {
|
|
1169
|
+
const c = this.getCodeNorm();
|
|
1170
|
+
if (c === QUESTION) {
|
|
1171
|
+
this.state = S_PI_ENDING;
|
|
1172
|
+
} else if (!isS(c)) {
|
|
1173
|
+
this.text = String.fromCodePoint(c);
|
|
1174
|
+
}
|
|
1175
|
+
} else if (this.captureToChar(QUESTION)) {
|
|
1176
|
+
this.state = S_PI_ENDING;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
sPIEnding() {
|
|
1180
|
+
var _a;
|
|
1181
|
+
const c = this.getCodeNorm();
|
|
1182
|
+
if (c === GREATER) {
|
|
1183
|
+
const { piTarget } = this;
|
|
1184
|
+
if (piTarget.toLowerCase() === "xml") {
|
|
1185
|
+
this.fail("the XML declaration must appear at the start of the document.");
|
|
1186
|
+
}
|
|
1187
|
+
(_a = this.piHandler) === null || _a === void 0 ? void 0 : _a.call(this, {
|
|
1188
|
+
target: piTarget,
|
|
1189
|
+
body: this.text
|
|
1190
|
+
});
|
|
1191
|
+
this.piTarget = this.text = "";
|
|
1192
|
+
this.state = S_TEXT;
|
|
1193
|
+
} else if (c === QUESTION) {
|
|
1194
|
+
this.text += "?";
|
|
1195
|
+
} else {
|
|
1196
|
+
this.text += `?${String.fromCodePoint(c)}`;
|
|
1197
|
+
this.state = S_PI_BODY;
|
|
1198
|
+
}
|
|
1199
|
+
this.xmlDeclPossible = false;
|
|
1200
|
+
}
|
|
1201
|
+
sXMLDeclNameStart() {
|
|
1202
|
+
const c = this.skipSpaces();
|
|
1203
|
+
if (c === QUESTION) {
|
|
1204
|
+
this.state = S_XML_DECL_ENDING;
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
if (c !== EOC) {
|
|
1208
|
+
this.state = S_XML_DECL_NAME;
|
|
1209
|
+
this.name = String.fromCodePoint(c);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
sXMLDeclName() {
|
|
1213
|
+
const c = this.captureTo(XML_DECL_NAME_TERMINATOR);
|
|
1214
|
+
if (c === QUESTION) {
|
|
1215
|
+
this.state = S_XML_DECL_ENDING;
|
|
1216
|
+
this.name += this.text;
|
|
1217
|
+
this.text = "";
|
|
1218
|
+
this.fail("XML declaration is incomplete.");
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
if (!(isS(c) || c === EQUAL)) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
this.name += this.text;
|
|
1225
|
+
this.text = "";
|
|
1226
|
+
if (!this.xmlDeclExpects.includes(this.name)) {
|
|
1227
|
+
switch (this.name.length) {
|
|
1228
|
+
case 0:
|
|
1229
|
+
this.fail("did not expect any more name/value pairs.");
|
|
1230
|
+
break;
|
|
1231
|
+
case 1:
|
|
1232
|
+
this.fail(`expected the name ${this.xmlDeclExpects[0]}.`);
|
|
1233
|
+
break;
|
|
1234
|
+
default:
|
|
1235
|
+
this.fail(`expected one of ${this.xmlDeclExpects.join(", ")}`);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
this.state = c === EQUAL ? S_XML_DECL_VALUE_START : S_XML_DECL_EQ;
|
|
1239
|
+
}
|
|
1240
|
+
sXMLDeclEq() {
|
|
1241
|
+
const c = this.getCodeNorm();
|
|
1242
|
+
if (c === QUESTION) {
|
|
1243
|
+
this.state = S_XML_DECL_ENDING;
|
|
1244
|
+
this.fail("XML declaration is incomplete.");
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (isS(c)) {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (c !== EQUAL) {
|
|
1251
|
+
this.fail("value required.");
|
|
1252
|
+
}
|
|
1253
|
+
this.state = S_XML_DECL_VALUE_START;
|
|
1254
|
+
}
|
|
1255
|
+
sXMLDeclValueStart() {
|
|
1256
|
+
const c = this.getCodeNorm();
|
|
1257
|
+
if (c === QUESTION) {
|
|
1258
|
+
this.state = S_XML_DECL_ENDING;
|
|
1259
|
+
this.fail("XML declaration is incomplete.");
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
if (isS(c)) {
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
if (!isQuote(c)) {
|
|
1266
|
+
this.fail("value must be quoted.");
|
|
1267
|
+
this.q = SPACE;
|
|
1268
|
+
} else {
|
|
1269
|
+
this.q = c;
|
|
1270
|
+
}
|
|
1271
|
+
this.state = S_XML_DECL_VALUE;
|
|
1272
|
+
}
|
|
1273
|
+
sXMLDeclValue() {
|
|
1274
|
+
const c = this.captureTo([this.q, QUESTION]);
|
|
1275
|
+
if (c === QUESTION) {
|
|
1276
|
+
this.state = S_XML_DECL_ENDING;
|
|
1277
|
+
this.text = "";
|
|
1278
|
+
this.fail("XML declaration is incomplete.");
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
if (c === EOC) {
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
const value = this.text;
|
|
1285
|
+
this.text = "";
|
|
1286
|
+
switch (this.name) {
|
|
1287
|
+
case "version": {
|
|
1288
|
+
this.xmlDeclExpects = ["encoding", "standalone"];
|
|
1289
|
+
const version = value;
|
|
1290
|
+
this.xmlDecl.version = version;
|
|
1291
|
+
if (!/^1\.[0-9]+$/.test(version)) {
|
|
1292
|
+
this.fail("version number must match /^1\\.[0-9]+$/.");
|
|
1293
|
+
} else if (!this.opt.forceXMLVersion) {
|
|
1294
|
+
this.setXMLVersion(version);
|
|
1295
|
+
}
|
|
1296
|
+
break;
|
|
1297
|
+
}
|
|
1298
|
+
case "encoding":
|
|
1299
|
+
if (!/^[A-Za-z][A-Za-z0-9._-]*$/.test(value)) {
|
|
1300
|
+
this.fail("encoding value must match /^[A-Za-z0-9][A-Za-z0-9._-]*$/.");
|
|
1301
|
+
}
|
|
1302
|
+
this.xmlDeclExpects = ["standalone"];
|
|
1303
|
+
this.xmlDecl.encoding = value;
|
|
1304
|
+
break;
|
|
1305
|
+
case "standalone":
|
|
1306
|
+
if (value !== "yes" && value !== "no") {
|
|
1307
|
+
this.fail('standalone value must match "yes" or "no".');
|
|
1308
|
+
}
|
|
1309
|
+
this.xmlDeclExpects = [];
|
|
1310
|
+
this.xmlDecl.standalone = value;
|
|
1311
|
+
break;
|
|
1312
|
+
default:
|
|
1313
|
+
}
|
|
1314
|
+
this.name = "";
|
|
1315
|
+
this.state = S_XML_DECL_SEPARATOR;
|
|
1316
|
+
}
|
|
1317
|
+
sXMLDeclSeparator() {
|
|
1318
|
+
const c = this.getCodeNorm();
|
|
1319
|
+
if (c === QUESTION) {
|
|
1320
|
+
this.state = S_XML_DECL_ENDING;
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
if (!isS(c)) {
|
|
1324
|
+
this.fail("whitespace required.");
|
|
1325
|
+
this.unget();
|
|
1326
|
+
}
|
|
1327
|
+
this.state = S_XML_DECL_NAME_START;
|
|
1328
|
+
}
|
|
1329
|
+
sXMLDeclEnding() {
|
|
1330
|
+
var _a;
|
|
1331
|
+
const c = this.getCodeNorm();
|
|
1332
|
+
if (c === GREATER) {
|
|
1333
|
+
if (this.piTarget !== "xml") {
|
|
1334
|
+
this.fail("processing instructions are not allowed before root.");
|
|
1335
|
+
} else if (this.name !== "version" && this.xmlDeclExpects.includes("version")) {
|
|
1336
|
+
this.fail("XML declaration must contain a version.");
|
|
1337
|
+
}
|
|
1338
|
+
(_a = this.xmldeclHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.xmlDecl);
|
|
1339
|
+
this.name = "";
|
|
1340
|
+
this.piTarget = this.text = "";
|
|
1341
|
+
this.state = S_TEXT;
|
|
1342
|
+
} else {
|
|
1343
|
+
this.fail("The character ? is disallowed anywhere in XML declarations.");
|
|
1344
|
+
}
|
|
1345
|
+
this.xmlDeclPossible = false;
|
|
1346
|
+
}
|
|
1347
|
+
sOpenTag() {
|
|
1348
|
+
var _a;
|
|
1349
|
+
const c = this.captureNameChars();
|
|
1350
|
+
if (c === EOC) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
const tag = this.tag = {
|
|
1354
|
+
name: this.name,
|
|
1355
|
+
attributes: /* @__PURE__ */ Object.create(null)
|
|
1356
|
+
};
|
|
1357
|
+
this.name = "";
|
|
1358
|
+
if (this.xmlnsOpt) {
|
|
1359
|
+
this.topNS = tag.ns = /* @__PURE__ */ Object.create(null);
|
|
1360
|
+
}
|
|
1361
|
+
(_a = this.openTagStartHandler) === null || _a === void 0 ? void 0 : _a.call(this, tag);
|
|
1362
|
+
this.sawRoot = true;
|
|
1363
|
+
if (!this.fragmentOpt && this.closedRoot) {
|
|
1364
|
+
this.fail("documents may contain only one root.");
|
|
1365
|
+
}
|
|
1366
|
+
switch (c) {
|
|
1367
|
+
case GREATER:
|
|
1368
|
+
this.openTag();
|
|
1369
|
+
break;
|
|
1370
|
+
case FORWARD_SLASH:
|
|
1371
|
+
this.state = S_OPEN_TAG_SLASH;
|
|
1372
|
+
break;
|
|
1373
|
+
default:
|
|
1374
|
+
if (!isS(c)) {
|
|
1375
|
+
this.fail("disallowed character in tag name.");
|
|
1376
|
+
}
|
|
1377
|
+
this.state = S_ATTRIB;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
sOpenTagSlash() {
|
|
1381
|
+
if (this.getCode() === GREATER) {
|
|
1382
|
+
this.openSelfClosingTag();
|
|
1383
|
+
} else {
|
|
1384
|
+
this.fail("forward-slash in opening tag not followed by >.");
|
|
1385
|
+
this.state = S_ATTRIB;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
sAttrib() {
|
|
1389
|
+
const c = this.skipSpaces();
|
|
1390
|
+
if (c === EOC) {
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
if (isNameStartChar(c)) {
|
|
1394
|
+
this.unget();
|
|
1395
|
+
this.state = S_ATTRIB_NAME;
|
|
1396
|
+
} else if (c === GREATER) {
|
|
1397
|
+
this.openTag();
|
|
1398
|
+
} else if (c === FORWARD_SLASH) {
|
|
1399
|
+
this.state = S_OPEN_TAG_SLASH;
|
|
1400
|
+
} else {
|
|
1401
|
+
this.fail("disallowed character in attribute name.");
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
sAttribName() {
|
|
1405
|
+
const c = this.captureNameChars();
|
|
1406
|
+
if (c === EQUAL) {
|
|
1407
|
+
this.state = S_ATTRIB_VALUE;
|
|
1408
|
+
} else if (isS(c)) {
|
|
1409
|
+
this.state = S_ATTRIB_NAME_SAW_WHITE;
|
|
1410
|
+
} else if (c === GREATER) {
|
|
1411
|
+
this.fail("attribute without value.");
|
|
1412
|
+
this.pushAttrib(this.name, this.name);
|
|
1413
|
+
this.name = this.text = "";
|
|
1414
|
+
this.openTag();
|
|
1415
|
+
} else if (c !== EOC) {
|
|
1416
|
+
this.fail("disallowed character in attribute name.");
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
sAttribNameSawWhite() {
|
|
1420
|
+
const c = this.skipSpaces();
|
|
1421
|
+
switch (c) {
|
|
1422
|
+
case EOC:
|
|
1423
|
+
return;
|
|
1424
|
+
case EQUAL:
|
|
1425
|
+
this.state = S_ATTRIB_VALUE;
|
|
1426
|
+
break;
|
|
1427
|
+
default:
|
|
1428
|
+
this.fail("attribute without value.");
|
|
1429
|
+
this.text = "";
|
|
1430
|
+
this.name = "";
|
|
1431
|
+
if (c === GREATER) {
|
|
1432
|
+
this.openTag();
|
|
1433
|
+
} else if (isNameStartChar(c)) {
|
|
1434
|
+
this.unget();
|
|
1435
|
+
this.state = S_ATTRIB_NAME;
|
|
1436
|
+
} else {
|
|
1437
|
+
this.fail("disallowed character in attribute name.");
|
|
1438
|
+
this.state = S_ATTRIB;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
sAttribValue() {
|
|
1443
|
+
const c = this.getCodeNorm();
|
|
1444
|
+
if (isQuote(c)) {
|
|
1445
|
+
this.q = c;
|
|
1446
|
+
this.state = S_ATTRIB_VALUE_QUOTED;
|
|
1447
|
+
} else if (!isS(c)) {
|
|
1448
|
+
this.fail("unquoted attribute value.");
|
|
1449
|
+
this.state = S_ATTRIB_VALUE_UNQUOTED;
|
|
1450
|
+
this.unget();
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
sAttribValueQuoted() {
|
|
1454
|
+
const { q, chunk } = this;
|
|
1455
|
+
let { i: start } = this;
|
|
1456
|
+
while (true) {
|
|
1457
|
+
switch (this.getCode()) {
|
|
1458
|
+
case q:
|
|
1459
|
+
this.pushAttrib(this.name, this.text + chunk.slice(start, this.prevI));
|
|
1460
|
+
this.name = this.text = "";
|
|
1461
|
+
this.q = null;
|
|
1462
|
+
this.state = S_ATTRIB_VALUE_CLOSED;
|
|
1463
|
+
return;
|
|
1464
|
+
case AMP:
|
|
1465
|
+
this.text += chunk.slice(start, this.prevI);
|
|
1466
|
+
this.state = S_ENTITY;
|
|
1467
|
+
this.entityReturnState = S_ATTRIB_VALUE_QUOTED;
|
|
1468
|
+
return;
|
|
1469
|
+
case NL:
|
|
1470
|
+
case NL_LIKE:
|
|
1471
|
+
case TAB:
|
|
1472
|
+
this.text += `${chunk.slice(start, this.prevI)} `;
|
|
1473
|
+
start = this.i;
|
|
1474
|
+
break;
|
|
1475
|
+
case LESS:
|
|
1476
|
+
this.text += chunk.slice(start, this.prevI);
|
|
1477
|
+
this.fail("disallowed character.");
|
|
1478
|
+
return;
|
|
1479
|
+
case EOC:
|
|
1480
|
+
this.text += chunk.slice(start);
|
|
1481
|
+
return;
|
|
1482
|
+
default:
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
sAttribValueClosed() {
|
|
1487
|
+
const c = this.getCodeNorm();
|
|
1488
|
+
if (isS(c)) {
|
|
1489
|
+
this.state = S_ATTRIB;
|
|
1490
|
+
} else if (c === GREATER) {
|
|
1491
|
+
this.openTag();
|
|
1492
|
+
} else if (c === FORWARD_SLASH) {
|
|
1493
|
+
this.state = S_OPEN_TAG_SLASH;
|
|
1494
|
+
} else if (isNameStartChar(c)) {
|
|
1495
|
+
this.fail("no whitespace between attributes.");
|
|
1496
|
+
this.unget();
|
|
1497
|
+
this.state = S_ATTRIB_NAME;
|
|
1498
|
+
} else {
|
|
1499
|
+
this.fail("disallowed character in attribute name.");
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
sAttribValueUnquoted() {
|
|
1503
|
+
const c = this.captureTo(ATTRIB_VALUE_UNQUOTED_TERMINATOR);
|
|
1504
|
+
switch (c) {
|
|
1505
|
+
case AMP:
|
|
1506
|
+
this.state = S_ENTITY;
|
|
1507
|
+
this.entityReturnState = S_ATTRIB_VALUE_UNQUOTED;
|
|
1508
|
+
break;
|
|
1509
|
+
case LESS:
|
|
1510
|
+
this.fail("disallowed character.");
|
|
1511
|
+
break;
|
|
1512
|
+
case EOC:
|
|
1513
|
+
break;
|
|
1514
|
+
default:
|
|
1515
|
+
if (this.text.includes("]]>")) {
|
|
1516
|
+
this.fail('the string "]]>" is disallowed in char data.');
|
|
1517
|
+
}
|
|
1518
|
+
this.pushAttrib(this.name, this.text);
|
|
1519
|
+
this.name = this.text = "";
|
|
1520
|
+
if (c === GREATER) {
|
|
1521
|
+
this.openTag();
|
|
1522
|
+
} else {
|
|
1523
|
+
this.state = S_ATTRIB;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
sCloseTag() {
|
|
1528
|
+
const c = this.captureNameChars();
|
|
1529
|
+
if (c === GREATER) {
|
|
1530
|
+
this.closeTag();
|
|
1531
|
+
} else if (isS(c)) {
|
|
1532
|
+
this.state = S_CLOSE_TAG_SAW_WHITE;
|
|
1533
|
+
} else if (c !== EOC) {
|
|
1534
|
+
this.fail("disallowed character in closing tag.");
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
sCloseTagSawWhite() {
|
|
1538
|
+
switch (this.skipSpaces()) {
|
|
1539
|
+
case GREATER:
|
|
1540
|
+
this.closeTag();
|
|
1541
|
+
break;
|
|
1542
|
+
case EOC:
|
|
1543
|
+
break;
|
|
1544
|
+
default:
|
|
1545
|
+
this.fail("disallowed character in closing tag.");
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
// END OF STATE ENGINE METHODS
|
|
1549
|
+
handleTextInRoot() {
|
|
1550
|
+
let { i: start, forbiddenState } = this;
|
|
1551
|
+
const { chunk, textHandler: handler } = this;
|
|
1552
|
+
scanLoop:
|
|
1553
|
+
while (true) {
|
|
1554
|
+
switch (this.getCode()) {
|
|
1555
|
+
case LESS: {
|
|
1556
|
+
this.state = S_OPEN_WAKA;
|
|
1557
|
+
if (handler !== void 0) {
|
|
1558
|
+
const { text } = this;
|
|
1559
|
+
const slice = chunk.slice(start, this.prevI);
|
|
1560
|
+
if (text.length !== 0) {
|
|
1561
|
+
handler(text + slice);
|
|
1562
|
+
this.text = "";
|
|
1563
|
+
} else if (slice.length !== 0) {
|
|
1564
|
+
handler(slice);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
forbiddenState = FORBIDDEN_START;
|
|
1568
|
+
break scanLoop;
|
|
1569
|
+
}
|
|
1570
|
+
case AMP:
|
|
1571
|
+
this.state = S_ENTITY;
|
|
1572
|
+
this.entityReturnState = S_TEXT;
|
|
1573
|
+
if (handler !== void 0) {
|
|
1574
|
+
this.text += chunk.slice(start, this.prevI);
|
|
1575
|
+
}
|
|
1576
|
+
forbiddenState = FORBIDDEN_START;
|
|
1577
|
+
break scanLoop;
|
|
1578
|
+
case CLOSE_BRACKET:
|
|
1579
|
+
switch (forbiddenState) {
|
|
1580
|
+
case FORBIDDEN_START:
|
|
1581
|
+
forbiddenState = FORBIDDEN_BRACKET;
|
|
1582
|
+
break;
|
|
1583
|
+
case FORBIDDEN_BRACKET:
|
|
1584
|
+
forbiddenState = FORBIDDEN_BRACKET_BRACKET;
|
|
1585
|
+
break;
|
|
1586
|
+
case FORBIDDEN_BRACKET_BRACKET:
|
|
1587
|
+
break;
|
|
1588
|
+
default:
|
|
1589
|
+
throw new Error("impossible state");
|
|
1590
|
+
}
|
|
1591
|
+
break;
|
|
1592
|
+
case GREATER:
|
|
1593
|
+
if (forbiddenState === FORBIDDEN_BRACKET_BRACKET) {
|
|
1594
|
+
this.fail('the string "]]>" is disallowed in char data.');
|
|
1595
|
+
}
|
|
1596
|
+
forbiddenState = FORBIDDEN_START;
|
|
1597
|
+
break;
|
|
1598
|
+
case NL_LIKE:
|
|
1599
|
+
if (handler !== void 0) {
|
|
1600
|
+
this.text += `${chunk.slice(start, this.prevI)}
|
|
1601
|
+
`;
|
|
1602
|
+
}
|
|
1603
|
+
start = this.i;
|
|
1604
|
+
forbiddenState = FORBIDDEN_START;
|
|
1605
|
+
break;
|
|
1606
|
+
case EOC:
|
|
1607
|
+
if (handler !== void 0) {
|
|
1608
|
+
this.text += chunk.slice(start);
|
|
1609
|
+
}
|
|
1610
|
+
break scanLoop;
|
|
1611
|
+
default:
|
|
1612
|
+
forbiddenState = FORBIDDEN_START;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
this.forbiddenState = forbiddenState;
|
|
1616
|
+
}
|
|
1617
|
+
handleTextOutsideRoot() {
|
|
1618
|
+
let { i: start } = this;
|
|
1619
|
+
const { chunk, textHandler: handler } = this;
|
|
1620
|
+
let nonSpace = false;
|
|
1621
|
+
outRootLoop:
|
|
1622
|
+
while (true) {
|
|
1623
|
+
const code = this.getCode();
|
|
1624
|
+
switch (code) {
|
|
1625
|
+
case LESS: {
|
|
1626
|
+
this.state = S_OPEN_WAKA;
|
|
1627
|
+
if (handler !== void 0) {
|
|
1628
|
+
const { text } = this;
|
|
1629
|
+
const slice = chunk.slice(start, this.prevI);
|
|
1630
|
+
if (text.length !== 0) {
|
|
1631
|
+
handler(text + slice);
|
|
1632
|
+
this.text = "";
|
|
1633
|
+
} else if (slice.length !== 0) {
|
|
1634
|
+
handler(slice);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
break outRootLoop;
|
|
1638
|
+
}
|
|
1639
|
+
case AMP:
|
|
1640
|
+
this.state = S_ENTITY;
|
|
1641
|
+
this.entityReturnState = S_TEXT;
|
|
1642
|
+
if (handler !== void 0) {
|
|
1643
|
+
this.text += chunk.slice(start, this.prevI);
|
|
1644
|
+
}
|
|
1645
|
+
nonSpace = true;
|
|
1646
|
+
break outRootLoop;
|
|
1647
|
+
case NL_LIKE:
|
|
1648
|
+
if (handler !== void 0) {
|
|
1649
|
+
this.text += `${chunk.slice(start, this.prevI)}
|
|
1650
|
+
`;
|
|
1651
|
+
}
|
|
1652
|
+
start = this.i;
|
|
1653
|
+
break;
|
|
1654
|
+
case EOC:
|
|
1655
|
+
if (handler !== void 0) {
|
|
1656
|
+
this.text += chunk.slice(start);
|
|
1657
|
+
}
|
|
1658
|
+
break outRootLoop;
|
|
1659
|
+
default:
|
|
1660
|
+
if (!isS(code)) {
|
|
1661
|
+
nonSpace = true;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
if (!nonSpace) {
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
if (!this.sawRoot && !this.reportedTextBeforeRoot) {
|
|
1669
|
+
this.fail("text data outside of root node.");
|
|
1670
|
+
this.reportedTextBeforeRoot = true;
|
|
1671
|
+
}
|
|
1672
|
+
if (this.closedRoot && !this.reportedTextAfterRoot) {
|
|
1673
|
+
this.fail("text data outside of root node.");
|
|
1674
|
+
this.reportedTextAfterRoot = true;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
pushAttribNS(name, value) {
|
|
1678
|
+
var _a;
|
|
1679
|
+
const { prefix, local } = this.qname(name);
|
|
1680
|
+
const attr = { name, prefix, local, value };
|
|
1681
|
+
this.attribList.push(attr);
|
|
1682
|
+
(_a = this.attributeHandler) === null || _a === void 0 ? void 0 : _a.call(this, attr);
|
|
1683
|
+
if (prefix === "xmlns") {
|
|
1684
|
+
const trimmed = value.trim();
|
|
1685
|
+
if (this.currentXMLVersion === "1.0" && trimmed === "") {
|
|
1686
|
+
this.fail("invalid attempt to undefine prefix in XML 1.0");
|
|
1687
|
+
}
|
|
1688
|
+
this.topNS[local] = trimmed;
|
|
1689
|
+
nsPairCheck(this, local, trimmed);
|
|
1690
|
+
} else if (name === "xmlns") {
|
|
1691
|
+
const trimmed = value.trim();
|
|
1692
|
+
this.topNS[""] = trimmed;
|
|
1693
|
+
nsPairCheck(this, "", trimmed);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
pushAttribPlain(name, value) {
|
|
1697
|
+
var _a;
|
|
1698
|
+
const attr = { name, value };
|
|
1699
|
+
this.attribList.push(attr);
|
|
1700
|
+
(_a = this.attributeHandler) === null || _a === void 0 ? void 0 : _a.call(this, attr);
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* End parsing. This performs final well-formedness checks and resets the
|
|
1704
|
+
* parser to a clean state.
|
|
1705
|
+
*
|
|
1706
|
+
* @returns this
|
|
1707
|
+
*/
|
|
1708
|
+
end() {
|
|
1709
|
+
var _a, _b;
|
|
1710
|
+
if (!this.sawRoot) {
|
|
1711
|
+
this.fail("document must contain a root element.");
|
|
1712
|
+
}
|
|
1713
|
+
const { tags } = this;
|
|
1714
|
+
while (tags.length > 0) {
|
|
1715
|
+
const tag = tags.pop();
|
|
1716
|
+
this.fail(`unclosed tag: ${tag.name}`);
|
|
1717
|
+
}
|
|
1718
|
+
if (this.state !== S_BEGIN && this.state !== S_TEXT) {
|
|
1719
|
+
this.fail("unexpected end.");
|
|
1720
|
+
}
|
|
1721
|
+
const { text } = this;
|
|
1722
|
+
if (text.length !== 0) {
|
|
1723
|
+
(_a = this.textHandler) === null || _a === void 0 ? void 0 : _a.call(this, text);
|
|
1724
|
+
this.text = "";
|
|
1725
|
+
}
|
|
1726
|
+
this._closed = true;
|
|
1727
|
+
(_b = this.endHandler) === null || _b === void 0 ? void 0 : _b.call(this);
|
|
1728
|
+
this._init();
|
|
1729
|
+
return this;
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Resolve a namespace prefix.
|
|
1733
|
+
*
|
|
1734
|
+
* @param prefix The prefix to resolve.
|
|
1735
|
+
*
|
|
1736
|
+
* @returns The namespace URI or ``undefined`` if the prefix is not defined.
|
|
1737
|
+
*/
|
|
1738
|
+
resolve(prefix) {
|
|
1739
|
+
var _a, _b;
|
|
1740
|
+
let uri = this.topNS[prefix];
|
|
1741
|
+
if (uri !== void 0) {
|
|
1742
|
+
return uri;
|
|
1743
|
+
}
|
|
1744
|
+
const { tags } = this;
|
|
1745
|
+
for (let index = tags.length - 1; index >= 0; index--) {
|
|
1746
|
+
uri = tags[index].ns[prefix];
|
|
1747
|
+
if (uri !== void 0) {
|
|
1748
|
+
return uri;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
uri = this.ns[prefix];
|
|
1752
|
+
if (uri !== void 0) {
|
|
1753
|
+
return uri;
|
|
1754
|
+
}
|
|
1755
|
+
return (_b = (_a = this.opt).resolvePrefix) === null || _b === void 0 ? void 0 : _b.call(_a, prefix);
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Parse a qname into its prefix and local name parts.
|
|
1759
|
+
*
|
|
1760
|
+
* @param name The name to parse
|
|
1761
|
+
*
|
|
1762
|
+
* @returns
|
|
1763
|
+
*/
|
|
1764
|
+
qname(name) {
|
|
1765
|
+
const colon = name.indexOf(":");
|
|
1766
|
+
if (colon === -1) {
|
|
1767
|
+
return { prefix: "", local: name };
|
|
1768
|
+
}
|
|
1769
|
+
const local = name.slice(colon + 1);
|
|
1770
|
+
const prefix = name.slice(0, colon);
|
|
1771
|
+
if (prefix === "" || local === "" || local.includes(":")) {
|
|
1772
|
+
this.fail(`malformed name: ${name}.`);
|
|
1773
|
+
}
|
|
1774
|
+
return { prefix, local };
|
|
1775
|
+
}
|
|
1776
|
+
processAttribsNS() {
|
|
1777
|
+
var _a;
|
|
1778
|
+
const { attribList } = this;
|
|
1779
|
+
const tag = this.tag;
|
|
1780
|
+
{
|
|
1781
|
+
const { prefix, local } = this.qname(tag.name);
|
|
1782
|
+
tag.prefix = prefix;
|
|
1783
|
+
tag.local = local;
|
|
1784
|
+
const uri = tag.uri = (_a = this.resolve(prefix)) !== null && _a !== void 0 ? _a : "";
|
|
1785
|
+
if (prefix !== "") {
|
|
1786
|
+
if (prefix === "xmlns") {
|
|
1787
|
+
this.fail('tags may not have "xmlns" as prefix.');
|
|
1788
|
+
}
|
|
1789
|
+
if (uri === "") {
|
|
1790
|
+
this.fail(`unbound namespace prefix: ${JSON.stringify(prefix)}.`);
|
|
1791
|
+
tag.uri = prefix;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
if (attribList.length === 0) {
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
const { attributes } = tag;
|
|
1799
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1800
|
+
for (const attr of attribList) {
|
|
1801
|
+
const { name, prefix, local } = attr;
|
|
1802
|
+
let uri;
|
|
1803
|
+
let eqname;
|
|
1804
|
+
if (prefix === "") {
|
|
1805
|
+
uri = name === "xmlns" ? XMLNS_NAMESPACE : "";
|
|
1806
|
+
eqname = name;
|
|
1807
|
+
} else {
|
|
1808
|
+
uri = this.resolve(prefix);
|
|
1809
|
+
if (uri === void 0) {
|
|
1810
|
+
this.fail(`unbound namespace prefix: ${JSON.stringify(prefix)}.`);
|
|
1811
|
+
uri = prefix;
|
|
1812
|
+
}
|
|
1813
|
+
eqname = `{${uri}}${local}`;
|
|
1814
|
+
}
|
|
1815
|
+
if (seen.has(eqname)) {
|
|
1816
|
+
this.fail(`duplicate attribute: ${eqname}.`);
|
|
1817
|
+
}
|
|
1818
|
+
seen.add(eqname);
|
|
1819
|
+
attr.uri = uri;
|
|
1820
|
+
attributes[name] = attr;
|
|
1821
|
+
}
|
|
1822
|
+
this.attribList = [];
|
|
1823
|
+
}
|
|
1824
|
+
processAttribsPlain() {
|
|
1825
|
+
const { attribList } = this;
|
|
1826
|
+
const attributes = this.tag.attributes;
|
|
1827
|
+
for (const { name, value } of attribList) {
|
|
1828
|
+
if (attributes[name] !== void 0) {
|
|
1829
|
+
this.fail(`duplicate attribute: ${name}.`);
|
|
1830
|
+
}
|
|
1831
|
+
attributes[name] = value;
|
|
1832
|
+
}
|
|
1833
|
+
this.attribList = [];
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Handle a complete open tag. This parser code calls this once it has seen
|
|
1837
|
+
* the whole tag. This method checks for well-formeness and then emits
|
|
1838
|
+
* ``onopentag``.
|
|
1839
|
+
*/
|
|
1840
|
+
openTag() {
|
|
1841
|
+
var _a;
|
|
1842
|
+
this.processAttribs();
|
|
1843
|
+
const { tags } = this;
|
|
1844
|
+
const tag = this.tag;
|
|
1845
|
+
tag.isSelfClosing = false;
|
|
1846
|
+
(_a = this.openTagHandler) === null || _a === void 0 ? void 0 : _a.call(this, tag);
|
|
1847
|
+
tags.push(tag);
|
|
1848
|
+
this.state = S_TEXT;
|
|
1849
|
+
this.name = "";
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Handle a complete self-closing tag. This parser code calls this once it has
|
|
1853
|
+
* seen the whole tag. This method checks for well-formeness and then emits
|
|
1854
|
+
* ``onopentag`` and ``onclosetag``.
|
|
1855
|
+
*/
|
|
1856
|
+
openSelfClosingTag() {
|
|
1857
|
+
var _a, _b, _c;
|
|
1858
|
+
this.processAttribs();
|
|
1859
|
+
const { tags } = this;
|
|
1860
|
+
const tag = this.tag;
|
|
1861
|
+
tag.isSelfClosing = true;
|
|
1862
|
+
(_a = this.openTagHandler) === null || _a === void 0 ? void 0 : _a.call(this, tag);
|
|
1863
|
+
(_b = this.closeTagHandler) === null || _b === void 0 ? void 0 : _b.call(this, tag);
|
|
1864
|
+
const top = this.tag = (_c = tags[tags.length - 1]) !== null && _c !== void 0 ? _c : null;
|
|
1865
|
+
if (top === null) {
|
|
1866
|
+
this.closedRoot = true;
|
|
1867
|
+
}
|
|
1868
|
+
this.state = S_TEXT;
|
|
1869
|
+
this.name = "";
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Handle a complete close tag. This parser code calls this once it has seen
|
|
1873
|
+
* the whole tag. This method checks for well-formeness and then emits
|
|
1874
|
+
* ``onclosetag``.
|
|
1875
|
+
*/
|
|
1876
|
+
closeTag() {
|
|
1877
|
+
const { tags, name } = this;
|
|
1878
|
+
this.state = S_TEXT;
|
|
1879
|
+
this.name = "";
|
|
1880
|
+
if (name === "") {
|
|
1881
|
+
this.fail("weird empty close tag.");
|
|
1882
|
+
this.text += "</>";
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
const handler = this.closeTagHandler;
|
|
1886
|
+
let l = tags.length;
|
|
1887
|
+
while (l-- > 0) {
|
|
1888
|
+
const tag = this.tag = tags.pop();
|
|
1889
|
+
this.topNS = tag.ns;
|
|
1890
|
+
handler === null || handler === void 0 ? void 0 : handler(tag);
|
|
1891
|
+
if (tag.name === name) {
|
|
1892
|
+
break;
|
|
1893
|
+
}
|
|
1894
|
+
this.fail("unexpected close tag.");
|
|
1895
|
+
}
|
|
1896
|
+
if (l === 0) {
|
|
1897
|
+
this.closedRoot = true;
|
|
1898
|
+
} else if (l < 0) {
|
|
1899
|
+
this.fail(`unmatched closing tag: ${name}.`);
|
|
1900
|
+
this.text += `</${name}>`;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Resolves an entity. Makes any necessary well-formedness checks.
|
|
1905
|
+
*
|
|
1906
|
+
* @param entity The entity to resolve.
|
|
1907
|
+
*
|
|
1908
|
+
* @returns The parsed entity.
|
|
1909
|
+
*/
|
|
1910
|
+
parseEntity(entity) {
|
|
1911
|
+
if (entity[0] !== "#") {
|
|
1912
|
+
const defined = this.ENTITIES[entity];
|
|
1913
|
+
if (defined !== void 0) {
|
|
1914
|
+
return defined;
|
|
1915
|
+
}
|
|
1916
|
+
this.fail(this.isName(entity) ? "undefined entity." : "disallowed character in entity name.");
|
|
1917
|
+
return `&${entity};`;
|
|
1918
|
+
}
|
|
1919
|
+
let num = NaN;
|
|
1920
|
+
if (entity[1] === "x" && /^#x[0-9a-f]+$/i.test(entity)) {
|
|
1921
|
+
num = parseInt(entity.slice(2), 16);
|
|
1922
|
+
} else if (/^#[0-9]+$/.test(entity)) {
|
|
1923
|
+
num = parseInt(entity.slice(1), 10);
|
|
1924
|
+
}
|
|
1925
|
+
if (!this.isChar(num)) {
|
|
1926
|
+
this.fail("malformed character entity.");
|
|
1927
|
+
return `&${entity};`;
|
|
1928
|
+
}
|
|
1929
|
+
return String.fromCodePoint(num);
|
|
1930
|
+
}
|
|
1931
|
+
};
|
|
1932
|
+
exports.SaxesParser = SaxesParser2;
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
2
1935
|
|
|
3
1936
|
// ../core/dist/server.js
|
|
4
1937
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
@@ -29,7 +1962,6 @@ function toPublic(inst) {
|
|
|
29
1962
|
pluginVersion: inst.pluginVersion,
|
|
30
1963
|
pluginVariant: inst.pluginVariant,
|
|
31
1964
|
serverVersion: inst.serverVersion,
|
|
32
|
-
versionMismatch: inst.versionMismatch,
|
|
33
1965
|
lastActivity: inst.lastActivity,
|
|
34
1966
|
connectedAt: inst.connectedAt
|
|
35
1967
|
};
|
|
@@ -37,8 +1969,8 @@ function toPublic(inst) {
|
|
|
37
1969
|
var STALE_INSTANCE_MS = 3e4;
|
|
38
1970
|
var DUPLICATE_TAKEOVER_MS = 3e3;
|
|
39
1971
|
var INSTANCE_ALIAS_TTL_MS = 5 * 60 * 1e3;
|
|
40
|
-
var
|
|
41
|
-
var
|
|
1972
|
+
var ACCEPTED_REQUEST_TOMBSTONE_TTL_MS = 6e4;
|
|
1973
|
+
var MAX_ACCEPTED_REQUEST_TOMBSTONES = 4096;
|
|
42
1974
|
function publishedInstanceId(placeId) {
|
|
43
1975
|
if (placeId === void 0 || !Number.isFinite(placeId) || placeId <= 0)
|
|
44
1976
|
return void 0;
|
|
@@ -46,11 +1978,14 @@ function publishedInstanceId(placeId) {
|
|
|
46
1978
|
}
|
|
47
1979
|
var BridgeService = class {
|
|
48
1980
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
1981
|
+
acceptedRequestIds = /* @__PURE__ */ new Map();
|
|
49
1982
|
// Keyed by pluginSessionId (the per-plugin GUID).
|
|
50
1983
|
instances = /* @__PURE__ */ new Map();
|
|
51
1984
|
instanceAliases = /* @__PURE__ */ new Map();
|
|
52
1985
|
instanceRegisteredListeners = /* @__PURE__ */ new Set();
|
|
53
|
-
|
|
1986
|
+
requestAvailableListeners = /* @__PURE__ */ new Set();
|
|
1987
|
+
sessionClosedListeners = /* @__PURE__ */ new Set();
|
|
1988
|
+
deliveryOwnersByPhysicalSession = /* @__PURE__ */ new Map();
|
|
54
1989
|
requestTimeout = 3e4;
|
|
55
1990
|
onInstanceRegistered(listener) {
|
|
56
1991
|
this.instanceRegisteredListeners.add(listener);
|
|
@@ -70,6 +2005,47 @@ var BridgeService = class {
|
|
|
70
2005
|
}
|
|
71
2006
|
}
|
|
72
2007
|
}
|
|
2008
|
+
onRequestAvailable(listener) {
|
|
2009
|
+
this.requestAvailableListeners.add(listener);
|
|
2010
|
+
return () => this.requestAvailableListeners.delete(listener);
|
|
2011
|
+
}
|
|
2012
|
+
onSessionClosed(listener) {
|
|
2013
|
+
this.sessionClosedListeners.add(listener);
|
|
2014
|
+
return () => this.sessionClosedListeners.delete(listener);
|
|
2015
|
+
}
|
|
2016
|
+
setDeliveryActive(physicalSessionId, owner, active) {
|
|
2017
|
+
let owners = this.deliveryOwnersByPhysicalSession.get(physicalSessionId);
|
|
2018
|
+
if (active) {
|
|
2019
|
+
if (!owners) {
|
|
2020
|
+
owners = /* @__PURE__ */ new Set();
|
|
2021
|
+
this.deliveryOwnersByPhysicalSession.set(physicalSessionId, owners);
|
|
2022
|
+
}
|
|
2023
|
+
owners.add(owner);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
if (!owners)
|
|
2027
|
+
return;
|
|
2028
|
+
owners.delete(owner);
|
|
2029
|
+
if (owners.size === 0)
|
|
2030
|
+
this.deliveryOwnersByPhysicalSession.delete(physicalSessionId);
|
|
2031
|
+
}
|
|
2032
|
+
notifyRequestAvailable(physicalSessionId) {
|
|
2033
|
+
for (const listener of this.requestAvailableListeners) {
|
|
2034
|
+
try {
|
|
2035
|
+
listener(physicalSessionId);
|
|
2036
|
+
} catch {
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
physicalSessionsForTarget(targetInstanceId, targetRole) {
|
|
2041
|
+
const physicalSessionIds = /* @__PURE__ */ new Set();
|
|
2042
|
+
for (const instance of this.instances.values()) {
|
|
2043
|
+
if (instance.instanceId === targetInstanceId && instance.role === targetRole) {
|
|
2044
|
+
physicalSessionIds.add(instance.physicalSessionId);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
return Array.from(physicalSessionIds);
|
|
2048
|
+
}
|
|
73
2049
|
canonicalInstanceId(instanceId, placeId) {
|
|
74
2050
|
return publishedInstanceId(placeId) ?? instanceId;
|
|
75
2051
|
}
|
|
@@ -140,7 +2116,6 @@ var BridgeService = class {
|
|
|
140
2116
|
const pluginVersion = input.pluginVersion ?? "";
|
|
141
2117
|
const pluginVariant = input.pluginVariant ?? "unknown";
|
|
142
2118
|
const serverVersion = input.serverVersion ?? "";
|
|
143
|
-
const versionMismatch = pluginVersion !== "" && serverVersion !== "" && pluginVersion !== serverVersion;
|
|
144
2119
|
this.rememberInstanceAlias(rawInstanceId, instanceId);
|
|
145
2120
|
if (prior && prior.instanceId !== instanceId) {
|
|
146
2121
|
this.rememberInstanceAlias(prior.instanceId, instanceId);
|
|
@@ -166,7 +2141,8 @@ var BridgeService = class {
|
|
|
166
2141
|
}
|
|
167
2142
|
const existing = Array.from(this.instances.values()).find((i) => i.instanceId === instanceId && i.role === assignedRole && i.pluginSessionId !== pluginSessionId);
|
|
168
2143
|
if (existing) {
|
|
169
|
-
|
|
2144
|
+
const existingDeliveryActive = (this.deliveryOwnersByPhysicalSession.get(existing.physicalSessionId)?.size ?? 0) > 0;
|
|
2145
|
+
if (!existingDeliveryActive && Date.now() - existing.lastActivity > DUPLICATE_TAKEOVER_MS) {
|
|
170
2146
|
this.unregisterInstance(existing.pluginSessionId);
|
|
171
2147
|
} else {
|
|
172
2148
|
return {
|
|
@@ -181,6 +2157,7 @@ var BridgeService = class {
|
|
|
181
2157
|
}
|
|
182
2158
|
const registered = {
|
|
183
2159
|
pluginSessionId,
|
|
2160
|
+
physicalSessionId: input.physicalSessionId,
|
|
184
2161
|
instanceId,
|
|
185
2162
|
role: assignedRole,
|
|
186
2163
|
placeId: input.placeId ?? 0,
|
|
@@ -190,24 +2167,35 @@ var BridgeService = class {
|
|
|
190
2167
|
pluginVersion,
|
|
191
2168
|
pluginVariant,
|
|
192
2169
|
serverVersion,
|
|
193
|
-
versionMismatch,
|
|
194
2170
|
lastActivity: Date.now(),
|
|
195
2171
|
connectedAt: prior?.connectedAt ?? Date.now()
|
|
196
2172
|
};
|
|
197
2173
|
this.instances.set(pluginSessionId, registered);
|
|
198
2174
|
this.notifyInstanceRegistered(toPublic(registered));
|
|
199
|
-
this.
|
|
2175
|
+
this.notifyRequestAvailable(registered.physicalSessionId);
|
|
200
2176
|
return { ok: true, assignedRole, instanceId };
|
|
201
2177
|
}
|
|
202
2178
|
unregisterInstance(pluginSessionId) {
|
|
203
2179
|
const removed = this.instances.get(pluginSessionId);
|
|
204
|
-
this.instances.delete(pluginSessionId);
|
|
205
|
-
const pendingPoll = this.pendingPolls.get(pluginSessionId);
|
|
206
|
-
if (pendingPoll) {
|
|
207
|
-
this.settlePendingPoll(pendingPoll, { kind: "session_closed" });
|
|
208
|
-
}
|
|
209
2180
|
if (!removed)
|
|
210
2181
|
return;
|
|
2182
|
+
const logicalChildren = removed.physicalSessionId === pluginSessionId ? Array.from(this.instances.values()).filter((instance) => instance.pluginSessionId !== pluginSessionId && instance.physicalSessionId === pluginSessionId) : [];
|
|
2183
|
+
this.instances.delete(pluginSessionId);
|
|
2184
|
+
const session = {
|
|
2185
|
+
logicalSessionId: pluginSessionId,
|
|
2186
|
+
physicalSessionId: removed.physicalSessionId
|
|
2187
|
+
};
|
|
2188
|
+
for (const listener of this.sessionClosedListeners) {
|
|
2189
|
+
try {
|
|
2190
|
+
listener(session);
|
|
2191
|
+
} catch {
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
if (removed.physicalSessionId === pluginSessionId) {
|
|
2195
|
+
this.deliveryOwnersByPhysicalSession.delete(pluginSessionId);
|
|
2196
|
+
}
|
|
2197
|
+
for (const child of logicalChildren)
|
|
2198
|
+
this.unregisterInstance(child.pluginSessionId);
|
|
211
2199
|
for (const [id, req] of this.pendingRequests.entries()) {
|
|
212
2200
|
const stillHasHandler = Array.from(this.instances.values()).some((i) => i.instanceId === req.targetInstanceId && i.role === req.targetRole);
|
|
213
2201
|
if (!stillHasHandler) {
|
|
@@ -241,9 +2229,14 @@ var BridgeService = class {
|
|
|
241
2229
|
return this.pendingRequests.size;
|
|
242
2230
|
}
|
|
243
2231
|
updateInstanceActivity(pluginSessionId) {
|
|
244
|
-
const
|
|
245
|
-
if (
|
|
246
|
-
|
|
2232
|
+
const instance = this.instances.get(pluginSessionId);
|
|
2233
|
+
if (!instance)
|
|
2234
|
+
return;
|
|
2235
|
+
const now = Date.now();
|
|
2236
|
+
for (const candidate of this.instances.values()) {
|
|
2237
|
+
if (candidate.physicalSessionId === instance.physicalSessionId) {
|
|
2238
|
+
candidate.lastActivity = now;
|
|
2239
|
+
}
|
|
247
2240
|
}
|
|
248
2241
|
}
|
|
249
2242
|
updateInstanceMetadata(pluginSessionId, metadata) {
|
|
@@ -266,14 +2259,15 @@ var BridgeService = class {
|
|
|
266
2259
|
this.rememberInstanceAlias(priorInstanceId, canonicalInstanceId);
|
|
267
2260
|
this.migratePendingRequests(priorInstanceId, canonicalInstanceId);
|
|
268
2261
|
inst.instanceId = canonicalInstanceId;
|
|
269
|
-
this.
|
|
2262
|
+
this.notifyRequestAvailable(inst.physicalSessionId);
|
|
270
2263
|
}
|
|
271
2264
|
}
|
|
272
2265
|
}
|
|
273
2266
|
cleanupStaleInstances() {
|
|
274
2267
|
const now = Date.now();
|
|
275
2268
|
for (const [id, inst] of this.instances.entries()) {
|
|
276
|
-
|
|
2269
|
+
const deliveryActive = (this.deliveryOwnersByPhysicalSession.get(inst.physicalSessionId)?.size ?? 0) > 0;
|
|
2270
|
+
if (!deliveryActive && now - inst.lastActivity > STALE_INSTANCE_MS) {
|
|
277
2271
|
this.unregisterInstance(id);
|
|
278
2272
|
}
|
|
279
2273
|
}
|
|
@@ -389,9 +2383,9 @@ var BridgeService = class {
|
|
|
389
2383
|
};
|
|
390
2384
|
}
|
|
391
2385
|
if (distinctInstanceIds.size > 1) {
|
|
392
|
-
const
|
|
2386
|
+
const errorCode2 = role ? "ambiguous_target" : "multiple_instances_connected";
|
|
393
2387
|
const msg = role ? `target=${role} is ambiguous because multiple Studio places are connected. Pass instance_id to choose a place.` : "Multiple Studio places are connected. Pass instance_id to disambiguate.";
|
|
394
|
-
return { ok: false, error: { code:
|
|
2388
|
+
return { ok: false, error: { code: errorCode2, message: msg, data: errorData } };
|
|
395
2389
|
}
|
|
396
2390
|
const onlyInstanceId = distinctInstanceIds.values().next().value;
|
|
397
2391
|
return this.resolveTarget({ instance_id: onlyInstanceId, target });
|
|
@@ -413,138 +2407,93 @@ var BridgeService = class {
|
|
|
413
2407
|
targetInstanceId,
|
|
414
2408
|
targetRole,
|
|
415
2409
|
timestamp: Date.now(),
|
|
416
|
-
inFlight: false,
|
|
417
2410
|
resolve: resolve5,
|
|
418
2411
|
reject,
|
|
419
2412
|
timeoutId,
|
|
420
2413
|
timeoutMs: effectiveTimeoutMs
|
|
421
2414
|
};
|
|
422
2415
|
this.pendingRequests.set(requestId, request);
|
|
423
|
-
this.
|
|
2416
|
+
for (const physicalSessionId of this.physicalSessionsForTarget(targetInstanceId, targetRole)) {
|
|
2417
|
+
this.notifyRequestAvailable(physicalSessionId);
|
|
2418
|
+
}
|
|
424
2419
|
});
|
|
425
2420
|
}
|
|
426
|
-
|
|
427
|
-
let oldestRequest
|
|
2421
|
+
claimNextRequestForPhysical(physicalSessionId, claimOwner) {
|
|
2422
|
+
let oldestRequest;
|
|
2423
|
+
let logicalSessionId = "";
|
|
428
2424
|
for (const request of this.pendingRequests.values()) {
|
|
429
|
-
if (request.
|
|
430
|
-
continue;
|
|
431
|
-
if (request.targetRole !== callerRole)
|
|
2425
|
+
if (request.claimOwner !== void 0)
|
|
432
2426
|
continue;
|
|
433
|
-
|
|
2427
|
+
let matchingSessionId;
|
|
2428
|
+
for (const candidate of this.instances.values()) {
|
|
2429
|
+
if (candidate.physicalSessionId === physicalSessionId && candidate.instanceId === request.targetInstanceId && candidate.role === request.targetRole) {
|
|
2430
|
+
matchingSessionId = candidate.pluginSessionId;
|
|
2431
|
+
break;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
if (!matchingSessionId)
|
|
434
2435
|
continue;
|
|
435
2436
|
if (!oldestRequest || request.timestamp < oldestRequest.timestamp) {
|
|
436
2437
|
oldestRequest = request;
|
|
2438
|
+
logicalSessionId = matchingSessionId;
|
|
437
2439
|
}
|
|
438
2440
|
}
|
|
439
|
-
if (oldestRequest)
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
return null;
|
|
450
|
-
}
|
|
451
|
-
waitForPendingRequest(pluginSessionId, options = {}) {
|
|
452
|
-
const instance = this.instances.get(pluginSessionId);
|
|
453
|
-
if (!instance) {
|
|
454
|
-
return Promise.resolve({ kind: "unknown_session" });
|
|
455
|
-
}
|
|
456
|
-
if (options.signal?.aborted) {
|
|
457
|
-
return Promise.resolve({ kind: "aborted" });
|
|
458
|
-
}
|
|
459
|
-
this.updateInstanceActivity(pluginSessionId);
|
|
460
|
-
const existingPoll = this.pendingPolls.get(pluginSessionId);
|
|
461
|
-
if (existingPoll) {
|
|
462
|
-
this.settlePendingPoll(existingPoll, { kind: "superseded" });
|
|
463
|
-
}
|
|
464
|
-
const pendingRequest = this.getPendingRequest(instance.instanceId, instance.role);
|
|
465
|
-
if (pendingRequest) {
|
|
466
|
-
return Promise.resolve({
|
|
467
|
-
kind: "request",
|
|
468
|
-
requestId: pendingRequest.requestId,
|
|
469
|
-
request: pendingRequest.request
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
if (this.pendingPolls.size >= MAX_ACTIVE_POLLS) {
|
|
473
|
-
return Promise.resolve({ kind: "capacity" });
|
|
474
|
-
}
|
|
475
|
-
const timeoutMs = Math.min(DEFAULT_POLL_WAIT_TIMEOUT_MS, Math.max(1, options.timeoutMs ?? DEFAULT_POLL_WAIT_TIMEOUT_MS));
|
|
476
|
-
let resolvePoll;
|
|
477
|
-
const promise = new Promise((resolve5) => {
|
|
478
|
-
resolvePoll = resolve5;
|
|
479
|
-
});
|
|
480
|
-
let waiter;
|
|
481
|
-
const timeoutId = setTimeout(() => {
|
|
482
|
-
this.settlePendingPoll(waiter, { kind: "timeout" }, true);
|
|
483
|
-
}, timeoutMs);
|
|
484
|
-
timeoutId.unref();
|
|
485
|
-
waiter = {
|
|
486
|
-
pluginSessionId,
|
|
487
|
-
timeoutId,
|
|
488
|
-
signal: options.signal,
|
|
489
|
-
resolve: resolvePoll
|
|
2441
|
+
if (!oldestRequest)
|
|
2442
|
+
return null;
|
|
2443
|
+
oldestRequest.claimOwner = claimOwner;
|
|
2444
|
+
return {
|
|
2445
|
+
requestId: oldestRequest.id,
|
|
2446
|
+
logicalSessionId,
|
|
2447
|
+
target: oldestRequest.targetRole,
|
|
2448
|
+
endpoint: oldestRequest.endpoint,
|
|
2449
|
+
data: oldestRequest.data
|
|
490
2450
|
};
|
|
491
|
-
if (options.signal) {
|
|
492
|
-
waiter.onAbort = () => {
|
|
493
|
-
this.settlePendingPoll(waiter, { kind: "aborted" });
|
|
494
|
-
};
|
|
495
|
-
options.signal.addEventListener("abort", waiter.onAbort, { once: true });
|
|
496
|
-
}
|
|
497
|
-
this.pendingPolls.set(pluginSessionId, waiter);
|
|
498
|
-
return promise;
|
|
499
2451
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (
|
|
504
|
-
this.settlePendingPoll(waiter, { kind: "session_closed" });
|
|
2452
|
+
releaseDeliveryClaims(claimOwner) {
|
|
2453
|
+
const physicalSessionIds = /* @__PURE__ */ new Set();
|
|
2454
|
+
for (const request of this.pendingRequests.values()) {
|
|
2455
|
+
if (request.claimOwner !== claimOwner)
|
|
505
2456
|
continue;
|
|
2457
|
+
request.claimOwner = void 0;
|
|
2458
|
+
for (const physicalSessionId of this.physicalSessionsForTarget(request.targetInstanceId, request.targetRole)) {
|
|
2459
|
+
physicalSessionIds.add(physicalSessionId);
|
|
506
2460
|
}
|
|
507
|
-
if (targetInstanceId !== void 0 && instance.instanceId !== targetInstanceId)
|
|
508
|
-
continue;
|
|
509
|
-
if (targetRole !== void 0 && instance.role !== targetRole)
|
|
510
|
-
continue;
|
|
511
|
-
const pendingRequest = this.getPendingRequest(instance.instanceId, instance.role);
|
|
512
|
-
if (!pendingRequest)
|
|
513
|
-
continue;
|
|
514
|
-
this.settlePendingPoll(waiter, {
|
|
515
|
-
kind: "request",
|
|
516
|
-
requestId: pendingRequest.requestId,
|
|
517
|
-
request: pendingRequest.request
|
|
518
|
-
}, true);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
settlePendingPoll(waiter, result, refreshActivity = false) {
|
|
522
|
-
if (this.pendingPolls.get(waiter.pluginSessionId) !== waiter)
|
|
523
|
-
return;
|
|
524
|
-
this.pendingPolls.delete(waiter.pluginSessionId);
|
|
525
|
-
clearTimeout(waiter.timeoutId);
|
|
526
|
-
if (waiter.signal && waiter.onAbort) {
|
|
527
|
-
waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
528
2461
|
}
|
|
529
|
-
|
|
530
|
-
this.
|
|
2462
|
+
for (const physicalSessionId of physicalSessionIds) {
|
|
2463
|
+
this.notifyRequestAvailable(physicalSessionId);
|
|
531
2464
|
}
|
|
532
|
-
waiter.resolve(result);
|
|
533
2465
|
}
|
|
534
2466
|
resolveRequest(requestId, response) {
|
|
535
|
-
|
|
536
|
-
if (request) {
|
|
537
|
-
clearTimeout(request.timeoutId);
|
|
538
|
-
this.pendingRequests.delete(requestId);
|
|
539
|
-
request.resolve(response);
|
|
540
|
-
}
|
|
2467
|
+
return this.settleRequest(requestId, (request) => request.resolve(response));
|
|
541
2468
|
}
|
|
542
2469
|
rejectRequest(requestId, error) {
|
|
2470
|
+
return this.settleRequest(requestId, (request) => request.reject(error));
|
|
2471
|
+
}
|
|
2472
|
+
settleRequest(requestId, settle) {
|
|
2473
|
+
const now = Date.now();
|
|
2474
|
+
this.pruneAcceptedRequestIds(now);
|
|
543
2475
|
const request = this.pendingRequests.get(requestId);
|
|
544
|
-
if (request) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
2476
|
+
if (!request) {
|
|
2477
|
+
return this.acceptedRequestIds.has(requestId) ? "already_settled" : "unknown";
|
|
2478
|
+
}
|
|
2479
|
+
clearTimeout(request.timeoutId);
|
|
2480
|
+
this.pendingRequests.delete(requestId);
|
|
2481
|
+
this.acceptedRequestIds.set(requestId, now);
|
|
2482
|
+
this.pruneAcceptedRequestIds(now);
|
|
2483
|
+
settle(request);
|
|
2484
|
+
return "accepted";
|
|
2485
|
+
}
|
|
2486
|
+
pruneAcceptedRequestIds(now) {
|
|
2487
|
+
for (const [requestId, acceptedAt] of this.acceptedRequestIds) {
|
|
2488
|
+
if (now - acceptedAt < ACCEPTED_REQUEST_TOMBSTONE_TTL_MS)
|
|
2489
|
+
break;
|
|
2490
|
+
this.acceptedRequestIds.delete(requestId);
|
|
2491
|
+
}
|
|
2492
|
+
while (this.acceptedRequestIds.size > MAX_ACCEPTED_REQUEST_TOMBSTONES) {
|
|
2493
|
+
const oldestRequestId = this.acceptedRequestIds.keys().next().value;
|
|
2494
|
+
if (oldestRequestId === void 0)
|
|
2495
|
+
break;
|
|
2496
|
+
this.acceptedRequestIds.delete(oldestRequestId);
|
|
548
2497
|
}
|
|
549
2498
|
}
|
|
550
2499
|
cleanupOldRequests() {
|
|
@@ -563,9 +2512,6 @@ var BridgeService = class {
|
|
|
563
2512
|
request.reject(new Error("Connection closed"));
|
|
564
2513
|
}
|
|
565
2514
|
this.pendingRequests.clear();
|
|
566
|
-
for (const waiter of Array.from(this.pendingPolls.values())) {
|
|
567
|
-
this.settlePendingPoll(waiter, { kind: "bridge_closed" });
|
|
568
|
-
}
|
|
569
2515
|
}
|
|
570
2516
|
};
|
|
571
2517
|
|
|
@@ -3377,8 +5323,7 @@ var INTERNAL_RESULT_KEYS = /* @__PURE__ */ new Set([
|
|
|
3377
5323
|
"pluginVariant",
|
|
3378
5324
|
"pluginVersion",
|
|
3379
5325
|
"requestId",
|
|
3380
|
-
"serverVersion"
|
|
3381
|
-
"versionMismatch"
|
|
5326
|
+
"serverVersion"
|
|
3382
5327
|
]);
|
|
3383
5328
|
var TEXT_RESULT_TOOLS = /* @__PURE__ */ new Set(["get_roblox_docs"]);
|
|
3384
5329
|
var SIDE_EFFECTING_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -3656,6 +5601,182 @@ function tokensMatch(provided, expected) {
|
|
|
3656
5601
|
return timingSafeEqual(a, b);
|
|
3657
5602
|
}
|
|
3658
5603
|
|
|
5604
|
+
// ../core/dist/studio-transport.js
|
|
5605
|
+
var HEARTBEAT_INTERVAL_MS = 1e4;
|
|
5606
|
+
var MAX_ACTIVE_EVENT_STREAMS = 64;
|
|
5607
|
+
var SseStudioTransport = class {
|
|
5608
|
+
queue;
|
|
5609
|
+
streams = /* @__PURE__ */ new Map();
|
|
5610
|
+
unsubscribeRequestAvailable;
|
|
5611
|
+
unsubscribeSessionClosed;
|
|
5612
|
+
nextGeneration = 0;
|
|
5613
|
+
constructor(queue) {
|
|
5614
|
+
this.queue = queue;
|
|
5615
|
+
this.unsubscribeRequestAvailable = queue.onRequestAvailable((physicalSessionId) => {
|
|
5616
|
+
const stream = this.streams.get(physicalSessionId);
|
|
5617
|
+
if (stream)
|
|
5618
|
+
this.pump(stream);
|
|
5619
|
+
});
|
|
5620
|
+
this.unsubscribeSessionClosed = queue.onSessionClosed((route) => {
|
|
5621
|
+
if (route.logicalSessionId === route.physicalSessionId) {
|
|
5622
|
+
this.closePhysical(route.physicalSessionId);
|
|
5623
|
+
}
|
|
5624
|
+
});
|
|
5625
|
+
}
|
|
5626
|
+
get activeStreamCount() {
|
|
5627
|
+
return this.streams.size;
|
|
5628
|
+
}
|
|
5629
|
+
canOpen(physicalSessionId) {
|
|
5630
|
+
return this.streams.has(physicalSessionId) || this.streams.size < MAX_ACTIVE_EVENT_STREAMS;
|
|
5631
|
+
}
|
|
5632
|
+
open(physicalSessionId, sink, status) {
|
|
5633
|
+
if (!this.canOpen(physicalSessionId))
|
|
5634
|
+
return void 0;
|
|
5635
|
+
this.nextGeneration += 1;
|
|
5636
|
+
const claimOwner = `sse:${physicalSessionId}:${this.nextGeneration}`;
|
|
5637
|
+
const stream = {
|
|
5638
|
+
physicalSessionId,
|
|
5639
|
+
claimOwner,
|
|
5640
|
+
sink,
|
|
5641
|
+
status,
|
|
5642
|
+
closed: false,
|
|
5643
|
+
blocked: false,
|
|
5644
|
+
statusPending: true,
|
|
5645
|
+
onClose: () => this.closeStream(stream),
|
|
5646
|
+
onDrain: () => {
|
|
5647
|
+
if (stream.closed)
|
|
5648
|
+
return;
|
|
5649
|
+
stream.blocked = false;
|
|
5650
|
+
this.pump(stream);
|
|
5651
|
+
}
|
|
5652
|
+
};
|
|
5653
|
+
this.queue.setDeliveryActive(physicalSessionId, claimOwner, true);
|
|
5654
|
+
this.queue.updateInstanceActivity(physicalSessionId);
|
|
5655
|
+
const replaced = this.streams.get(physicalSessionId);
|
|
5656
|
+
if (replaced)
|
|
5657
|
+
this.closeStream(replaced, true);
|
|
5658
|
+
this.streams.set(physicalSessionId, stream);
|
|
5659
|
+
sink.on("close", stream.onClose);
|
|
5660
|
+
sink.on("error", stream.onClose);
|
|
5661
|
+
sink.on("drain", stream.onDrain);
|
|
5662
|
+
stream.heartbeatTimer = setInterval(() => {
|
|
5663
|
+
if (!stream.closed && !stream.blocked) {
|
|
5664
|
+
this.queue.updateInstanceActivity(physicalSessionId);
|
|
5665
|
+
stream.statusPending = true;
|
|
5666
|
+
this.pump(stream);
|
|
5667
|
+
if (!stream.blocked) {
|
|
5668
|
+
this.write(stream, { kind: "heartbeat", timestamp: Date.now() });
|
|
5669
|
+
}
|
|
5670
|
+
}
|
|
5671
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
5672
|
+
stream.heartbeatTimer.unref();
|
|
5673
|
+
this.pump(stream);
|
|
5674
|
+
return {
|
|
5675
|
+
physicalSessionId,
|
|
5676
|
+
close: () => this.closeStream(stream, true)
|
|
5677
|
+
};
|
|
5678
|
+
}
|
|
5679
|
+
refreshStatus(physicalSessionId) {
|
|
5680
|
+
if (physicalSessionId !== void 0) {
|
|
5681
|
+
const stream = this.streams.get(physicalSessionId);
|
|
5682
|
+
if (stream) {
|
|
5683
|
+
stream.lastStatusJson = void 0;
|
|
5684
|
+
stream.statusPending = true;
|
|
5685
|
+
this.pump(stream);
|
|
5686
|
+
}
|
|
5687
|
+
return;
|
|
5688
|
+
}
|
|
5689
|
+
for (const stream of this.streams.values()) {
|
|
5690
|
+
stream.lastStatusJson = void 0;
|
|
5691
|
+
stream.statusPending = true;
|
|
5692
|
+
this.pump(stream);
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
closePhysical(physicalSessionId) {
|
|
5696
|
+
const stream = this.streams.get(physicalSessionId);
|
|
5697
|
+
if (stream)
|
|
5698
|
+
this.closeStream(stream, true);
|
|
5699
|
+
}
|
|
5700
|
+
close() {
|
|
5701
|
+
for (const stream of Array.from(this.streams.values())) {
|
|
5702
|
+
this.closeStream(stream, true);
|
|
5703
|
+
}
|
|
5704
|
+
this.unsubscribeRequestAvailable();
|
|
5705
|
+
this.unsubscribeSessionClosed();
|
|
5706
|
+
}
|
|
5707
|
+
pump(stream) {
|
|
5708
|
+
if (stream.closed || stream.blocked || this.streams.get(stream.physicalSessionId) !== stream)
|
|
5709
|
+
return;
|
|
5710
|
+
if (stream.statusPending) {
|
|
5711
|
+
stream.statusPending = false;
|
|
5712
|
+
let status;
|
|
5713
|
+
try {
|
|
5714
|
+
status = stream.status();
|
|
5715
|
+
} catch {
|
|
5716
|
+
this.closeStream(stream);
|
|
5717
|
+
return;
|
|
5718
|
+
}
|
|
5719
|
+
const statusJson = JSON.stringify(status);
|
|
5720
|
+
if (statusJson !== stream.lastStatusJson) {
|
|
5721
|
+
stream.lastStatusJson = statusJson;
|
|
5722
|
+
if (!this.write(stream, status))
|
|
5723
|
+
return;
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5726
|
+
while (!stream.closed && !stream.blocked) {
|
|
5727
|
+
const request = this.queue.claimNextRequestForPhysical(stream.physicalSessionId, stream.claimOwner);
|
|
5728
|
+
if (!request)
|
|
5729
|
+
return;
|
|
5730
|
+
const event = {
|
|
5731
|
+
kind: "request",
|
|
5732
|
+
requestId: request.requestId,
|
|
5733
|
+
logicalSessionId: request.logicalSessionId,
|
|
5734
|
+
target: request.target,
|
|
5735
|
+
endpoint: request.endpoint,
|
|
5736
|
+
data: request.data === void 0 ? null : request.data
|
|
5737
|
+
};
|
|
5738
|
+
if (!this.write(stream, event))
|
|
5739
|
+
return;
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
write(stream, event) {
|
|
5743
|
+
if (stream.closed)
|
|
5744
|
+
return false;
|
|
5745
|
+
try {
|
|
5746
|
+
const writable = stream.sink.write(`data: ${JSON.stringify(event)}
|
|
5747
|
+
|
|
5748
|
+
`);
|
|
5749
|
+
if (!writable)
|
|
5750
|
+
stream.blocked = true;
|
|
5751
|
+
return writable;
|
|
5752
|
+
} catch {
|
|
5753
|
+
this.closeStream(stream);
|
|
5754
|
+
return false;
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
closeStream(stream, endSink = false) {
|
|
5758
|
+
if (stream.closed)
|
|
5759
|
+
return;
|
|
5760
|
+
stream.closed = true;
|
|
5761
|
+
clearInterval(stream.heartbeatTimer);
|
|
5762
|
+
stream.sink.removeListener("close", stream.onClose);
|
|
5763
|
+
stream.sink.removeListener("error", stream.onClose);
|
|
5764
|
+
stream.sink.removeListener("drain", stream.onDrain);
|
|
5765
|
+
if (this.streams.get(stream.physicalSessionId) === stream) {
|
|
5766
|
+
this.streams.delete(stream.physicalSessionId);
|
|
5767
|
+
}
|
|
5768
|
+
this.queue.updateInstanceActivity(stream.physicalSessionId);
|
|
5769
|
+
this.queue.setDeliveryActive(stream.physicalSessionId, stream.claimOwner, false);
|
|
5770
|
+
this.queue.releaseDeliveryClaims(stream.claimOwner);
|
|
5771
|
+
if (endSink) {
|
|
5772
|
+
try {
|
|
5773
|
+
stream.sink.end();
|
|
5774
|
+
} catch {
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
};
|
|
5779
|
+
|
|
3659
5780
|
// ../core/dist/http-server.js
|
|
3660
5781
|
function parseLineRange(lineRange) {
|
|
3661
5782
|
const validLine = (line) => line === void 0 || line >= 1;
|
|
@@ -3812,26 +5933,26 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3812
5933
|
let lastMCPActivity = 0;
|
|
3813
5934
|
let mcpServerStartTime = 0;
|
|
3814
5935
|
const proxyInstances = /* @__PURE__ */ new Set();
|
|
3815
|
-
const
|
|
3816
|
-
|
|
3817
|
-
|
|
5936
|
+
const rejectedVersionSessions = /* @__PURE__ */ new Set();
|
|
5937
|
+
const eventTransport = new SseStudioTransport(bridge);
|
|
5938
|
+
const eventStreamHandles = /* @__PURE__ */ new Set();
|
|
3818
5939
|
const setMCPServerActive = (active) => {
|
|
3819
5940
|
mcpServerActive = active;
|
|
3820
5941
|
if (active) {
|
|
3821
|
-
if (activePollEpoch.signal.aborted) {
|
|
3822
|
-
activePollEpoch = new AbortController();
|
|
3823
|
-
}
|
|
3824
5942
|
mcpServerStartTime = Date.now();
|
|
3825
5943
|
lastMCPActivity = Date.now();
|
|
3826
5944
|
} else {
|
|
3827
|
-
activePollEpoch.abort();
|
|
3828
5945
|
mcpServerStartTime = 0;
|
|
3829
5946
|
lastMCPActivity = 0;
|
|
3830
5947
|
}
|
|
5948
|
+
eventTransport.refreshStatus();
|
|
3831
5949
|
};
|
|
3832
5950
|
const trackMCPActivity = () => {
|
|
3833
5951
|
if (mcpServerActive) {
|
|
5952
|
+
const wasConnected = Date.now() - lastMCPActivity < 3e4;
|
|
3834
5953
|
lastMCPActivity = Date.now();
|
|
5954
|
+
if (!wasConnected)
|
|
5955
|
+
eventTransport.refreshStatus();
|
|
3835
5956
|
}
|
|
3836
5957
|
};
|
|
3837
5958
|
const isMCPServerActive = () => {
|
|
@@ -3839,6 +5960,18 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3839
5960
|
return false;
|
|
3840
5961
|
return Date.now() - lastMCPActivity < 3e4;
|
|
3841
5962
|
};
|
|
5963
|
+
const eventStatus = (physicalSessionId) => {
|
|
5964
|
+
const instance = bridge.getInstanceBySessionId(physicalSessionId);
|
|
5965
|
+
const knownInstance = instance?.physicalSessionId === physicalSessionId;
|
|
5966
|
+
return {
|
|
5967
|
+
kind: "status",
|
|
5968
|
+
knownInstance,
|
|
5969
|
+
mcpConnected: isMCPServerActive(),
|
|
5970
|
+
serverVersion: serverConfig?.version,
|
|
5971
|
+
pluginVersion: instance?.pluginVersion,
|
|
5972
|
+
pluginVariant: instance?.pluginVariant
|
|
5973
|
+
};
|
|
5974
|
+
};
|
|
3842
5975
|
const isPluginConnected = () => {
|
|
3843
5976
|
return bridge.getInstances().length > 0;
|
|
3844
5977
|
};
|
|
@@ -3908,17 +6041,18 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3908
6041
|
pluginConnected: instances.length > 0,
|
|
3909
6042
|
instanceCount: instances.length,
|
|
3910
6043
|
instances: publicInstances,
|
|
3911
|
-
versionMismatch: publicInstances.some((inst) => inst.versionMismatch),
|
|
3912
6044
|
mcpServerActive: isMCPServerActive(),
|
|
3913
6045
|
uptime: mcpServerActive ? Date.now() - mcpServerStartTime : 0,
|
|
3914
6046
|
pendingRequests: bridge.getPendingRequestCount(),
|
|
3915
6047
|
proxyInstanceCount: proxyInstances.size,
|
|
6048
|
+
activeEventStreams: eventTransport.activeStreamCount,
|
|
3916
6049
|
streamableHttp: !!serverConfig
|
|
3917
6050
|
});
|
|
3918
6051
|
});
|
|
3919
6052
|
app.post("/ready", (req, res) => {
|
|
3920
|
-
const { pluginSessionId, instanceId, role, placeId, placeName, dataModelName, isRunning, pluginVersion, pluginVariant } = req.body;
|
|
6053
|
+
const { pluginSessionId, physicalSessionId, instanceId, role, placeId, placeName, dataModelName, isRunning, pluginVersion, pluginVariant } = req.body;
|
|
3921
6054
|
const requestContext = {
|
|
6055
|
+
physicalSessionId: typeof physicalSessionId === "string" ? physicalSessionId : void 0,
|
|
3922
6056
|
instanceId: typeof instanceId === "string" ? instanceId : void 0,
|
|
3923
6057
|
role: typeof role === "string" ? role : void 0,
|
|
3924
6058
|
placeId: typeof placeId === "number" ? placeId : void 0,
|
|
@@ -3928,12 +6062,15 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3928
6062
|
pluginVersion: typeof pluginVersion === "string" ? pluginVersion : void 0,
|
|
3929
6063
|
pluginVariant: typeof pluginVariant === "string" ? pluginVariant : void 0
|
|
3930
6064
|
};
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
6065
|
+
const missingFields = [
|
|
6066
|
+
typeof pluginSessionId !== "string" || pluginSessionId === "" ? "pluginSessionId" : void 0,
|
|
6067
|
+
typeof physicalSessionId !== "string" || physicalSessionId === "" ? "physicalSessionId" : void 0,
|
|
6068
|
+
typeof instanceId !== "string" || instanceId === "" ? "instanceId" : void 0,
|
|
6069
|
+
typeof role !== "string" || role === "" ? "role" : void 0,
|
|
6070
|
+
typeof pluginVersion !== "string" || pluginVersion === "" ? "pluginVersion" : void 0,
|
|
6071
|
+
typeof pluginVariant !== "string" || pluginVariant === "" ? "pluginVariant" : void 0
|
|
6072
|
+
].filter((field) => !!field);
|
|
6073
|
+
if (missingFields.length > 0) {
|
|
3937
6074
|
res.status(400).json({
|
|
3938
6075
|
success: false,
|
|
3939
6076
|
error: "missing_ready_fields",
|
|
@@ -3943,19 +6080,71 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3943
6080
|
});
|
|
3944
6081
|
return;
|
|
3945
6082
|
}
|
|
6083
|
+
const serverVersion = serverConfig?.version;
|
|
6084
|
+
if (!serverVersion) {
|
|
6085
|
+
res.status(503).json({
|
|
6086
|
+
success: false,
|
|
6087
|
+
error: "server_version_unavailable",
|
|
6088
|
+
message: "The MCP server cannot accept Studio connections without a configured version.",
|
|
6089
|
+
request: requestContext
|
|
6090
|
+
});
|
|
6091
|
+
return;
|
|
6092
|
+
}
|
|
6093
|
+
if (pluginVersion !== serverVersion) {
|
|
6094
|
+
if (!rejectedVersionSessions.has(pluginSessionId)) {
|
|
6095
|
+
if (rejectedVersionSessions.size >= 256)
|
|
6096
|
+
rejectedVersionSessions.clear();
|
|
6097
|
+
rejectedVersionSessions.add(pluginSessionId);
|
|
6098
|
+
console.error(`[plugin-version-rejected] Studio plugin v${pluginVersion} (${pluginVariant}) does not match MCP server v${serverVersion} for ${instanceId}/${role}`);
|
|
6099
|
+
}
|
|
6100
|
+
res.status(426).json({
|
|
6101
|
+
success: false,
|
|
6102
|
+
error: "plugin_version_mismatch",
|
|
6103
|
+
message: `Studio plugin v${pluginVersion} does not match MCP server v${serverVersion}.`,
|
|
6104
|
+
pluginVersion,
|
|
6105
|
+
serverVersion,
|
|
6106
|
+
request: requestContext
|
|
6107
|
+
});
|
|
6108
|
+
return;
|
|
6109
|
+
}
|
|
6110
|
+
const isClientRole = role === "client" || /^client-[1-9]\d*$/.test(role);
|
|
6111
|
+
const isLogicalSession = physicalSessionId !== pluginSessionId;
|
|
6112
|
+
if (isLogicalSession && !isClientRole || !isLogicalSession && isClientRole || !isClientRole && role !== "edit" && role !== "server") {
|
|
6113
|
+
res.status(400).json({
|
|
6114
|
+
success: false,
|
|
6115
|
+
error: "invalid_session_topology",
|
|
6116
|
+
message: "Physical sessions must use the edit or server role; client roles must use a distinct physical server session.",
|
|
6117
|
+
request: requestContext
|
|
6118
|
+
});
|
|
6119
|
+
return;
|
|
6120
|
+
}
|
|
6121
|
+
if (isLogicalSession) {
|
|
6122
|
+
const physicalOwner = bridge.getInstanceBySessionId(physicalSessionId);
|
|
6123
|
+
const requestedInstanceId = typeof placeId === "number" && Number.isFinite(placeId) && placeId > 0 ? `place:${Math.trunc(placeId)}` : bridge.resolveInstanceId(instanceId);
|
|
6124
|
+
if (!physicalOwner || physicalOwner.pluginSessionId !== physicalSessionId || physicalOwner.physicalSessionId !== physicalSessionId || physicalOwner.role !== "server" || physicalOwner.instanceId !== requestedInstanceId) {
|
|
6125
|
+
res.status(409).json({
|
|
6126
|
+
success: false,
|
|
6127
|
+
error: "physical_session_unavailable",
|
|
6128
|
+
message: "A logical client requires a registered physical server session for the same Studio instance.",
|
|
6129
|
+
request: requestContext
|
|
6130
|
+
});
|
|
6131
|
+
return;
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
3946
6134
|
let result;
|
|
3947
6135
|
try {
|
|
3948
6136
|
result = bridge.registerInstance({
|
|
3949
6137
|
pluginSessionId,
|
|
6138
|
+
physicalSessionId,
|
|
3950
6139
|
instanceId,
|
|
3951
6140
|
role,
|
|
3952
6141
|
placeId: typeof placeId === "number" ? placeId : 0,
|
|
3953
6142
|
placeName: typeof placeName === "string" ? placeName : "",
|
|
3954
6143
|
dataModelName: typeof dataModelName === "string" ? dataModelName : "",
|
|
3955
6144
|
isRunning: !!isRunning,
|
|
3956
|
-
pluginVersion
|
|
3957
|
-
pluginVariant
|
|
3958
|
-
serverVersion
|
|
6145
|
+
pluginVersion,
|
|
6146
|
+
pluginVariant,
|
|
6147
|
+
serverVersion
|
|
3959
6148
|
});
|
|
3960
6149
|
} catch (err2) {
|
|
3961
6150
|
res.status(500).json({
|
|
@@ -3976,17 +6165,12 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3976
6165
|
});
|
|
3977
6166
|
return;
|
|
3978
6167
|
}
|
|
3979
|
-
|
|
3980
|
-
if (registered?.versionMismatch && !warnedVersionMismatches.has(pluginSessionId)) {
|
|
3981
|
-
warnedVersionMismatches.add(pluginSessionId);
|
|
3982
|
-
console.error(`[version-mismatch] Studio plugin v${registered.pluginVersion} (${registered.pluginVariant}) does not match MCP server v${registered.serverVersion} for ${registered.instanceId}/${registered.role}`);
|
|
3983
|
-
}
|
|
6168
|
+
eventTransport.refreshStatus(physicalSessionId);
|
|
3984
6169
|
res.json({
|
|
3985
6170
|
success: true,
|
|
3986
6171
|
assignedRole: result.assignedRole,
|
|
3987
6172
|
instanceId: result.instanceId,
|
|
3988
|
-
serverVersion
|
|
3989
|
-
versionMismatch: registered?.versionMismatch ?? false
|
|
6173
|
+
serverVersion
|
|
3990
6174
|
});
|
|
3991
6175
|
});
|
|
3992
6176
|
app.post("/disconnect", (req, res) => {
|
|
@@ -4013,7 +6197,6 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
4013
6197
|
instanceCount: instances.length,
|
|
4014
6198
|
instances: publicInstances,
|
|
4015
6199
|
serverVersion: serverConfig?.version,
|
|
4016
|
-
versionMismatch: publicInstances.some((inst) => inst.versionMismatch),
|
|
4017
6200
|
mcpServerActive: isMCPServerActive(),
|
|
4018
6201
|
lastMCPActivity,
|
|
4019
6202
|
uptime: mcpServerActive ? Date.now() - mcpServerStartTime : 0
|
|
@@ -4023,179 +6206,71 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
4023
6206
|
const instances = bridge.getInstances();
|
|
4024
6207
|
res.json({
|
|
4025
6208
|
instances,
|
|
4026
|
-
serverVersion: serverConfig?.version
|
|
4027
|
-
versionMismatch: instances.some((inst) => inst.versionMismatch)
|
|
6209
|
+
serverVersion: serverConfig?.version
|
|
4028
6210
|
});
|
|
4029
6211
|
});
|
|
4030
|
-
app.get("/
|
|
6212
|
+
app.get("/events", (req, res) => {
|
|
4031
6213
|
const pluginSessionId = typeof req.query.pluginSessionId === "string" ? req.query.pluginSessionId : void 0;
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
const pollMetadata = () => {
|
|
4037
|
-
const instance = pluginSessionId ? bridge.getInstanceBySessionId(pluginSessionId) : void 0;
|
|
4038
|
-
return {
|
|
4039
|
-
knownInstance: instance !== void 0,
|
|
4040
|
-
pluginVersion: instance?.pluginVersion,
|
|
4041
|
-
pluginVariant: instance?.pluginVariant,
|
|
4042
|
-
versionMismatch: instance?.versionMismatch ?? false
|
|
4043
|
-
};
|
|
4044
|
-
};
|
|
4045
|
-
let metadata = pollMetadata();
|
|
4046
|
-
if (!isMCPServerActive()) {
|
|
4047
|
-
res.status(503).json({
|
|
4048
|
-
error: "MCP server not connected",
|
|
4049
|
-
pluginConnected: true,
|
|
4050
|
-
mcpConnected: false,
|
|
4051
|
-
knownInstance: metadata.knownInstance,
|
|
4052
|
-
serverVersion: serverConfig?.version,
|
|
4053
|
-
pluginVersion: metadata.pluginVersion,
|
|
4054
|
-
pluginVariant: metadata.pluginVariant,
|
|
4055
|
-
versionMismatch: metadata.versionMismatch,
|
|
4056
|
-
request: null
|
|
6214
|
+
if (!pluginSessionId) {
|
|
6215
|
+
res.status(400).json({
|
|
6216
|
+
error: "missing_plugin_session_id",
|
|
6217
|
+
message: "pluginSessionId is required"
|
|
4057
6218
|
});
|
|
4058
6219
|
return;
|
|
4059
6220
|
}
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
knownInstance: false,
|
|
4066
|
-
serverVersion: serverConfig?.version,
|
|
4067
|
-
pluginVersion: metadata.pluginVersion,
|
|
4068
|
-
pluginVariant: metadata.pluginVariant,
|
|
4069
|
-
versionMismatch: metadata.versionMismatch,
|
|
4070
|
-
proxyInstanceCount: proxyInstances.size,
|
|
4071
|
-
pollMode: longPollRequested ? "long" : void 0
|
|
6221
|
+
const instance = bridge.getInstanceBySessionId(pluginSessionId);
|
|
6222
|
+
if (!instance) {
|
|
6223
|
+
res.status(404).json({
|
|
6224
|
+
error: "unknown_session",
|
|
6225
|
+
knownInstance: false
|
|
4072
6226
|
});
|
|
4073
6227
|
return;
|
|
4074
6228
|
}
|
|
4075
|
-
if (
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
res.json({
|
|
4080
|
-
request: pendingRequest?.request ?? null,
|
|
4081
|
-
requestId: pendingRequest?.requestId,
|
|
4082
|
-
mcpConnected: true,
|
|
4083
|
-
pluginConnected: true,
|
|
4084
|
-
knownInstance: metadata.knownInstance,
|
|
4085
|
-
serverVersion: serverConfig?.version,
|
|
4086
|
-
pluginVersion: metadata.pluginVersion,
|
|
4087
|
-
pluginVariant: metadata.pluginVariant,
|
|
4088
|
-
versionMismatch: metadata.versionMismatch,
|
|
4089
|
-
proxyInstanceCount: proxyInstances.size
|
|
6229
|
+
if (instance.physicalSessionId !== pluginSessionId) {
|
|
6230
|
+
res.status(409).json({
|
|
6231
|
+
error: "logical_session_has_no_event_stream",
|
|
6232
|
+
physicalSessionId: instance.physicalSessionId
|
|
4090
6233
|
});
|
|
4091
6234
|
return;
|
|
4092
6235
|
}
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
req.removeListener("aborted", onSocketClosed);
|
|
4099
|
-
res.removeListener("close", onSocketClosed);
|
|
4100
|
-
res.removeListener("finish", onResponseFinished);
|
|
4101
|
-
pollEpoch.signal.removeEventListener("abort", onMCPInactive);
|
|
4102
|
-
};
|
|
4103
|
-
const onSocketClosed = () => {
|
|
4104
|
-
socketClosed = true;
|
|
4105
|
-
pollController.abort();
|
|
4106
|
-
detachListeners();
|
|
4107
|
-
};
|
|
4108
|
-
const onResponseFinished = () => {
|
|
4109
|
-
detachListeners();
|
|
4110
|
-
};
|
|
4111
|
-
const onMCPInactive = () => {
|
|
4112
|
-
mcpBecameInactive = true;
|
|
4113
|
-
pollController.abort();
|
|
4114
|
-
};
|
|
4115
|
-
req.once("aborted", onSocketClosed);
|
|
4116
|
-
res.once("close", onSocketClosed);
|
|
4117
|
-
res.once("finish", onResponseFinished);
|
|
4118
|
-
pollEpoch.signal.addEventListener("abort", onMCPInactive, { once: true });
|
|
4119
|
-
if (req.aborted || res.destroyed) {
|
|
4120
|
-
onSocketClosed();
|
|
4121
|
-
} else if (pollEpoch.signal.aborted) {
|
|
4122
|
-
onMCPInactive();
|
|
4123
|
-
}
|
|
4124
|
-
try {
|
|
4125
|
-
const result = await bridge.waitForPendingRequest(pluginSessionId, {
|
|
4126
|
-
signal: pollController.signal
|
|
4127
|
-
});
|
|
4128
|
-
if (socketClosed || res.destroyed) {
|
|
4129
|
-
return;
|
|
4130
|
-
}
|
|
4131
|
-
metadata = pollMetadata();
|
|
4132
|
-
if (result.kind === "request" && metadata.knownInstance) {
|
|
4133
|
-
res.json({
|
|
4134
|
-
request: result.request,
|
|
4135
|
-
requestId: result.requestId,
|
|
4136
|
-
mcpConnected: true,
|
|
4137
|
-
pluginConnected: true,
|
|
4138
|
-
knownInstance: true,
|
|
4139
|
-
serverVersion: serverConfig?.version,
|
|
4140
|
-
pluginVersion: metadata.pluginVersion,
|
|
4141
|
-
pluginVariant: metadata.pluginVariant,
|
|
4142
|
-
versionMismatch: metadata.versionMismatch,
|
|
4143
|
-
proxyInstanceCount: proxyInstances.size,
|
|
4144
|
-
pollMode: "long"
|
|
4145
|
-
});
|
|
4146
|
-
return;
|
|
4147
|
-
}
|
|
4148
|
-
if (mcpBecameInactive || pollEpoch.signal.aborted || !isMCPServerActive()) {
|
|
4149
|
-
res.status(503).json({
|
|
4150
|
-
error: "MCP server not connected",
|
|
4151
|
-
pluginConnected: true,
|
|
4152
|
-
mcpConnected: false,
|
|
4153
|
-
knownInstance: metadata.knownInstance,
|
|
4154
|
-
serverVersion: serverConfig?.version,
|
|
4155
|
-
pluginVersion: metadata.pluginVersion,
|
|
4156
|
-
pluginVariant: metadata.pluginVariant,
|
|
4157
|
-
versionMismatch: metadata.versionMismatch,
|
|
4158
|
-
request: null
|
|
4159
|
-
});
|
|
4160
|
-
return;
|
|
4161
|
-
}
|
|
4162
|
-
if (result.kind === "capacity") {
|
|
4163
|
-
res.setHeader("Retry-After", "1");
|
|
4164
|
-
res.status(503).json({
|
|
4165
|
-
error: "Poll capacity reached",
|
|
4166
|
-
code: "poll_capacity_reached",
|
|
4167
|
-
pluginConnected: true,
|
|
4168
|
-
mcpConnected: true,
|
|
4169
|
-
knownInstance: metadata.knownInstance,
|
|
4170
|
-
request: null
|
|
4171
|
-
});
|
|
4172
|
-
return;
|
|
4173
|
-
}
|
|
4174
|
-
res.json({
|
|
4175
|
-
request: null,
|
|
4176
|
-
mcpConnected: true,
|
|
4177
|
-
pluginConnected: true,
|
|
4178
|
-
knownInstance: metadata.knownInstance,
|
|
4179
|
-
serverVersion: serverConfig?.version,
|
|
4180
|
-
pluginVersion: metadata.pluginVersion,
|
|
4181
|
-
pluginVariant: metadata.pluginVariant,
|
|
4182
|
-
versionMismatch: metadata.versionMismatch,
|
|
4183
|
-
proxyInstanceCount: proxyInstances.size,
|
|
4184
|
-
pollMode: "long"
|
|
6236
|
+
if (!eventTransport.canOpen(pluginSessionId)) {
|
|
6237
|
+
res.setHeader("Retry-After", "1");
|
|
6238
|
+
res.status(503).json({
|
|
6239
|
+
error: "event_stream_capacity_reached",
|
|
6240
|
+
capacity: MAX_ACTIVE_EVENT_STREAMS
|
|
4185
6241
|
});
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
6242
|
+
return;
|
|
6243
|
+
}
|
|
6244
|
+
bridge.updateInstanceActivity(pluginSessionId);
|
|
6245
|
+
res.status(200);
|
|
6246
|
+
res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
|
|
6247
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
6248
|
+
res.setHeader("Connection", "keep-alive");
|
|
6249
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
6250
|
+
res.flushHeaders();
|
|
6251
|
+
const handle = eventTransport.open(pluginSessionId, res, () => eventStatus(pluginSessionId));
|
|
6252
|
+
if (!handle) {
|
|
6253
|
+
res.end();
|
|
6254
|
+
return;
|
|
4189
6255
|
}
|
|
6256
|
+
eventStreamHandles.add(handle);
|
|
6257
|
+
res.once("close", () => eventStreamHandles.delete(handle));
|
|
4190
6258
|
});
|
|
4191
6259
|
app.post("/response", (req, res) => {
|
|
4192
6260
|
const { requestId, response, error } = req.body;
|
|
4193
|
-
if (
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
6261
|
+
if (typeof requestId !== "string" || requestId.length === 0) {
|
|
6262
|
+
res.status(400).json({
|
|
6263
|
+
success: false,
|
|
6264
|
+
error: "invalid_request_id"
|
|
6265
|
+
});
|
|
6266
|
+
return;
|
|
4197
6267
|
}
|
|
4198
|
-
|
|
6268
|
+
const disposition = error !== void 0 ? bridge.rejectRequest(requestId, error) : bridge.resolveRequest(requestId, response);
|
|
6269
|
+
if (disposition === "unknown") {
|
|
6270
|
+
res.status(404).json({ success: false, disposition });
|
|
6271
|
+
return;
|
|
6272
|
+
}
|
|
6273
|
+
res.json({ success: true, disposition });
|
|
4199
6274
|
});
|
|
4200
6275
|
app.post("/proxy", async (req, res) => {
|
|
4201
6276
|
const { endpoint, data, targetInstanceId, targetRole, proxyInstanceId } = req.body;
|
|
@@ -4257,6 +6332,13 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
4257
6332
|
app.isMCPServerActive = isMCPServerActive;
|
|
4258
6333
|
app.trackMCPActivity = trackMCPActivity;
|
|
4259
6334
|
app.closeMcpHandler = () => mcpHandler?.close();
|
|
6335
|
+
app.cleanup = async () => {
|
|
6336
|
+
for (const handle of eventStreamHandles)
|
|
6337
|
+
handle.close();
|
|
6338
|
+
eventStreamHandles.clear();
|
|
6339
|
+
eventTransport.close();
|
|
6340
|
+
await mcpHandler?.close();
|
|
6341
|
+
};
|
|
4260
6342
|
return app;
|
|
4261
6343
|
}
|
|
4262
6344
|
function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
@@ -7106,6 +9188,7 @@ function rgbaToPng(rgba, width, height) {
|
|
|
7106
9188
|
import * as fs3 from "fs";
|
|
7107
9189
|
import * as path5 from "path";
|
|
7108
9190
|
var MAX_INLINE_IMAGE_BYTES = 6e6;
|
|
9191
|
+
var MAX_MATRIX_IMAGE_BYTES = 8e6;
|
|
7109
9192
|
var DEFAULT_ASSET_AUDIO_PREVIEWS = 3;
|
|
7110
9193
|
var MAX_ASSET_AUDIO_PREVIEWS = 5;
|
|
7111
9194
|
var MAX_INLINE_AUDIO_PREVIEW_BYTES = 3e6;
|
|
@@ -8047,7 +10130,7 @@ var RobloxStudioTools = class {
|
|
|
8047
10130
|
}
|
|
8048
10131
|
_resolveDeviceSimulatorSingleTarget(target, instance_id, toolName) {
|
|
8049
10132
|
const selectedTarget = target ?? "edit";
|
|
8050
|
-
if (selectedTarget === "server" || selectedTarget === "all" || selectedTarget === "all-clients"
|
|
10133
|
+
if (selectedTarget === "server" || selectedTarget === "all" || selectedTarget === "all-clients") {
|
|
8051
10134
|
throw new Error(`${toolName} target must be "edit" or "client-N" (got: ${selectedTarget})`);
|
|
8052
10135
|
}
|
|
8053
10136
|
if (selectedTarget !== "edit" && !/^client-\d+$/.test(selectedTarget)) {
|
|
@@ -8085,7 +10168,7 @@ var RobloxStudioTools = class {
|
|
|
8085
10168
|
}
|
|
8086
10169
|
_resolveSimulationTargets(target, instance_id, toolName) {
|
|
8087
10170
|
const selectedTarget = target ?? "edit-and-clients";
|
|
8088
|
-
if (selectedTarget === "server" || selectedTarget === "all"
|
|
10171
|
+
if (selectedTarget === "server" || selectedTarget === "all") {
|
|
8089
10172
|
throw new Error(`${toolName} target must be "edit", "client-N", "all-clients", or "edit-and-clients" (got: ${selectedTarget})`);
|
|
8090
10173
|
}
|
|
8091
10174
|
const instanceId = this._resolveInstanceIdOnly(instance_id);
|
|
@@ -8886,6 +10969,7 @@ var RobloxStudioTools = class {
|
|
|
8886
10969
|
const entrySummaries = summary.entries;
|
|
8887
10970
|
const content = [];
|
|
8888
10971
|
const failures = [];
|
|
10972
|
+
let imageBudget = MAX_MATRIX_IMAGE_BYTES;
|
|
8889
10973
|
try {
|
|
8890
10974
|
for (let i = 0; i < matrixEntries.length; i++) {
|
|
8891
10975
|
const entry = matrixEntries[i];
|
|
@@ -8903,8 +10987,10 @@ var RobloxStudioTools = class {
|
|
|
8903
10987
|
entrySummary.applied = applied;
|
|
8904
10988
|
if (settleMs > 0)
|
|
8905
10989
|
await sleep(settleMs);
|
|
8906
|
-
const
|
|
10990
|
+
const entryBudget = Math.max(1, Math.floor(imageBudget / (matrixEntries.length - i)));
|
|
10991
|
+
const capture = await this._captureViewportImage(resolved.instanceId, resolved.role, format, quality, entryBudget);
|
|
8907
10992
|
if (capture.success) {
|
|
10993
|
+
imageBudget -= Math.ceil(capture.data.length * 3 / 4);
|
|
8908
10994
|
entrySummary.screenshot = {
|
|
8909
10995
|
width: capture.width,
|
|
8910
10996
|
height: capture.height,
|
|
@@ -9001,7 +11087,7 @@ var RobloxStudioTools = class {
|
|
|
9001
11087
|
content: [{ type: "text", text: JSON.stringify(response) }]
|
|
9002
11088
|
};
|
|
9003
11089
|
}
|
|
9004
|
-
const targets = resolved.targets
|
|
11090
|
+
const targets = resolved.targets;
|
|
9005
11091
|
const originPeerReliable = targets.length > 0 ? await this._isMultiplayerTestRunning(targets[0].targetInstanceId) : false;
|
|
9006
11092
|
const responses = await Promise.allSettled(targets.map(async (t) => {
|
|
9007
11093
|
const r = await this.client.request("/api/get-runtime-logs", data, t.targetInstanceId, t.targetRole);
|
|
@@ -10646,7 +12732,7 @@ var RobloxStudioTools = class {
|
|
|
10646
12732
|
const response = await this.client.request("/api/get-memory-breakdown", data, resolved.targetInstanceId, resolved.targetRole);
|
|
10647
12733
|
return { content: [{ type: "text", text: JSON.stringify(response) }] };
|
|
10648
12734
|
}
|
|
10649
|
-
const targets = resolved.targets
|
|
12735
|
+
const targets = resolved.targets;
|
|
10650
12736
|
const responses = await Promise.allSettled(targets.map(async (t) => ({
|
|
10651
12737
|
peer: t.targetRole,
|
|
10652
12738
|
result: await this.client.request("/api/get-memory-breakdown", data, t.targetInstanceId, t.targetRole)
|
|
@@ -10679,7 +12765,7 @@ var RobloxStudioTools = class {
|
|
|
10679
12765
|
const response = await this.client.request("/api/get-scene-analysis", data, resolved.targetInstanceId, resolved.targetRole);
|
|
10680
12766
|
return { content: [{ type: "text", text: JSON.stringify(response) }] };
|
|
10681
12767
|
}
|
|
10682
|
-
const targets = resolved.targets
|
|
12768
|
+
const targets = resolved.targets;
|
|
10683
12769
|
const responses = await Promise.allSettled(targets.map(async (t) => ({
|
|
10684
12770
|
peer: t.targetRole,
|
|
10685
12771
|
result: await this.client.request("/api/get-scene-analysis", data, t.targetInstanceId, t.targetRole)
|
|
@@ -10803,7 +12889,7 @@ var RobloxStudioTools = class {
|
|
|
10803
12889
|
}, tgt, instance_id);
|
|
10804
12890
|
return { content: [{ type: "text", text: JSON.stringify(response) }] };
|
|
10805
12891
|
}
|
|
10806
|
-
async _captureViewportImage(instanceId, targetRole, format, quality) {
|
|
12892
|
+
async _captureViewportImage(instanceId, targetRole, format, quality, maxBytes = MAX_INLINE_IMAGE_BYTES) {
|
|
10807
12893
|
let response;
|
|
10808
12894
|
if (targetRole.startsWith("client-")) {
|
|
10809
12895
|
const begin = await this._callSingle("/api/capture-begin", {}, targetRole, instanceId);
|
|
@@ -10836,21 +12922,29 @@ var RobloxStudioTools = class {
|
|
|
10836
12922
|
const { mimeType } = encoded;
|
|
10837
12923
|
let usedQ = q;
|
|
10838
12924
|
let note = "";
|
|
10839
|
-
if (buffer.length >
|
|
12925
|
+
if (buffer.length > maxBytes) {
|
|
10840
12926
|
if (fmt === "png") {
|
|
10841
12927
|
const mb = (buffer.length / 1048576).toFixed(1);
|
|
10842
12928
|
return {
|
|
10843
12929
|
success: false,
|
|
10844
|
-
error: `PNG screenshot is ${mb}MB, over the ~${(
|
|
12930
|
+
error: `PNG screenshot is ${mb}MB, over the ~${(maxBytes / 1048576).toFixed(1)}MB inline image limit. Use the default jpeg format (optionally with a "quality" value) or make the Studio window smaller for a lossless capture.`
|
|
10845
12931
|
};
|
|
10846
12932
|
}
|
|
10847
|
-
while (buffer.length >
|
|
12933
|
+
while (buffer.length > maxBytes && usedQ > 25) {
|
|
10848
12934
|
usedQ = Math.max(25, usedQ - 20);
|
|
10849
12935
|
buffer = encodeImageFromRgbaResponse(response, "jpeg", usedQ).buffer;
|
|
10850
12936
|
}
|
|
12937
|
+
if (buffer.length > maxBytes) {
|
|
12938
|
+
return {
|
|
12939
|
+
success: false,
|
|
12940
|
+
error: `JPEG screenshot is still ${(buffer.length / 1048576).toFixed(1)}MB at q${usedQ}, over the ${(maxBytes / 1048576).toFixed(1)}MB inline budget. Make the Studio window smaller or capture fewer images per call.`
|
|
12941
|
+
};
|
|
12942
|
+
}
|
|
10851
12943
|
note = ` \u2014 auto-reduced to q${usedQ} to fit the inline size limit; enlarge the Studio window or capture a smaller region for finer detail`;
|
|
10852
12944
|
}
|
|
10853
|
-
const
|
|
12945
|
+
const nativeW = response.nativeWidth ?? w;
|
|
12946
|
+
const nativeH = response.nativeHeight ?? h;
|
|
12947
|
+
const message = nativeW !== w || nativeH !== h ? `Screenshot ${w}x${h}px (${fmt}${fmt === "jpeg" ? ` q${usedQ}` : ""})${note}, downscaled from the ${nativeW}x${nativeH} viewport to fit transport limits. For simulate_mouse_input, multiply x read off this image by ${(nativeW / w).toFixed(4)} and y by ${(nativeH / h).toFixed(4)} to get viewport pixel coordinates ((0,0) at the top-left).` : `Screenshot ${w}x${h}px (${fmt}${fmt === "jpeg" ? ` q${usedQ}` : ""})${note}. For simulate_mouse_input, x/y are pixel coordinates in this exact image with (0,0) at the top-left; it is not downscaled, so use coordinates as you read them off the image.`;
|
|
10854
12948
|
return {
|
|
10855
12949
|
success: true,
|
|
10856
12950
|
width: w,
|
|
@@ -10878,7 +12972,8 @@ var RobloxStudioTools = class {
|
|
|
10878
12972
|
height: capture.height,
|
|
10879
12973
|
format: capture.format,
|
|
10880
12974
|
mimeType: capture.mimeType,
|
|
10881
|
-
...capture.quality === void 0 ? {} : { quality: capture.quality }
|
|
12975
|
+
...capture.quality === void 0 ? {} : { quality: capture.quality },
|
|
12976
|
+
message: capture.message
|
|
10882
12977
|
})
|
|
10883
12978
|
},
|
|
10884
12979
|
{
|
|
@@ -11050,6 +13145,8 @@ var RobloxStudioMCPServer = class {
|
|
|
11050
13145
|
console.error(`HTTP server listening on ${host}:${boundPort} for Studio plugin (primary mode)`);
|
|
11051
13146
|
console.error(`Streamable HTTP MCP endpoint: http://localhost:${boundPort}/mcp`);
|
|
11052
13147
|
} catch (error) {
|
|
13148
|
+
await primaryApp?.cleanup().catch(() => {
|
|
13149
|
+
});
|
|
11053
13150
|
if (process.env.ROBLOX_STUDIO_REQUIRE_PRIMARY === "1") {
|
|
11054
13151
|
console.error(`Port ${basePort} is unavailable and ROBLOX_STUDIO_REQUIRE_PRIMARY=1; refusing proxy mode`);
|
|
11055
13152
|
throw error;
|
|
@@ -11083,23 +13180,11 @@ var RobloxStudioMCPServer = class {
|
|
|
11083
13180
|
if (promotionInterval)
|
|
11084
13181
|
clearInterval(promotionInterval);
|
|
11085
13182
|
} catch {
|
|
13183
|
+
await candidateApp.cleanup().catch(() => {
|
|
13184
|
+
});
|
|
11086
13185
|
}
|
|
11087
13186
|
}, promotionIntervalMs);
|
|
11088
13187
|
}
|
|
11089
|
-
const LEGACY_PORT = 3002;
|
|
11090
|
-
let legacyHandle;
|
|
11091
|
-
let legacyApp;
|
|
11092
|
-
if (boundPort !== LEGACY_PORT && bridgeMode === "primary") {
|
|
11093
|
-
legacyApp = createHttpServer(this.tools, this.bridge, this.allowedToolNames, this.config, security);
|
|
11094
|
-
try {
|
|
11095
|
-
const result = await listenWithRetry(legacyApp, host, LEGACY_PORT, 1);
|
|
11096
|
-
legacyHandle = result.server;
|
|
11097
|
-
console.error(`Legacy HTTP server also listening on ${host}:${LEGACY_PORT} for old plugins`);
|
|
11098
|
-
legacyApp.setMCPServerActive(true);
|
|
11099
|
-
} catch {
|
|
11100
|
-
console.error(`Legacy port ${LEGACY_PORT} in use, skipping backward-compat listener`);
|
|
11101
|
-
}
|
|
11102
|
-
}
|
|
11103
13188
|
const stdioHandle = serveStdio((context) => createToolServer({
|
|
11104
13189
|
config: this.config,
|
|
11105
13190
|
getTools: () => this.tools,
|
|
@@ -11121,8 +13206,6 @@ var RobloxStudioMCPServer = class {
|
|
|
11121
13206
|
const activityInterval = setInterval(() => {
|
|
11122
13207
|
if (primaryApp)
|
|
11123
13208
|
primaryApp.trackMCPActivity();
|
|
11124
|
-
if (legacyApp)
|
|
11125
|
-
legacyApp.trackMCPActivity();
|
|
11126
13209
|
if (bridgeMode === "primary" && primaryApp) {
|
|
11127
13210
|
const pluginConnected = primaryApp.isPluginConnected();
|
|
11128
13211
|
const mcpActive = primaryApp.isMCPServerActive();
|
|
@@ -11147,22 +13230,16 @@ var RobloxStudioMCPServer = class {
|
|
|
11147
13230
|
if (promotionInterval)
|
|
11148
13231
|
clearInterval(promotionInterval);
|
|
11149
13232
|
primaryApp?.setMCPServerActive(false);
|
|
11150
|
-
legacyApp?.setMCPServerActive(false);
|
|
11151
13233
|
this.bridge.clearAllPendingRequests();
|
|
11152
13234
|
if (this.bridge instanceof ProxyBridgeService) {
|
|
11153
13235
|
this.bridge.stop();
|
|
11154
13236
|
}
|
|
11155
13237
|
await stdioHandle.close().catch(() => {
|
|
11156
13238
|
});
|
|
11157
|
-
await
|
|
11158
|
-
primaryApp?.closeMcpHandler(),
|
|
11159
|
-
legacyApp?.closeMcpHandler()
|
|
11160
|
-
]).catch(() => {
|
|
13239
|
+
await primaryApp?.cleanup().catch(() => {
|
|
11161
13240
|
});
|
|
11162
13241
|
if (httpHandle)
|
|
11163
13242
|
httpHandle.close();
|
|
11164
|
-
if (legacyHandle)
|
|
11165
|
-
legacyHandle.close();
|
|
11166
13243
|
process.exit(0);
|
|
11167
13244
|
};
|
|
11168
13245
|
process.on("SIGTERM", shutdown);
|
|
@@ -12909,12 +14986,15 @@ var TOOL_DEFINITIONS = [
|
|
|
12909
14986
|
var getReadOnlyTools = () => TOOL_DEFINITIONS.filter((t) => t.category === "read");
|
|
12910
14987
|
|
|
12911
14988
|
// ../core/dist/install-plugin-helpers.js
|
|
12912
|
-
|
|
14989
|
+
var import_saxes = __toESM(require_saxes(), 1);
|
|
14990
|
+
import { existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync, rmSync as rmSync2, statSync as statSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
12913
14991
|
import { execSync } from "child_process";
|
|
12914
|
-
import {
|
|
14992
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
14993
|
+
import { basename as basename3, join as join5 } from "path";
|
|
12915
14994
|
import { homedir as homedir4 } from "os";
|
|
14995
|
+
import { isUtf8 } from "buffer";
|
|
12916
14996
|
var DEFAULT_PLUGIN_PORT = 58741;
|
|
12917
|
-
var SERVER_URL_SETTING_KEY_PATTERN = /MCP_LAST_SUCCESSFUL_SERVER_URL_GLOBAL_V1|MCP_LAST_SUCCESSFUL_SERVER_URL_
|
|
14997
|
+
var SERVER_URL_SETTING_KEY_PATTERN = /MCP_LAST_SUCCESSFUL_SERVER_URL_GLOBAL_V1|MCP_LAST_SUCCESSFUL_SERVER_URL_/g;
|
|
12918
14998
|
function configurePluginAssetForPort(source, rawPort = process.env.ROBLOX_STUDIO_PORT) {
|
|
12919
14999
|
if (rawPort === void 0 || rawPort === "")
|
|
12920
15000
|
return source;
|
|
@@ -12941,19 +15021,19 @@ function configurePluginAssetForPort(source, rawPort = process.env.ROBLOX_STUDIO
|
|
|
12941
15021
|
function isWSL() {
|
|
12942
15022
|
return getStudioPlatformCapabilities().isWsl;
|
|
12943
15023
|
}
|
|
12944
|
-
function
|
|
15024
|
+
function getWindowsUserLocalAppData() {
|
|
12945
15025
|
try {
|
|
12946
|
-
const
|
|
15026
|
+
const windowsPath = execSync('cmd.exe /c "echo %LOCALAPPDATA%"', {
|
|
12947
15027
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12948
15028
|
}).toString().trim();
|
|
12949
|
-
if (!
|
|
15029
|
+
if (!windowsPath || windowsPath.includes("%"))
|
|
12950
15030
|
return null;
|
|
12951
|
-
const linuxPath = execSync(`wslpath -u '${
|
|
15031
|
+
const linuxPath = execSync(`wslpath -u '${windowsPath.replace(/'/g, "'\\''")}'`, {
|
|
12952
15032
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12953
15033
|
}).toString().trim();
|
|
12954
15034
|
if (!linuxPath)
|
|
12955
15035
|
return null;
|
|
12956
|
-
return
|
|
15036
|
+
return { linuxPath, windowsPath };
|
|
12957
15037
|
} catch {
|
|
12958
15038
|
return null;
|
|
12959
15039
|
}
|
|
@@ -12965,13 +15045,78 @@ function getPluginsFolder() {
|
|
|
12965
15045
|
return join5(process.env.LOCALAPPDATA || join5(homedir4(), "AppData", "Local"), "Roblox", "Plugins");
|
|
12966
15046
|
}
|
|
12967
15047
|
if (isWSL()) {
|
|
12968
|
-
const
|
|
12969
|
-
if (
|
|
12970
|
-
return
|
|
15048
|
+
const localAppData = getWindowsUserLocalAppData();
|
|
15049
|
+
if (localAppData)
|
|
15050
|
+
return join5(localAppData.linuxPath, "Roblox", "Plugins");
|
|
12971
15051
|
console.warn("[install-plugin] WSL detected but could not resolve Windows %LOCALAPPDATA%. Falling back to ~/Documents/Roblox/Plugins/ - you will likely need to copy the rbxmx to /mnt/c/Users/<you>/AppData/Local/Roblox/Plugins/ manually. Set MCP_PLUGINS_DIR to skip detection.");
|
|
12972
15052
|
}
|
|
12973
15053
|
return join5(homedir4(), "Documents", "Roblox", "Plugins");
|
|
12974
15054
|
}
|
|
15055
|
+
var STALE_TEST_PLUGINS_DIRECTORY = "RsmcpIsolatedPlugins";
|
|
15056
|
+
function pluginDirectorySettingPattern() {
|
|
15057
|
+
return /(<QDir\b[^>]*\bname="PluginsDir"[^>]*>)([\s\S]*?)(<\/QDir>)/gu;
|
|
15058
|
+
}
|
|
15059
|
+
function escapeXmlText(value) {
|
|
15060
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
15061
|
+
}
|
|
15062
|
+
function repairStaleStudioPluginDirectorySetting({ settingsPath, studioPluginsDirectory }) {
|
|
15063
|
+
if (!existsSync6(settingsPath))
|
|
15064
|
+
return false;
|
|
15065
|
+
if (studioPluginsDirectory === "") {
|
|
15066
|
+
throw new Error("studioPluginsDirectory must be a non-empty path.");
|
|
15067
|
+
}
|
|
15068
|
+
const contents = readFileSync6(settingsPath, "utf8");
|
|
15069
|
+
const matches = [...contents.matchAll(pluginDirectorySettingPattern())];
|
|
15070
|
+
if (matches.length !== 1) {
|
|
15071
|
+
throw new Error(`Expected exactly one Studio.PluginsDir QDir in ${settingsPath}; found ${matches.length}.`);
|
|
15072
|
+
}
|
|
15073
|
+
if (matches[0][2] !== STALE_TEST_PLUGINS_DIRECTORY)
|
|
15074
|
+
return false;
|
|
15075
|
+
const studioPath = escapeXmlText(studioPluginsDirectory);
|
|
15076
|
+
const updated = contents.replace(pluginDirectorySettingPattern(), (_match, open2, _value, close) => `${open2}${studioPath}${close}`);
|
|
15077
|
+
const temporaryPath = `${settingsPath}.rsmcp-install-${process.pid}-${randomUUID5()}.tmp`;
|
|
15078
|
+
try {
|
|
15079
|
+
writeFileSync3(temporaryPath, updated, {
|
|
15080
|
+
encoding: "utf8",
|
|
15081
|
+
mode: statSync3(settingsPath).mode
|
|
15082
|
+
});
|
|
15083
|
+
renameSync(temporaryPath, settingsPath);
|
|
15084
|
+
} finally {
|
|
15085
|
+
rmSync2(temporaryPath, { force: true });
|
|
15086
|
+
}
|
|
15087
|
+
return true;
|
|
15088
|
+
}
|
|
15089
|
+
function repairDefaultStudioPluginDirectorySetting(pluginsFolder, log) {
|
|
15090
|
+
if (process.env.MCP_PLUGINS_DIR)
|
|
15091
|
+
return;
|
|
15092
|
+
let settingsPath;
|
|
15093
|
+
let defaultPluginsFolder;
|
|
15094
|
+
let studioPluginsDirectory;
|
|
15095
|
+
if (process.platform === "win32") {
|
|
15096
|
+
const localAppData = process.env.LOCALAPPDATA || join5(homedir4(), "AppData", "Local");
|
|
15097
|
+
defaultPluginsFolder = join5(localAppData, "Roblox", "Plugins");
|
|
15098
|
+
settingsPath = join5(localAppData, "Roblox", "GlobalSettings_13.xml");
|
|
15099
|
+
studioPluginsDirectory = defaultPluginsFolder.replaceAll("\\", "/");
|
|
15100
|
+
} else if (isWSL()) {
|
|
15101
|
+
const localAppData = getWindowsUserLocalAppData();
|
|
15102
|
+
if (!localAppData)
|
|
15103
|
+
return;
|
|
15104
|
+
defaultPluginsFolder = join5(localAppData.linuxPath, "Roblox", "Plugins");
|
|
15105
|
+
settingsPath = join5(localAppData.linuxPath, "Roblox", "GlobalSettings_13.xml");
|
|
15106
|
+
studioPluginsDirectory = [
|
|
15107
|
+
localAppData.windowsPath.replace(/[\\/]+$/u, ""),
|
|
15108
|
+
"Roblox",
|
|
15109
|
+
"Plugins"
|
|
15110
|
+
].join("/").replaceAll("\\", "/");
|
|
15111
|
+
} else {
|
|
15112
|
+
return;
|
|
15113
|
+
}
|
|
15114
|
+
if (pluginsFolder !== defaultPluginsFolder)
|
|
15115
|
+
return;
|
|
15116
|
+
if (repairStaleStudioPluginDirectorySetting({ settingsPath, studioPluginsDirectory })) {
|
|
15117
|
+
log(`Restored Studio PluginsDir to ${studioPluginsDirectory}.`);
|
|
15118
|
+
}
|
|
15119
|
+
}
|
|
12975
15120
|
function handleVariantConflict({ pluginsFolder, otherAssetName, replace, log = console.log, warn = console.warn }) {
|
|
12976
15121
|
const otherDest = join5(pluginsFolder, otherAssetName);
|
|
12977
15122
|
if (!existsSync6(otherDest))
|
|
@@ -12991,12 +15136,190 @@ Only one MCP plugin variant should be present. If both variants are in the Studi
|
|
|
12991
15136
|
Delete ${otherAssetName} manually or use the default CLI installer behavior to replace it.
|
|
12992
15137
|
`);
|
|
12993
15138
|
}
|
|
15139
|
+
var PLUGIN_INSTALL_LOCK_NAME = ".robloxstudio-mcp-plugin-install.lock";
|
|
15140
|
+
function errorCode(error) {
|
|
15141
|
+
return error.code;
|
|
15142
|
+
}
|
|
15143
|
+
function acquirePluginInstallLock(pluginsFolder, warn) {
|
|
15144
|
+
const lockPath = join5(pluginsFolder, PLUGIN_INSTALL_LOCK_NAME);
|
|
15145
|
+
const ownerPath = join5(lockPath, "owner.json");
|
|
15146
|
+
const owner = {
|
|
15147
|
+
pid: process.pid,
|
|
15148
|
+
token: randomUUID5(),
|
|
15149
|
+
createdAt: Date.now()
|
|
15150
|
+
};
|
|
15151
|
+
try {
|
|
15152
|
+
mkdirSync4(lockPath);
|
|
15153
|
+
} catch (error) {
|
|
15154
|
+
if (errorCode(error) !== "EEXIST")
|
|
15155
|
+
throw error;
|
|
15156
|
+
throw new Error(`Another Studio plugin installation is already in progress: ${lockPath}. If no installer is running, remove that lock directory and retry.`);
|
|
15157
|
+
}
|
|
15158
|
+
try {
|
|
15159
|
+
writeFileSync3(ownerPath, `${JSON.stringify(owner)}
|
|
15160
|
+
`, {
|
|
15161
|
+
encoding: "utf8",
|
|
15162
|
+
flag: "wx"
|
|
15163
|
+
});
|
|
15164
|
+
} catch (error) {
|
|
15165
|
+
try {
|
|
15166
|
+
rmSync2(lockPath, { recursive: true, force: true });
|
|
15167
|
+
} catch (cleanupError) {
|
|
15168
|
+
warn(`[install-plugin] Could not clean failed install lock ${lockPath}: ${cleanupError}`);
|
|
15169
|
+
}
|
|
15170
|
+
throw error;
|
|
15171
|
+
}
|
|
15172
|
+
return () => {
|
|
15173
|
+
try {
|
|
15174
|
+
const currentOwner = JSON.parse(readFileSync6(ownerPath, "utf8"));
|
|
15175
|
+
if (currentOwner.pid === owner.pid && currentOwner.token === owner.token) {
|
|
15176
|
+
rmSync2(lockPath, { recursive: true, force: true });
|
|
15177
|
+
}
|
|
15178
|
+
} catch (error) {
|
|
15179
|
+
if (errorCode(error) !== "ENOENT") {
|
|
15180
|
+
warn(`[install-plugin] Could not release install lock ${lockPath}: ${error}`);
|
|
15181
|
+
}
|
|
15182
|
+
}
|
|
15183
|
+
};
|
|
15184
|
+
}
|
|
15185
|
+
function assertAssetFileName(name, field) {
|
|
15186
|
+
if (name === "" || basename3(name) !== name) {
|
|
15187
|
+
throw new Error(`${field} must be a file name, received ${JSON.stringify(name)}`);
|
|
15188
|
+
}
|
|
15189
|
+
}
|
|
15190
|
+
function embeddedPluginValue(source, name, assetName) {
|
|
15191
|
+
const pattern = new RegExp(`\\blocal\\s+${name}\\s*=\\s*"([^"]+)"\\s*;?`, "g");
|
|
15192
|
+
const matches = [...source.matchAll(pattern)];
|
|
15193
|
+
if (matches.length !== 1) {
|
|
15194
|
+
throw new Error(`${assetName} must contain exactly one embedded ${name}; found ${matches.length}.`);
|
|
15195
|
+
}
|
|
15196
|
+
return matches[0][1];
|
|
15197
|
+
}
|
|
15198
|
+
var PLUGIN_SOURCE_CLASSES = {
|
|
15199
|
+
Script: true,
|
|
15200
|
+
ModuleScript: true,
|
|
15201
|
+
LocalScript: true
|
|
15202
|
+
};
|
|
15203
|
+
function parsePluginScriptSources(source, assetName) {
|
|
15204
|
+
if (!isUtf8(source)) {
|
|
15205
|
+
throw new Error(`${assetName} is not valid UTF-8 Roblox XML.`);
|
|
15206
|
+
}
|
|
15207
|
+
const text = source.toString("utf8");
|
|
15208
|
+
const elementStack = [];
|
|
15209
|
+
const scriptSources = [];
|
|
15210
|
+
let rootName;
|
|
15211
|
+
let currentSource;
|
|
15212
|
+
const parser = new import_saxes.SaxesParser({ fragment: false, xmlns: false });
|
|
15213
|
+
parser.on("doctype", () => {
|
|
15214
|
+
throw new Error("DOCTYPE declarations are not allowed in plugin artifacts.");
|
|
15215
|
+
});
|
|
15216
|
+
parser.on("opentag", (tag) => {
|
|
15217
|
+
if (elementStack.length === 0)
|
|
15218
|
+
rootName = tag.name;
|
|
15219
|
+
if (currentSource === void 0 && tag.name === "string" && tag.attributes.name === "Source") {
|
|
15220
|
+
const nearestItem = [...elementStack].reverse().find((element) => element.name === "Item");
|
|
15221
|
+
const sourceClass = nearestItem?.attributes.class;
|
|
15222
|
+
if (sourceClass !== void 0 && PLUGIN_SOURCE_CLASSES[sourceClass] === true) {
|
|
15223
|
+
currentSource = { depth: elementStack.length, text: "" };
|
|
15224
|
+
}
|
|
15225
|
+
}
|
|
15226
|
+
elementStack.push({ name: tag.name, attributes: tag.attributes });
|
|
15227
|
+
});
|
|
15228
|
+
parser.on("text", (value) => {
|
|
15229
|
+
if (currentSource !== void 0)
|
|
15230
|
+
currentSource.text += value;
|
|
15231
|
+
});
|
|
15232
|
+
parser.on("cdata", (value) => {
|
|
15233
|
+
if (currentSource !== void 0)
|
|
15234
|
+
currentSource.text += value;
|
|
15235
|
+
});
|
|
15236
|
+
parser.on("closetag", (tag) => {
|
|
15237
|
+
if (currentSource !== void 0 && tag.name === "string" && elementStack.length === currentSource.depth + 1) {
|
|
15238
|
+
scriptSources.push(currentSource.text);
|
|
15239
|
+
currentSource = void 0;
|
|
15240
|
+
}
|
|
15241
|
+
elementStack.pop();
|
|
15242
|
+
});
|
|
15243
|
+
try {
|
|
15244
|
+
parser.write(text).close();
|
|
15245
|
+
} catch (error) {
|
|
15246
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
15247
|
+
throw new Error(`${assetName} is not well-formed Roblox XML: ${detail}`, {
|
|
15248
|
+
cause: error
|
|
15249
|
+
});
|
|
15250
|
+
}
|
|
15251
|
+
if (rootName !== "roblox" || scriptSources.length === 0) {
|
|
15252
|
+
throw new Error(`${assetName} is not a Roblox XML model containing a Script source.`);
|
|
15253
|
+
}
|
|
15254
|
+
return scriptSources;
|
|
15255
|
+
}
|
|
15256
|
+
function assertPluginAssetIdentity(source, assetName, expectedVersion, expectedVariant) {
|
|
15257
|
+
const pluginSource = parsePluginScriptSources(source, assetName).join("\n");
|
|
15258
|
+
const actualVersion = embeddedPluginValue(pluginSource, "CURRENT_VERSION", assetName);
|
|
15259
|
+
if (actualVersion !== expectedVersion) {
|
|
15260
|
+
throw new Error(`${assetName} embeds version ${actualVersion}; expected ${expectedVersion}.`);
|
|
15261
|
+
}
|
|
15262
|
+
const actualVariant = embeddedPluginValue(pluginSource, "PLUGIN_VARIANT", assetName);
|
|
15263
|
+
if (actualVariant !== expectedVariant) {
|
|
15264
|
+
throw new Error(`${assetName} embeds variant ${actualVariant}; expected ${expectedVariant}.`);
|
|
15265
|
+
}
|
|
15266
|
+
}
|
|
15267
|
+
function installedFileMatches(source, destination) {
|
|
15268
|
+
if (!existsSync6(destination))
|
|
15269
|
+
return false;
|
|
15270
|
+
if (!lstatSync(destination).isFile())
|
|
15271
|
+
return false;
|
|
15272
|
+
const installed = readFileSync6(destination);
|
|
15273
|
+
return installed.length === source.length && installed.equals(source);
|
|
15274
|
+
}
|
|
15275
|
+
function installPluginAsset({ pluginsFolder, assetName, otherAssetName, source, expectedVersion, expectedVariant, rawPort, replaceVariant = true, log = console.log, warn = console.warn }) {
|
|
15276
|
+
assertAssetFileName(assetName, "assetName");
|
|
15277
|
+
assertAssetFileName(otherAssetName, "otherAssetName");
|
|
15278
|
+
if (assetName === otherAssetName) {
|
|
15279
|
+
throw new Error("assetName and otherAssetName must identify different files.");
|
|
15280
|
+
}
|
|
15281
|
+
const configured = configurePluginAssetForPort(source, rawPort);
|
|
15282
|
+
assertPluginAssetIdentity(configured, assetName, expectedVersion, expectedVariant);
|
|
15283
|
+
mkdirSync4(pluginsFolder, { recursive: true });
|
|
15284
|
+
const destination = join5(pluginsFolder, assetName);
|
|
15285
|
+
const releaseLock = acquirePluginInstallLock(pluginsFolder, warn);
|
|
15286
|
+
try {
|
|
15287
|
+
let installed = false;
|
|
15288
|
+
if (!installedFileMatches(configured, destination)) {
|
|
15289
|
+
const staged = join5(pluginsFolder, `.${assetName}.${process.pid}.${randomUUID5()}.tmp`);
|
|
15290
|
+
try {
|
|
15291
|
+
writeFileSync3(staged, configured, { flag: "wx" });
|
|
15292
|
+
renameSync(staged, destination);
|
|
15293
|
+
installed = true;
|
|
15294
|
+
} finally {
|
|
15295
|
+
try {
|
|
15296
|
+
unlinkSync(staged);
|
|
15297
|
+
} catch (error) {
|
|
15298
|
+
if (errorCode(error) !== "ENOENT") {
|
|
15299
|
+
warn(`[install-plugin] Could not remove staging file ${staged}: ${error}`);
|
|
15300
|
+
}
|
|
15301
|
+
}
|
|
15302
|
+
}
|
|
15303
|
+
}
|
|
15304
|
+
handleVariantConflict({
|
|
15305
|
+
pluginsFolder,
|
|
15306
|
+
otherAssetName,
|
|
15307
|
+
replace: replaceVariant,
|
|
15308
|
+
log,
|
|
15309
|
+
warn
|
|
15310
|
+
});
|
|
15311
|
+
repairDefaultStudioPluginDirectorySetting(pluginsFolder, log);
|
|
15312
|
+
return { destination, installed };
|
|
15313
|
+
} finally {
|
|
15314
|
+
releaseLock();
|
|
15315
|
+
}
|
|
15316
|
+
}
|
|
12994
15317
|
|
|
12995
15318
|
// src/index.ts
|
|
12996
15319
|
import { createRequire } from "module";
|
|
12997
15320
|
|
|
12998
15321
|
// src/install-plugin.ts
|
|
12999
|
-
import {
|
|
15322
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
13000
15323
|
import { dirname as dirname5, join as join6 } from "path";
|
|
13001
15324
|
import { fileURLToPath } from "url";
|
|
13002
15325
|
import { get } from "https";
|
|
@@ -13005,6 +15328,7 @@ var ASSET_NAME = "MCPInspectorPlugin.rbxmx";
|
|
|
13005
15328
|
var OTHER_VARIANT = "MCPPlugin.rbxmx";
|
|
13006
15329
|
var TIMEOUT_MS = 3e4;
|
|
13007
15330
|
var MAX_REDIRECTS = 5;
|
|
15331
|
+
var MAX_ASSET_BYTES = 64 * 1024 * 1024;
|
|
13008
15332
|
function httpsGet(url) {
|
|
13009
15333
|
return new Promise((resolve5, reject) => {
|
|
13010
15334
|
const req = get(url, { headers: { "User-Agent": "robloxstudio-mcp-inspector" } }, resolve5);
|
|
@@ -13014,36 +15338,33 @@ function httpsGet(url) {
|
|
|
13014
15338
|
});
|
|
13015
15339
|
});
|
|
13016
15340
|
}
|
|
13017
|
-
async function download(url,
|
|
15341
|
+
async function download(url, redirects = 0) {
|
|
13018
15342
|
const res = await httpsGet(url);
|
|
13019
15343
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
13020
15344
|
if (redirects >= MAX_REDIRECTS) throw new Error(`Too many redirects (max ${MAX_REDIRECTS})`);
|
|
13021
15345
|
const location = res.headers.location;
|
|
13022
15346
|
if (!location) throw new Error("Redirect with no location header");
|
|
13023
|
-
|
|
15347
|
+
for await (const _chunk of res) {
|
|
15348
|
+
}
|
|
15349
|
+
return download(location, redirects + 1);
|
|
13024
15350
|
}
|
|
13025
15351
|
if (res.statusCode !== 200) {
|
|
13026
15352
|
throw new Error(`Download failed: HTTP ${res.statusCode}`);
|
|
13027
15353
|
}
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
}
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
resolve5();
|
|
13043
|
-
});
|
|
13044
|
-
file.on("error", cleanup);
|
|
13045
|
-
res.on("error", cleanup);
|
|
13046
|
-
});
|
|
15354
|
+
const chunks = [];
|
|
15355
|
+
let totalBytes = 0;
|
|
15356
|
+
for await (const chunk of res) {
|
|
15357
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
15358
|
+
totalBytes += bytes.length;
|
|
15359
|
+
if (totalBytes > MAX_ASSET_BYTES) {
|
|
15360
|
+
res.destroy();
|
|
15361
|
+
throw new Error(
|
|
15362
|
+
`${ASSET_NAME} download exceeds the ${MAX_ASSET_BYTES}-byte limit.`
|
|
15363
|
+
);
|
|
15364
|
+
}
|
|
15365
|
+
chunks.push(bytes);
|
|
15366
|
+
}
|
|
15367
|
+
return Buffer.concat(chunks, totalBytes);
|
|
13047
15368
|
}
|
|
13048
15369
|
async function fetchJson(url) {
|
|
13049
15370
|
const res = await httpsGet(url);
|
|
@@ -13056,24 +15377,6 @@ async function fetchJson(url) {
|
|
|
13056
15377
|
}
|
|
13057
15378
|
return JSON.parse(Buffer.concat(chunks).toString());
|
|
13058
15379
|
}
|
|
13059
|
-
function prepareInstall({
|
|
13060
|
-
replaceVariant,
|
|
13061
|
-
log,
|
|
13062
|
-
warn
|
|
13063
|
-
}) {
|
|
13064
|
-
const pluginsFolder = getPluginsFolder();
|
|
13065
|
-
if (!existsSync7(pluginsFolder)) {
|
|
13066
|
-
mkdirSync4(pluginsFolder, { recursive: true });
|
|
13067
|
-
}
|
|
13068
|
-
handleVariantConflict({
|
|
13069
|
-
pluginsFolder,
|
|
13070
|
-
otherAssetName: OTHER_VARIANT,
|
|
13071
|
-
replace: replaceVariant,
|
|
13072
|
-
log,
|
|
13073
|
-
warn
|
|
13074
|
-
});
|
|
13075
|
-
return pluginsFolder;
|
|
13076
|
-
}
|
|
13077
15380
|
function bundledAssetPath() {
|
|
13078
15381
|
const currentDir = dirname5(fileURLToPath(import.meta.url));
|
|
13079
15382
|
const candidates = [
|
|
@@ -13088,47 +15391,36 @@ function resolvePluginAssetPath(sourcePath) {
|
|
|
13088
15391
|
}
|
|
13089
15392
|
function packageVersion() {
|
|
13090
15393
|
const currentDir = dirname5(fileURLToPath(import.meta.url));
|
|
13091
|
-
const pkg = JSON.parse(
|
|
15394
|
+
const pkg = JSON.parse(readFileSync7(join6(currentDir, "..", "package.json"), "utf8"));
|
|
13092
15395
|
if (!pkg.version) {
|
|
13093
15396
|
throw new Error("Package version not found");
|
|
13094
15397
|
}
|
|
13095
15398
|
return pkg.version;
|
|
13096
15399
|
}
|
|
13097
|
-
function bundledPluginVersion(source) {
|
|
13098
|
-
const match = readFileSync6(source, "utf8").match(/local CURRENT_VERSION = "([^"]+)"/);
|
|
13099
|
-
return match ? match[1] : null;
|
|
13100
|
-
}
|
|
13101
|
-
function assertBundledPluginVersion(source) {
|
|
13102
|
-
const expected = packageVersion();
|
|
13103
|
-
const actual = bundledPluginVersion(source);
|
|
13104
|
-
if (actual !== expected) {
|
|
13105
|
-
throw new Error(
|
|
13106
|
-
`Bundled ${ASSET_NAME} version ${actual ?? "unknown"} does not match package version ${expected}. Run npm run build:plugin:inspector before starting with --auto-install-plugin.`
|
|
13107
|
-
);
|
|
13108
|
-
}
|
|
13109
|
-
}
|
|
13110
|
-
function filesMatch(expected, actualPath) {
|
|
13111
|
-
if (!existsSync7(actualPath)) return false;
|
|
13112
|
-
const actual = readFileSync6(actualPath);
|
|
13113
|
-
return expected.length === actual.length && expected.equals(actual);
|
|
13114
|
-
}
|
|
13115
15400
|
async function installBundledPlugin(options = {}) {
|
|
13116
15401
|
const log = options.log ?? console.log;
|
|
13117
15402
|
const warn = options.warn ?? console.warn;
|
|
13118
15403
|
const replaceVariant = options.replaceVariant ?? true;
|
|
13119
|
-
const
|
|
13120
|
-
if (!
|
|
15404
|
+
const sourcePath = resolvePluginAssetPath(options.sourcePath);
|
|
15405
|
+
if (!sourcePath) {
|
|
13121
15406
|
throw new Error(
|
|
13122
15407
|
`Bundled ${ASSET_NAME} not found. Run npm run build:plugin:inspector in this worktree first.`
|
|
13123
15408
|
);
|
|
13124
15409
|
}
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
15410
|
+
const result = await installPluginAsset({
|
|
15411
|
+
pluginsFolder: getPluginsFolder(),
|
|
15412
|
+
assetName: ASSET_NAME,
|
|
15413
|
+
otherAssetName: OTHER_VARIANT,
|
|
15414
|
+
source: readFileSync7(sourcePath),
|
|
15415
|
+
expectedVersion: packageVersion(),
|
|
15416
|
+
expectedVariant: "inspector",
|
|
15417
|
+
replaceVariant,
|
|
15418
|
+
log,
|
|
15419
|
+
warn
|
|
15420
|
+
});
|
|
15421
|
+
if (result.installed) {
|
|
15422
|
+
log(`Installed ${ASSET_NAME} to ${result.destination}`);
|
|
15423
|
+
}
|
|
13132
15424
|
}
|
|
13133
15425
|
async function installPlugin(options = {}) {
|
|
13134
15426
|
const replaceVariant = options.replaceVariant ?? true;
|
|
@@ -13138,17 +15430,23 @@ async function installPlugin(options = {}) {
|
|
|
13138
15430
|
if (options.sourcePath !== void 0 && !bundled) {
|
|
13139
15431
|
throw new Error(`Plugin asset not found at explicit path ${options.sourcePath}.`);
|
|
13140
15432
|
}
|
|
13141
|
-
const pluginsFolder = prepareInstall({ replaceVariant, log, warn });
|
|
13142
15433
|
if (bundled) {
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
|
|
15434
|
+
const result2 = await installPluginAsset({
|
|
15435
|
+
pluginsFolder: getPluginsFolder(),
|
|
15436
|
+
assetName: ASSET_NAME,
|
|
15437
|
+
otherAssetName: OTHER_VARIANT,
|
|
15438
|
+
source: readFileSync7(bundled),
|
|
15439
|
+
expectedVersion: packageVersion(),
|
|
15440
|
+
expectedVariant: "inspector",
|
|
15441
|
+
replaceVariant,
|
|
15442
|
+
log,
|
|
15443
|
+
warn
|
|
15444
|
+
});
|
|
15445
|
+
if (result2.installed) {
|
|
15446
|
+
log(`Installed bundled ${ASSET_NAME} to ${result2.destination}`);
|
|
15447
|
+
} else {
|
|
13147
15448
|
log(`${ASSET_NAME} already installed.`);
|
|
13148
|
-
return;
|
|
13149
15449
|
}
|
|
13150
|
-
writeFileSync3(dest2, configured2);
|
|
13151
|
-
log(`Installed bundled ${ASSET_NAME} to ${dest2}`);
|
|
13152
15450
|
return;
|
|
13153
15451
|
}
|
|
13154
15452
|
log("Fetching latest release...");
|
|
@@ -13157,13 +15455,24 @@ async function installPlugin(options = {}) {
|
|
|
13157
15455
|
if (!asset) {
|
|
13158
15456
|
throw new Error(`${ASSET_NAME} not found in release ${release.tag_name}`);
|
|
13159
15457
|
}
|
|
13160
|
-
const dest = join6(pluginsFolder, ASSET_NAME);
|
|
13161
15458
|
log(`Downloading ${ASSET_NAME} from ${release.tag_name}...`);
|
|
13162
|
-
await download(asset.browser_download_url
|
|
13163
|
-
const
|
|
13164
|
-
|
|
13165
|
-
|
|
13166
|
-
|
|
15459
|
+
const downloaded = await download(asset.browser_download_url);
|
|
15460
|
+
const result = await installPluginAsset({
|
|
15461
|
+
pluginsFolder: getPluginsFolder(),
|
|
15462
|
+
assetName: ASSET_NAME,
|
|
15463
|
+
otherAssetName: OTHER_VARIANT,
|
|
15464
|
+
source: downloaded,
|
|
15465
|
+
expectedVersion: release.tag_name.replace(/^v/, ""),
|
|
15466
|
+
expectedVariant: "inspector",
|
|
15467
|
+
replaceVariant,
|
|
15468
|
+
log,
|
|
15469
|
+
warn
|
|
15470
|
+
});
|
|
15471
|
+
if (result.installed) {
|
|
15472
|
+
log(`Installed to ${result.destination}`);
|
|
15473
|
+
} else {
|
|
15474
|
+
log(`${ASSET_NAME} already installed.`);
|
|
15475
|
+
}
|
|
13167
15476
|
}
|
|
13168
15477
|
|
|
13169
15478
|
// src/index.ts
|
|
@@ -13201,3 +15510,32 @@ if (installBundledOnly || installWithFallback) {
|
|
|
13201
15510
|
process.exit(1);
|
|
13202
15511
|
});
|
|
13203
15512
|
}
|
|
15513
|
+
/*! Bundled license information:
|
|
15514
|
+
|
|
15515
|
+
xmlchars/xml/1.0/ed5.js:
|
|
15516
|
+
(**
|
|
15517
|
+
* Character classes and associated utilities for the 5th edition of XML 1.0.
|
|
15518
|
+
*
|
|
15519
|
+
* @author Louis-Dominique Dubeau
|
|
15520
|
+
* @license MIT
|
|
15521
|
+
* @copyright Louis-Dominique Dubeau
|
|
15522
|
+
*)
|
|
15523
|
+
|
|
15524
|
+
xmlchars/xml/1.1/ed2.js:
|
|
15525
|
+
(**
|
|
15526
|
+
* Character classes and associated utilities for the 2nd edition of XML 1.1.
|
|
15527
|
+
*
|
|
15528
|
+
* @author Louis-Dominique Dubeau
|
|
15529
|
+
* @license MIT
|
|
15530
|
+
* @copyright Louis-Dominique Dubeau
|
|
15531
|
+
*)
|
|
15532
|
+
|
|
15533
|
+
xmlchars/xmlns/1.0/ed3.js:
|
|
15534
|
+
(**
|
|
15535
|
+
* Character class utilities for XML NS 1.0 edition 3.
|
|
15536
|
+
*
|
|
15537
|
+
* @author Louis-Dominique Dubeau
|
|
15538
|
+
* @license MIT
|
|
15539
|
+
* @copyright Louis-Dominique Dubeau
|
|
15540
|
+
*)
|
|
15541
|
+
*/
|