node-opcua-server 2.84.0 → 2.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base_server.js +6 -6
- package/dist/base_server.js.map +1 -1
- package/dist/monitored_item.d.ts +2 -2
- package/dist/monitored_item.js +9 -13
- package/dist/monitored_item.js.map +1 -1
- package/dist/opcua_server.d.ts +1 -1
- package/dist/opcua_server.js +126 -140
- package/dist/opcua_server.js.map +1 -1
- package/dist/sampling_func.d.ts +1 -1
- package/dist/server_capabilities.d.ts +1 -1
- package/dist/server_end_point.d.ts +3 -3
- package/dist/server_end_point.js +4 -4
- package/dist/server_end_point.js.map +1 -1
- package/dist/server_engine.d.ts +3 -3
- package/dist/server_publish_engine.js +22 -22
- package/dist/server_publish_engine.js.map +1 -1
- package/dist/server_session.d.ts +1 -1
- package/dist/server_subscription.d.ts +4 -4
- package/dist/server_subscription.js +26 -26
- package/dist/server_subscription.js.map +1 -1
- package/dist/user_manager.d.ts +3 -3
- package/dist/validate_filter.js +1 -1
- package/dist/validate_filter.js.map +1 -1
- package/package.json +43 -43
- package/source/monitored_item.ts +3 -8
- package/source/opcua_server.ts +68 -82
- package/source/server_end_point.ts +9 -10
- package/source/server_subscription.ts +2 -2
- package/source/validate_filter.ts +1 -1
package/dist/opcua_server.js
CHANGED
|
@@ -484,92 +484,6 @@ const g_requestExactEndpointUrl = !!process.env.NODEOPCUA_SERVER_REQUEST_EXACT_E
|
|
|
484
484
|
*
|
|
485
485
|
*/
|
|
486
486
|
class OPCUAServer extends base_server_1.OPCUABaseServer {
|
|
487
|
-
constructor(options) {
|
|
488
|
-
super(options);
|
|
489
|
-
/**
|
|
490
|
-
* false if anonymous connection are not allowed
|
|
491
|
-
*/
|
|
492
|
-
this.allowAnonymous = false;
|
|
493
|
-
this.allowAnonymous = false;
|
|
494
|
-
options = options || {};
|
|
495
|
-
this.options = options;
|
|
496
|
-
if (options.maxAllowedSessionNumber !== undefined) {
|
|
497
|
-
warningLog("[NODE-OPCUA-W21] maxAllowedSessionNumber property is now deprecated , please use serverCapabilities.maxSessions instead");
|
|
498
|
-
options.serverCapabilities = options.serverCapabilities || {};
|
|
499
|
-
options.serverCapabilities.maxSessions = options.maxAllowedSessionNumber;
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* @property maxConnectionsPerEndpoint
|
|
503
|
-
*/
|
|
504
|
-
this.maxConnectionsPerEndpoint = options.maxConnectionsPerEndpoint || default_maxConnectionsPerEndpoint;
|
|
505
|
-
// build Info
|
|
506
|
-
const buildInfo = Object.assign(Object.assign({}, default_build_info), options.buildInfo);
|
|
507
|
-
// repair product name
|
|
508
|
-
buildInfo.productUri = buildInfo.productUri || this.serverInfo.productUri;
|
|
509
|
-
this.serverInfo.productUri = this.serverInfo.productUri || buildInfo.productUri;
|
|
510
|
-
this.userManager = (0, user_manager_1.makeUserManager)(options.userManager);
|
|
511
|
-
options.allowAnonymous = options.allowAnonymous === undefined ? true : !!options.allowAnonymous;
|
|
512
|
-
/**
|
|
513
|
-
* @property allowAnonymous
|
|
514
|
-
*/
|
|
515
|
-
this.allowAnonymous = options.allowAnonymous;
|
|
516
|
-
this.discoveryServerEndpointUrl = options.discoveryServerEndpointUrl || "opc.tcp://%FQDN%:4840";
|
|
517
|
-
(0, node_opcua_assert_1.assert)(typeof this.discoveryServerEndpointUrl === "string");
|
|
518
|
-
this.serverInfo.applicationType = node_opcua_service_endpoints_1.ApplicationType.Server;
|
|
519
|
-
this.capabilitiesForMDNS = options.capabilitiesForMDNS || ["NA"];
|
|
520
|
-
this.registerServerMethod = options.registerServerMethod || RegisterServerMethod.HIDDEN;
|
|
521
|
-
_installRegisterServerManager(this);
|
|
522
|
-
if (!options.userCertificateManager) {
|
|
523
|
-
this.userCertificateManager = (0, node_opcua_certificate_manager_1.getDefaultCertificateManager)("UserPKI");
|
|
524
|
-
}
|
|
525
|
-
else {
|
|
526
|
-
this.userCertificateManager = options.userCertificateManager;
|
|
527
|
-
}
|
|
528
|
-
// note: we need to delay initialization of endpoint as certain resources
|
|
529
|
-
// such as %FQDN% might not be ready yet at this stage
|
|
530
|
-
this._delayInit = () => __awaiter(this, void 0, void 0, function* () {
|
|
531
|
-
/* istanbul ignore next */
|
|
532
|
-
if (!options) {
|
|
533
|
-
throw new Error("Internal Error");
|
|
534
|
-
}
|
|
535
|
-
// to check => this.serverInfo.applicationName = this.serverInfo.productName || buildInfo.productName;
|
|
536
|
-
// note: applicationUri is handled in a special way
|
|
537
|
-
this.engine = new server_engine_1.ServerEngine({
|
|
538
|
-
applicationUri: () => this.serverInfo.applicationUri,
|
|
539
|
-
buildInfo,
|
|
540
|
-
isAuditing: options.isAuditing,
|
|
541
|
-
serverCapabilities: options.serverCapabilities
|
|
542
|
-
});
|
|
543
|
-
this.objectFactory = new factory_1.Factory(this.engine);
|
|
544
|
-
const endpointDefinitions = options.alternateEndpoints || [];
|
|
545
|
-
const hostname = (0, node_opcua_hostname_1.getFullyQualifiedDomainName)();
|
|
546
|
-
endpointDefinitions.push({
|
|
547
|
-
port: options.port || 26543,
|
|
548
|
-
allowAnonymous: options.allowAnonymous,
|
|
549
|
-
alternateHostname: options.alternateHostname,
|
|
550
|
-
disableDiscovery: options.disableDiscovery,
|
|
551
|
-
hostname: options.hostname || hostname,
|
|
552
|
-
securityModes: options.securityModes,
|
|
553
|
-
securityPolicies: options.securityPolicies
|
|
554
|
-
});
|
|
555
|
-
// todo should self.serverInfo.productUri match self.engine.buildInfo.productUri ?
|
|
556
|
-
for (const endpointOptions of endpointDefinitions) {
|
|
557
|
-
const endPoint = this.createEndpointDescriptions(options, endpointOptions);
|
|
558
|
-
this.endpoints.push(endPoint);
|
|
559
|
-
endPoint.on("message", (message, channel) => {
|
|
560
|
-
this.on_request(message, channel);
|
|
561
|
-
});
|
|
562
|
-
endPoint.on("error", (err) => {
|
|
563
|
-
errorLog("OPCUAServer endpoint error", err);
|
|
564
|
-
// set serverState to ServerState.Failed;
|
|
565
|
-
this.engine.setServerState(node_opcua_common_1.ServerState.Failed);
|
|
566
|
-
this.shutdown(() => {
|
|
567
|
-
/* empty */
|
|
568
|
-
});
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
487
|
/**
|
|
574
488
|
* total number of bytes written by the server since startup
|
|
575
489
|
*/
|
|
@@ -664,6 +578,92 @@ class OPCUAServer extends base_server_1.OPCUABaseServer {
|
|
|
664
578
|
get maxAllowedSessionNumber() {
|
|
665
579
|
return this.engine.serverCapabilities.maxSessions;
|
|
666
580
|
}
|
|
581
|
+
constructor(options) {
|
|
582
|
+
super(options);
|
|
583
|
+
/**
|
|
584
|
+
* false if anonymous connection are not allowed
|
|
585
|
+
*/
|
|
586
|
+
this.allowAnonymous = false;
|
|
587
|
+
this.allowAnonymous = false;
|
|
588
|
+
options = options || {};
|
|
589
|
+
this.options = options;
|
|
590
|
+
if (options.maxAllowedSessionNumber !== undefined) {
|
|
591
|
+
warningLog("[NODE-OPCUA-W21] maxAllowedSessionNumber property is now deprecated , please use serverCapabilities.maxSessions instead");
|
|
592
|
+
options.serverCapabilities = options.serverCapabilities || {};
|
|
593
|
+
options.serverCapabilities.maxSessions = options.maxAllowedSessionNumber;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* @property maxConnectionsPerEndpoint
|
|
597
|
+
*/
|
|
598
|
+
this.maxConnectionsPerEndpoint = options.maxConnectionsPerEndpoint || default_maxConnectionsPerEndpoint;
|
|
599
|
+
// build Info
|
|
600
|
+
const buildInfo = Object.assign(Object.assign({}, default_build_info), options.buildInfo);
|
|
601
|
+
// repair product name
|
|
602
|
+
buildInfo.productUri = buildInfo.productUri || this.serverInfo.productUri;
|
|
603
|
+
this.serverInfo.productUri = this.serverInfo.productUri || buildInfo.productUri;
|
|
604
|
+
this.userManager = (0, user_manager_1.makeUserManager)(options.userManager);
|
|
605
|
+
options.allowAnonymous = options.allowAnonymous === undefined ? true : !!options.allowAnonymous;
|
|
606
|
+
/**
|
|
607
|
+
* @property allowAnonymous
|
|
608
|
+
*/
|
|
609
|
+
this.allowAnonymous = options.allowAnonymous;
|
|
610
|
+
this.discoveryServerEndpointUrl = options.discoveryServerEndpointUrl || "opc.tcp://%FQDN%:4840";
|
|
611
|
+
(0, node_opcua_assert_1.assert)(typeof this.discoveryServerEndpointUrl === "string");
|
|
612
|
+
this.serverInfo.applicationType = node_opcua_service_endpoints_1.ApplicationType.Server;
|
|
613
|
+
this.capabilitiesForMDNS = options.capabilitiesForMDNS || ["NA"];
|
|
614
|
+
this.registerServerMethod = options.registerServerMethod || RegisterServerMethod.HIDDEN;
|
|
615
|
+
_installRegisterServerManager(this);
|
|
616
|
+
if (!options.userCertificateManager) {
|
|
617
|
+
this.userCertificateManager = (0, node_opcua_certificate_manager_1.getDefaultCertificateManager)("UserPKI");
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
this.userCertificateManager = options.userCertificateManager;
|
|
621
|
+
}
|
|
622
|
+
// note: we need to delay initialization of endpoint as certain resources
|
|
623
|
+
// such as %FQDN% might not be ready yet at this stage
|
|
624
|
+
this._delayInit = () => __awaiter(this, void 0, void 0, function* () {
|
|
625
|
+
/* istanbul ignore next */
|
|
626
|
+
if (!options) {
|
|
627
|
+
throw new Error("Internal Error");
|
|
628
|
+
}
|
|
629
|
+
// to check => this.serverInfo.applicationName = this.serverInfo.productName || buildInfo.productName;
|
|
630
|
+
// note: applicationUri is handled in a special way
|
|
631
|
+
this.engine = new server_engine_1.ServerEngine({
|
|
632
|
+
applicationUri: () => this.serverInfo.applicationUri,
|
|
633
|
+
buildInfo,
|
|
634
|
+
isAuditing: options.isAuditing,
|
|
635
|
+
serverCapabilities: options.serverCapabilities
|
|
636
|
+
});
|
|
637
|
+
this.objectFactory = new factory_1.Factory(this.engine);
|
|
638
|
+
const endpointDefinitions = options.alternateEndpoints || [];
|
|
639
|
+
const hostname = (0, node_opcua_hostname_1.getFullyQualifiedDomainName)();
|
|
640
|
+
endpointDefinitions.push({
|
|
641
|
+
port: options.port || 26543,
|
|
642
|
+
allowAnonymous: options.allowAnonymous,
|
|
643
|
+
alternateHostname: options.alternateHostname,
|
|
644
|
+
disableDiscovery: options.disableDiscovery,
|
|
645
|
+
hostname: options.hostname || hostname,
|
|
646
|
+
securityModes: options.securityModes,
|
|
647
|
+
securityPolicies: options.securityPolicies
|
|
648
|
+
});
|
|
649
|
+
// todo should self.serverInfo.productUri match self.engine.buildInfo.productUri ?
|
|
650
|
+
for (const endpointOptions of endpointDefinitions) {
|
|
651
|
+
const endPoint = this.createEndpointDescriptions(options, endpointOptions);
|
|
652
|
+
this.endpoints.push(endPoint);
|
|
653
|
+
endPoint.on("message", (message, channel) => {
|
|
654
|
+
this.on_request(message, channel);
|
|
655
|
+
});
|
|
656
|
+
endPoint.on("error", (err) => {
|
|
657
|
+
errorLog("OPCUAServer endpoint error", err);
|
|
658
|
+
// set serverState to ServerState.Failed;
|
|
659
|
+
this.engine.setServerState(node_opcua_common_1.ServerState.Failed);
|
|
660
|
+
this.shutdown(() => {
|
|
661
|
+
/* empty */
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
667
|
initialize(...args) {
|
|
668
668
|
const done = args[0];
|
|
669
669
|
(0, node_opcua_assert_1.assert)(!this.initialized, "server is already initialized"); // already initialized ?
|
|
@@ -933,16 +933,17 @@ class OPCUAServer extends base_server_1.OPCUABaseServer {
|
|
|
933
933
|
break;
|
|
934
934
|
}
|
|
935
935
|
}
|
|
936
|
-
if (
|
|
937
|
-
node_opcua_status_code_1.StatusCodes.
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
936
|
+
if (certificateStatus &&
|
|
937
|
+
(node_opcua_status_code_1.StatusCodes.BadCertificateUntrusted.equals(certificateStatus) ||
|
|
938
|
+
node_opcua_status_code_1.StatusCodes.BadCertificateTimeInvalid.equals(certificateStatus) ||
|
|
939
|
+
node_opcua_status_code_1.StatusCodes.BadCertificateIssuerTimeInvalid.equals(certificateStatus) ||
|
|
940
|
+
node_opcua_status_code_1.StatusCodes.BadCertificateIssuerUseNotAllowed.equals(certificateStatus) ||
|
|
941
|
+
node_opcua_status_code_1.StatusCodes.BadCertificateIssuerRevocationUnknown.equals(certificateStatus) ||
|
|
942
|
+
node_opcua_status_code_1.StatusCodes.BadCertificateRevocationUnknown.equals(certificateStatus) ||
|
|
943
|
+
node_opcua_status_code_1.StatusCodes.BadCertificateRevoked.equals(certificateStatus) ||
|
|
944
|
+
node_opcua_status_code_1.StatusCodes.BadCertificateUseNotAllowed.equals(certificateStatus) ||
|
|
945
|
+
node_opcua_status_code_1.StatusCodes.BadSecurityChecksFailed.equals(certificateStatus) ||
|
|
946
|
+
!node_opcua_status_code_1.StatusCodes.Good.equals(certificateStatus))) {
|
|
946
947
|
debugLog("isValidX509IdentityToken => certificateStatus = ", certificateStatus === null || certificateStatus === void 0 ? void 0 : certificateStatus.toString());
|
|
947
948
|
return callback(null, node_opcua_status_code_1.StatusCodes.BadIdentityTokenRejected);
|
|
948
949
|
}
|
|
@@ -1271,7 +1272,7 @@ class OPCUAServer extends base_server_1.OPCUABaseServer {
|
|
|
1271
1272
|
const authenticationToken = request.requestHeader.authenticationToken;
|
|
1272
1273
|
const session = this.getSession(authenticationToken);
|
|
1273
1274
|
function rejectConnection(server, statusCode) {
|
|
1274
|
-
if (statusCode
|
|
1275
|
+
if (statusCode.equals(node_opcua_status_code_1.StatusCodes.BadSessionIdInvalid)) {
|
|
1275
1276
|
server.engine.incrementRejectedSessionCount();
|
|
1276
1277
|
}
|
|
1277
1278
|
else {
|
|
@@ -1342,7 +1343,7 @@ class OPCUAServer extends base_server_1.OPCUABaseServer {
|
|
|
1342
1343
|
request.userIdentityToken = request.userIdentityToken || createAnonymousIdentityToken(session.endpoint);
|
|
1343
1344
|
// check request.userIdentityToken is correct ( expected type and correctly formed)
|
|
1344
1345
|
this.isValidUserIdentityToken(channel, session, request.userIdentityToken, request.userTokenSignature, session.endpoint, (err, statusCode) => {
|
|
1345
|
-
if (statusCode
|
|
1346
|
+
if (!statusCode || statusCode.isNotGood()) {
|
|
1346
1347
|
/* istanbul ignore next */
|
|
1347
1348
|
if (!(statusCode && statusCode instanceof node_opcua_status_code_1.StatusCode)) {
|
|
1348
1349
|
return rejectConnection(this, node_opcua_status_code_1.StatusCodes.BadCertificateInvalid);
|
|
@@ -1462,7 +1463,7 @@ class OPCUAServer extends base_server_1.OPCUABaseServer {
|
|
|
1462
1463
|
debugLog(chalk.red.bold(errMessage), chalk.yellow(message.session_statusCode.toString()), response.constructor.name);
|
|
1463
1464
|
return sendResponse(response);
|
|
1464
1465
|
}
|
|
1465
|
-
(0, node_opcua_assert_1.assert)(message.session_statusCode
|
|
1466
|
+
(0, node_opcua_assert_1.assert)(message.session_statusCode.isGood());
|
|
1466
1467
|
// OPC UA Specification 1.02 part 4 page 26
|
|
1467
1468
|
// When a Session is terminated, all outstanding requests on the Session are aborted and
|
|
1468
1469
|
// Bad_SessionClosed StatusCodes are returned to the Client. In addition, the Server deletes the entry
|
|
@@ -1636,34 +1637,23 @@ class OPCUAServer extends base_server_1.OPCUABaseServer {
|
|
|
1636
1637
|
const requestedMaxReferencesPerNode = Math.min(9876, request.requestedMaxReferencesPerNode);
|
|
1637
1638
|
(0, node_opcua_assert_1.assert)(request.nodesToBrowse[0].schema.name === "BrowseDescription");
|
|
1638
1639
|
const context = session.sessionContext;
|
|
1639
|
-
const
|
|
1640
|
-
|
|
1641
|
-
|
|
1640
|
+
const browseAll = (nodesToBrowse, callack) => {
|
|
1641
|
+
const f = (0, util_1.callbackify)(this.engine.browseWithAutomaticExpansion).bind(this.engine);
|
|
1642
|
+
f(request.nodesToBrowse, context, callack);
|
|
1643
|
+
};
|
|
1644
|
+
// handle continuation point and requestedMaxReferencesPerNode
|
|
1645
|
+
const maxBrowseContinuationPoints = this.engine.serverCapabilities.maxBrowseContinuationPoints;
|
|
1646
|
+
(0, node_opcua_address_space_1.innerBrowse)({
|
|
1647
|
+
browseAll,
|
|
1648
|
+
context,
|
|
1649
|
+
continuationPointManager: session.continuationPointManager,
|
|
1650
|
+
requestedMaxReferencesPerNode,
|
|
1651
|
+
maxBrowseContinuationPoints
|
|
1652
|
+
}, request.nodesToBrowse, (err, results) => {
|
|
1642
1653
|
if (!results) {
|
|
1643
|
-
|
|
1654
|
+
return sendError(node_opcua_status_code_1.StatusCodes.BadInternalError);
|
|
1644
1655
|
}
|
|
1645
1656
|
(0, node_opcua_assert_1.assert)(results[0].schema.name === "BrowseResult");
|
|
1646
|
-
// handle continuation point and requestedMaxReferencesPerNode
|
|
1647
|
-
const maxBrowseContinuationPoints = this.engine.serverCapabilities.maxBrowseContinuationPoints;
|
|
1648
|
-
results = results.map((result) => {
|
|
1649
|
-
(0, node_opcua_assert_1.assert)(!result.continuationPoint);
|
|
1650
|
-
// istanbul ignore next
|
|
1651
|
-
if (!session.continuationPointManager) {
|
|
1652
|
-
return new node_opcua_types_1.BrowseResult({ statusCode: node_opcua_status_code_1.StatusCodes.BadNoContinuationPoints });
|
|
1653
|
-
}
|
|
1654
|
-
if (session.continuationPointManager.hasReachedMaximum(maxBrowseContinuationPoints)) {
|
|
1655
|
-
return new node_opcua_types_1.BrowseResult({ statusCode: node_opcua_status_code_1.StatusCodes.BadNoContinuationPoints });
|
|
1656
|
-
}
|
|
1657
|
-
const truncatedResult = session.continuationPointManager.registerReferences(requestedMaxReferencesPerNode, result.references || [], { continuationPoint: null });
|
|
1658
|
-
let { statusCode } = truncatedResult;
|
|
1659
|
-
const { continuationPoint, values } = truncatedResult;
|
|
1660
|
-
statusCode = result.statusCode;
|
|
1661
|
-
return new node_opcua_types_1.BrowseResult({
|
|
1662
|
-
statusCode,
|
|
1663
|
-
continuationPoint,
|
|
1664
|
-
references: values
|
|
1665
|
-
});
|
|
1666
|
-
});
|
|
1667
1657
|
response = new node_opcua_service_browse_1.BrowseResponse({
|
|
1668
1658
|
diagnosticInfos: undefined,
|
|
1669
1659
|
results
|
|
@@ -1685,22 +1675,18 @@ class OPCUAServer extends base_server_1.OPCUABaseServer {
|
|
|
1685
1675
|
if (!request.continuationPoints || request.continuationPoints.length === 0) {
|
|
1686
1676
|
return sendError(node_opcua_status_code_1.StatusCodes.BadNothingToDo);
|
|
1687
1677
|
}
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
const response = new node_opcua_service_browse_1.BrowseNextResponse({
|
|
1700
|
-
diagnosticInfos: undefined,
|
|
1701
|
-
results
|
|
1678
|
+
(0, node_opcua_address_space_1.innerBrowseNext)({
|
|
1679
|
+
continuationPointManager: session.continuationPointManager
|
|
1680
|
+
}, request.continuationPoints, request.releaseContinuationPoints, (err, results) => {
|
|
1681
|
+
if (err) {
|
|
1682
|
+
return sendError(node_opcua_status_code_1.StatusCodes.BadInternalError);
|
|
1683
|
+
}
|
|
1684
|
+
const response = new node_opcua_service_browse_1.BrowseNextResponse({
|
|
1685
|
+
diagnosticInfos: undefined,
|
|
1686
|
+
results
|
|
1687
|
+
});
|
|
1688
|
+
sendResponse(response);
|
|
1702
1689
|
});
|
|
1703
|
-
sendResponse(response);
|
|
1704
1690
|
});
|
|
1705
1691
|
}
|
|
1706
1692
|
// read services
|
|
@@ -2089,7 +2075,7 @@ class OPCUAServer extends base_server_1.OPCUABaseServer {
|
|
|
2089
2075
|
}
|
|
2090
2076
|
}
|
|
2091
2077
|
const { addResults, removeResults, statusCode } = subscription.setTriggering(triggeringItemId, linksToAdd, linksToRemove);
|
|
2092
|
-
if (statusCode
|
|
2078
|
+
if (statusCode.isNotGood()) {
|
|
2093
2079
|
const response = new node_opcua_types_1.ServiceFault({ responseHeader: { serviceResult: statusCode } });
|
|
2094
2080
|
sendResponse(response);
|
|
2095
2081
|
}
|