@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.33.1-feature.vcdm2.tsup.26 → 0.33.1-feature.vcdm2.tsup.28
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.cjs +43 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +43 -43
- package/dist/index.js.map +1 -1
- package/package.json +19 -19
- package/src/session/OpSession.ts +12 -12
package/dist/index.js
CHANGED
|
@@ -813,7 +813,7 @@ import { CredentialMapper, parseDid } from "@sphereon/ssi-types";
|
|
|
813
813
|
import { v4 } from "uuid";
|
|
814
814
|
import { PEX } from "@sphereon/pex";
|
|
815
815
|
import { Loggers } from "@sphereon/ssi-types";
|
|
816
|
-
var
|
|
816
|
+
var logger = Loggers.DEFAULT.get("sphereon:oid4vp:OpSession");
|
|
817
817
|
var OpSession = class _OpSession {
|
|
818
818
|
static {
|
|
819
819
|
__name(this, "OpSession");
|
|
@@ -879,9 +879,9 @@ var OpSession = class _OpSession {
|
|
|
879
879
|
didPrefix,
|
|
880
880
|
agentMethods
|
|
881
881
|
});
|
|
882
|
-
debug(`RP supports subject syntax types: ${JSON.stringify(this.getSubjectSyntaxTypesSupported())}`);
|
|
882
|
+
logger.debug(`RP supports subject syntax types: ${JSON.stringify(this.getSubjectSyntaxTypesSupported())}`);
|
|
883
883
|
if (rpMethods.dids.length === 0) {
|
|
884
|
-
debug(`RP does not support DIDs. Supported: ${JSON.stringify(this.getSubjectSyntaxTypesSupported())}`);
|
|
884
|
+
logger.debug(`RP does not support DIDs. Supported: ${JSON.stringify(this.getSubjectSyntaxTypesSupported())}`);
|
|
885
885
|
return [];
|
|
886
886
|
}
|
|
887
887
|
let intersection;
|
|
@@ -899,7 +899,7 @@ var OpSession = class _OpSession {
|
|
|
899
899
|
}
|
|
900
900
|
getAgentDIDMethodsSupported(opts) {
|
|
901
901
|
const agentMethods = this.options.supportedDIDMethods?.map((method) => convertDidMethod(method, opts.didPrefix));
|
|
902
|
-
debug(`agent methods: ${JSON.stringify(agentMethods)}`);
|
|
902
|
+
logger.debug(`agent methods: ${JSON.stringify(agentMethods)}`);
|
|
903
903
|
return agentMethods;
|
|
904
904
|
}
|
|
905
905
|
async getSubjectSyntaxTypesSupported() {
|
|
@@ -910,15 +910,15 @@ var OpSession = class _OpSession {
|
|
|
910
910
|
async getRPDIDMethodsSupported(opts) {
|
|
911
911
|
let keyType;
|
|
912
912
|
const agentMethods = (opts.agentMethods ?? this.getAgentDIDMethodsSupported(opts))?.map((method) => convertDidMethod(method, opts.didPrefix)) ?? [];
|
|
913
|
-
debug(`agent methods supported: ${JSON.stringify(agentMethods)}`);
|
|
913
|
+
logger.debug(`agent methods supported: ${JSON.stringify(agentMethods)}`);
|
|
914
914
|
const authReq = await this.getAuthorizationRequest();
|
|
915
915
|
const subjectSyntaxTypesSupported = authReq.registrationMetadataPayload?.subject_syntax_types_supported?.map((method) => convertDidMethod(method, opts.didPrefix)).filter((val) => !val.startsWith("did"));
|
|
916
|
-
debug(`subject syntax types supported in rp method supported: ${JSON.stringify(subjectSyntaxTypesSupported)}`);
|
|
916
|
+
logger.debug(`subject syntax types supported in rp method supported: ${JSON.stringify(subjectSyntaxTypesSupported)}`);
|
|
917
917
|
const aud = await authReq.authorizationRequest.getMergedProperty("aud");
|
|
918
918
|
let rpMethods = [];
|
|
919
919
|
if (aud && aud.startsWith("did:")) {
|
|
920
920
|
const didMethod = convertDidMethod(parseDid(aud).method, opts.didPrefix);
|
|
921
|
-
debug(`aud did method: ${didMethod}`);
|
|
921
|
+
logger.debug(`aud did method: ${didMethod}`);
|
|
922
922
|
if (subjectSyntaxTypesSupported && subjectSyntaxTypesSupported.length > 0 && !subjectSyntaxTypesSupported.includes("did") && !subjectSyntaxTypesSupported.includes(didMethod)) {
|
|
923
923
|
throw Error(`The aud DID method ${didMethod} is not in the supported types ${subjectSyntaxTypesSupported}`);
|
|
924
924
|
}
|
|
@@ -933,7 +933,7 @@ var OpSession = class _OpSession {
|
|
|
933
933
|
const isEBSI = rpMethods.length === 0 && (authReq.issuer?.includes(".ebsi.eu") || (await authReq.authorizationRequest.getMergedProperty("client_id"))?.includes(".ebsi.eu"));
|
|
934
934
|
let codecName = void 0;
|
|
935
935
|
if (isEBSI && (!aud || !aud.startsWith("http"))) {
|
|
936
|
-
debug(`EBSI detected, adding did:key to supported DID methods for RP`);
|
|
936
|
+
logger.debug(`EBSI detected, adding did:key to supported DID methods for RP`);
|
|
937
937
|
const didKeyMethod = convertDidMethod("did:key", opts.didPrefix);
|
|
938
938
|
if (!agentMethods?.includes(didKeyMethod)) {
|
|
939
939
|
throw Error(`EBSI detected, but agent did not support did:key. Please reconfigure agent`);
|
|
@@ -952,13 +952,13 @@ var OpSession = class _OpSession {
|
|
|
952
952
|
}
|
|
953
953
|
async getSupportedIdentifiers(opts) {
|
|
954
954
|
const methods = await this.getSupportedDIDMethods(true);
|
|
955
|
-
debug(`supported DID methods (did: prefix = true): ${JSON.stringify(methods)}`);
|
|
955
|
+
logger.debug(`supported DID methods (did: prefix = true): ${JSON.stringify(methods)}`);
|
|
956
956
|
if (methods.length === 0) {
|
|
957
957
|
throw Error(`No DID methods are supported`);
|
|
958
958
|
}
|
|
959
959
|
const identifiers = await this.context.agent.didManagerFind().then((ids) => ids.filter((id) => methods.includes(id.provider)));
|
|
960
960
|
if (identifiers.length === 0) {
|
|
961
|
-
debug(`No identifiers available in agent supporting methods ${JSON.stringify(methods)}`);
|
|
961
|
+
logger.debug(`No identifiers available in agent supporting methods ${JSON.stringify(methods)}`);
|
|
962
962
|
if (opts?.createInCaseNoDIDFound !== false) {
|
|
963
963
|
const { codecName, keyType } = await this.getRPDIDMethodsSupported({
|
|
964
964
|
didPrefix: true,
|
|
@@ -972,11 +972,11 @@ var OpSession = class _OpSession {
|
|
|
972
972
|
type: keyType
|
|
973
973
|
}
|
|
974
974
|
});
|
|
975
|
-
debug(`Created a new identifier for the SIOP interaction: ${identifier.did}`);
|
|
975
|
+
logger.debug(`Created a new identifier for the SIOP interaction: ${identifier.did}`);
|
|
976
976
|
identifiers.push(identifier);
|
|
977
977
|
}
|
|
978
978
|
}
|
|
979
|
-
debug(`supported identifiers: ${JSON.stringify(identifiers.map((id) => id.did))}`);
|
|
979
|
+
logger.debug(`supported identifiers: ${JSON.stringify(identifiers.map((id) => id.did))}`);
|
|
980
980
|
return identifiers;
|
|
981
981
|
}
|
|
982
982
|
async getSupportedDIDs() {
|
|
@@ -1200,7 +1200,7 @@ var translate = Localization.translate;
|
|
|
1200
1200
|
|
|
1201
1201
|
// src/machine/Siopv2Machine.ts
|
|
1202
1202
|
import { Loggers as Loggers2 } from "@sphereon/ssi-types";
|
|
1203
|
-
var
|
|
1203
|
+
var logger2 = Loggers2.DEFAULT.get(LOGGER_NAMESPACE);
|
|
1204
1204
|
var Siopv2HasNoContactGuard = /* @__PURE__ */ __name((_ctx, _event) => {
|
|
1205
1205
|
const { contact } = _ctx;
|
|
1206
1206
|
return contact === void 0;
|
|
@@ -1530,7 +1530,7 @@ var Siopv2Machine = class {
|
|
|
1530
1530
|
__name(this, "Siopv2Machine");
|
|
1531
1531
|
}
|
|
1532
1532
|
static newInstance(opts) {
|
|
1533
|
-
|
|
1533
|
+
logger2.info("New Siopv2Machine instance");
|
|
1534
1534
|
const interpreter = interpret(createSiopv2Machine(opts).withConfig({
|
|
1535
1535
|
services: {
|
|
1536
1536
|
...opts?.services
|
|
@@ -1558,7 +1558,7 @@ var Siopv2Machine = class {
|
|
|
1558
1558
|
});
|
|
1559
1559
|
}
|
|
1560
1560
|
interpreter.onTransition((snapshot) => {
|
|
1561
|
-
|
|
1561
|
+
logger2.info("onTransition to new state", snapshot.value);
|
|
1562
1562
|
});
|
|
1563
1563
|
return {
|
|
1564
1564
|
interpreter
|
|
@@ -1642,9 +1642,9 @@ function convertToDcqlCredentials(credential, hasher) {
|
|
|
1642
1642
|
__name(convertToDcqlCredentials, "convertToDcqlCredentials");
|
|
1643
1643
|
|
|
1644
1644
|
// src/services/Siopv2MachineService.ts
|
|
1645
|
-
var
|
|
1645
|
+
var logger3 = Loggers3.DEFAULT.get(LOGGER_NAMESPACE);
|
|
1646
1646
|
var createEbsiIdentifier = /* @__PURE__ */ __name(async (agentContext) => {
|
|
1647
|
-
|
|
1647
|
+
logger3.log(`No EBSI key present yet. Creating a new one...`);
|
|
1648
1648
|
const { result: newIdentifier, created } = await getOrCreatePrimaryIdentifier(agentContext, {
|
|
1649
1649
|
method: SupportedDidMethodEnum.DID_KEY,
|
|
1650
1650
|
createOpts: {
|
|
@@ -1654,7 +1654,7 @@ var createEbsiIdentifier = /* @__PURE__ */ __name(async (agentContext) => {
|
|
|
1654
1654
|
}
|
|
1655
1655
|
}
|
|
1656
1656
|
});
|
|
1657
|
-
|
|
1657
|
+
logger3.log(`EBSI key created: ${newIdentifier.did}`);
|
|
1658
1658
|
if (created) {
|
|
1659
1659
|
await agentContext.agent.emit(Siopv2HolderEvent.IDENTIFIER_CREATED, {
|
|
1660
1660
|
result: newIdentifier
|
|
@@ -1684,8 +1684,8 @@ var siopSendAuthorizationResponse = /* @__PURE__ */ __name(async (connectionType
|
|
|
1684
1684
|
});
|
|
1685
1685
|
const request = await session.getAuthorizationRequest();
|
|
1686
1686
|
const aud = await request.authorizationRequest.getMergedProperty("aud");
|
|
1687
|
-
|
|
1688
|
-
|
|
1687
|
+
logger3.debug(`AUD: ${aud}`);
|
|
1688
|
+
logger3.debug(JSON.stringify(request.authorizationRequest));
|
|
1689
1689
|
let presentationsAndDefs;
|
|
1690
1690
|
let presentationSubmission;
|
|
1691
1691
|
if (await session.hasPresentationDefinitions()) {
|
|
@@ -1694,7 +1694,7 @@ var siopSendAuthorizationResponse = /* @__PURE__ */ __name(async (connectionType
|
|
|
1694
1694
|
});
|
|
1695
1695
|
const credentialsAndDefinitions = args.verifiableCredentialsWithDefinition ? args.verifiableCredentialsWithDefinition : await oid4vp.filterCredentialsAgainstAllDefinitions(CredentialRole.HOLDER);
|
|
1696
1696
|
const domain = await request.authorizationRequest.getMergedProperty("client_id") ?? request.issuer ?? (request.versions.includes(SupportedVersion2.JWT_VC_PRESENTATION_PROFILE_v1) ? "https://self-issued.me/v2/openid-vc" : "https://self-issued.me/v2");
|
|
1697
|
-
|
|
1697
|
+
logger3.log(`NONCE: ${session.nonce}, domain: ${domain}`);
|
|
1698
1698
|
const firstUniqueDC = credentialsAndDefinitions[0].credentials[0];
|
|
1699
1699
|
if (typeof firstUniqueDC !== "object" || !("digitalCredential" in firstUniqueDC)) {
|
|
1700
1700
|
return Promise.reject(Error("SiopMachine only supports UniqueDigitalCredentials for now"));
|
|
@@ -1715,7 +1715,7 @@ var siopSendAuthorizationResponse = /* @__PURE__ */ __name(async (connectionType
|
|
|
1715
1715
|
identifier: holder
|
|
1716
1716
|
});
|
|
1717
1717
|
} catch (e) {
|
|
1718
|
-
|
|
1718
|
+
logger3.debug(`Holder DID not found: ${holder}`);
|
|
1719
1719
|
throw e;
|
|
1720
1720
|
}
|
|
1721
1721
|
} else if (isOID4VCIssuerIdentifier2(digitalCredential.kmsKeyRef)) {
|
|
@@ -1748,7 +1748,7 @@ var siopSendAuthorizationResponse = /* @__PURE__ */ __name(async (connectionType
|
|
|
1748
1748
|
if (identifier === void 0 && idOpts2 !== void 0 && await hasEbsiClient(request.authorizationRequest)) {
|
|
1749
1749
|
identifier = await createEbsiIdentifier(agentContext);
|
|
1750
1750
|
}
|
|
1751
|
-
|
|
1751
|
+
logger3.debug(`Identifier`, identifier);
|
|
1752
1752
|
presentationsAndDefs = await oid4vp.createVerifiablePresentations(CredentialRole.HOLDER, credentialsAndDefinitions, {
|
|
1753
1753
|
idOpts: identifier,
|
|
1754
1754
|
proofOpts: {
|
|
@@ -1763,8 +1763,8 @@ var siopSendAuthorizationResponse = /* @__PURE__ */ __name(async (connectionType
|
|
|
1763
1763
|
}
|
|
1764
1764
|
idOpts2 = presentationsAndDefs[0].idOpts;
|
|
1765
1765
|
presentationSubmission = presentationsAndDefs[0].presentationSubmission;
|
|
1766
|
-
|
|
1767
|
-
|
|
1766
|
+
logger3.log(`Definitions and locations:`, JSON.stringify(presentationsAndDefs?.[0]?.verifiablePresentations, null, 2));
|
|
1767
|
+
logger3.log(`Presentation Submission:`, JSON.stringify(presentationSubmission, null, 2));
|
|
1768
1768
|
const mergedVerifiablePresentations = presentationsAndDefs?.flatMap((pd) => pd.verifiablePresentations) || [];
|
|
1769
1769
|
return await session.sendAuthorizationResponse({
|
|
1770
1770
|
...presentationsAndDefs && {
|
|
@@ -1781,7 +1781,7 @@ var siopSendAuthorizationResponse = /* @__PURE__ */ __name(async (connectionType
|
|
|
1781
1781
|
if (args.verifiableCredentialsWithDefinition !== void 0 && args.verifiableCredentialsWithDefinition !== null) {
|
|
1782
1782
|
const vcs = args.verifiableCredentialsWithDefinition.flatMap((vcd) => vcd.credentials);
|
|
1783
1783
|
const domain = await request.authorizationRequest.getMergedProperty("client_id") ?? request.issuer ?? (request.versions.includes(SupportedVersion2.JWT_VC_PRESENTATION_PROFILE_v1) ? "https://self-issued.me/v2/openid-vc" : "https://self-issued.me/v2");
|
|
1784
|
-
|
|
1784
|
+
logger3.debug(`NONCE: ${session.nonce}, domain: ${domain}`);
|
|
1785
1785
|
const firstUniqueDC = vcs[0];
|
|
1786
1786
|
if (typeof firstUniqueDC !== "object" || !("digitalCredential" in firstUniqueDC)) {
|
|
1787
1787
|
return Promise.reject(Error("SiopMachine only supports UniqueDigitalCredentials for now"));
|
|
@@ -1802,7 +1802,7 @@ var siopSendAuthorizationResponse = /* @__PURE__ */ __name(async (connectionType
|
|
|
1802
1802
|
identifier: holder
|
|
1803
1803
|
});
|
|
1804
1804
|
} catch (e) {
|
|
1805
|
-
|
|
1805
|
+
logger3.debug(`Holder DID not found: ${holder}`);
|
|
1806
1806
|
throw e;
|
|
1807
1807
|
}
|
|
1808
1808
|
} else if (isOID4VCIssuerIdentifier2(digitalCredential.kmsKeyRef)) {
|
|
@@ -1857,7 +1857,7 @@ var siopSendAuthorizationResponse = /* @__PURE__ */ __name(async (connectionType
|
|
|
1857
1857
|
}
|
|
1858
1858
|
}
|
|
1859
1859
|
});
|
|
1860
|
-
|
|
1860
|
+
logger3.debug(`Response: `, response);
|
|
1861
1861
|
return response;
|
|
1862
1862
|
}
|
|
1863
1863
|
}
|
|
@@ -1952,7 +1952,7 @@ var translateCorrelationIdToName = /* @__PURE__ */ __name(async (correlationId,
|
|
|
1952
1952
|
}, "translateCorrelationIdToName");
|
|
1953
1953
|
|
|
1954
1954
|
// src/agent/DidAuthSiopOpAuthenticator.ts
|
|
1955
|
-
var
|
|
1955
|
+
var logger4 = Loggers4.DEFAULT.options(LOGGER_NAMESPACE, {}).get(LOGGER_NAMESPACE);
|
|
1956
1956
|
var didAuthSiopOpAuthenticatorMethods = [
|
|
1957
1957
|
"cmGetContacts",
|
|
1958
1958
|
"cmGetContact",
|
|
@@ -2106,7 +2106,7 @@ var DidAuthSiopOpAuthenticator = class {
|
|
|
2106
2106
|
hasher: this.hasher
|
|
2107
2107
|
}
|
|
2108
2108
|
}));
|
|
2109
|
-
|
|
2109
|
+
logger4.debug(`session: ${JSON.stringify(session.id, null, 2)}`);
|
|
2110
2110
|
const verifiedAuthorizationRequest = await session.getAuthorizationRequest();
|
|
2111
2111
|
const clientName = verifiedAuthorizationRequest.registrationMetadataPayload?.client_name;
|
|
2112
2112
|
const url = verifiedAuthorizationRequest.responseURI ?? (args.url.includes("request_uri") ? decodeURIComponent(args.url.split("?request_uri=")[1].trim()) : verifiedAuthorizationRequest.issuer ?? verifiedAuthorizationRequest.registrationMetadataPayload?.client_id);
|
|
@@ -2186,7 +2186,7 @@ var DidAuthSiopOpAuthenticator = class {
|
|
|
2186
2186
|
contactId: contact.id,
|
|
2187
2187
|
identity: addedIdentity
|
|
2188
2188
|
});
|
|
2189
|
-
|
|
2189
|
+
logger4.info(`Contact identity created: ${JSON.stringify(addedIdentity)}`);
|
|
2190
2190
|
}
|
|
2191
2191
|
}
|
|
2192
2192
|
async siopSendResponse(args, context) {
|
|
@@ -2308,7 +2308,7 @@ var DidAuthSiopOpAuthenticator = class {
|
|
|
2308
2308
|
|
|
2309
2309
|
// src/machine/CallbackStateListener.ts
|
|
2310
2310
|
import { Loggers as Loggers5, LogLevel, LogMethod } from "@sphereon/ssi-types";
|
|
2311
|
-
var
|
|
2311
|
+
var logger5 = Loggers5.DEFAULT.options("sphereon:siopv2-oid4vp:op-auth", {
|
|
2312
2312
|
defaultLogLevel: LogLevel.DEBUG,
|
|
2313
2313
|
methods: [
|
|
2314
2314
|
LogMethod.CONSOLE
|
|
@@ -2317,21 +2317,21 @@ var logger4 = Loggers5.DEFAULT.options("sphereon:siopv2-oid4vp:op-auth", {
|
|
|
2317
2317
|
var OID4VPCallbackStateListener = /* @__PURE__ */ __name((callbacks) => {
|
|
2318
2318
|
return async (oid4vciMachine, state) => {
|
|
2319
2319
|
if (state._event.type === "internal") {
|
|
2320
|
-
|
|
2320
|
+
logger5.debug("oid4vpCallbackStateListener: internal event");
|
|
2321
2321
|
return;
|
|
2322
2322
|
}
|
|
2323
|
-
|
|
2323
|
+
logger5.info(`VP state listener state: ${JSON.stringify(state.value)}`);
|
|
2324
2324
|
if (!callbacks || callbacks.size === 0) {
|
|
2325
|
-
|
|
2325
|
+
logger5.info(`VP no callbacks registered for state: ${JSON.stringify(state.value)}`);
|
|
2326
2326
|
return;
|
|
2327
2327
|
}
|
|
2328
2328
|
for (const [stateKey, callback] of callbacks) {
|
|
2329
2329
|
if (state.matches(stateKey)) {
|
|
2330
|
-
|
|
2331
|
-
await callback(oid4vciMachine, state).then(() =>
|
|
2332
|
-
|
|
2330
|
+
logger5.log(`VP state callback for state: ${JSON.stringify(state.value)}, will execute...`);
|
|
2331
|
+
await callback(oid4vciMachine, state).then(() => logger5.log(`VP state callback executed for state: ${JSON.stringify(state.value)}`)).catch((error) => {
|
|
2332
|
+
logger5.error(`VP state callback failed for state: ${JSON.stringify(state.value)}, error: ${JSON.stringify(error?.message)}, ${JSON.stringify(state.event)}`);
|
|
2333
2333
|
if (error.stack) {
|
|
2334
|
-
|
|
2334
|
+
logger5.error(error.stack);
|
|
2335
2335
|
}
|
|
2336
2336
|
});
|
|
2337
2337
|
break;
|
|
@@ -2345,7 +2345,7 @@ import { contextHasPlugin } from "@sphereon/ssi-sdk.agent-config";
|
|
|
2345
2345
|
import { LinkHandlerAdapter } from "@sphereon/ssi-sdk.core";
|
|
2346
2346
|
import { interpreterStartOrResume } from "@sphereon/ssi-sdk.xstate-machine-persistence";
|
|
2347
2347
|
import { Loggers as Loggers6 } from "@sphereon/ssi-types";
|
|
2348
|
-
var
|
|
2348
|
+
var logger6 = Loggers6.DEFAULT.options(LOGGER_NAMESPACE, {}).get(LOGGER_NAMESPACE);
|
|
2349
2349
|
var Siopv2OID4VPLinkHandler = class extends LinkHandlerAdapter {
|
|
2350
2350
|
static {
|
|
2351
2351
|
__name(this, "Siopv2OID4VPLinkHandler");
|
|
@@ -2365,7 +2365,7 @@ var Siopv2OID4VPLinkHandler = class extends LinkHandlerAdapter {
|
|
|
2365
2365
|
this.idOpts = args.idOpts;
|
|
2366
2366
|
}
|
|
2367
2367
|
async handle(url, opts) {
|
|
2368
|
-
|
|
2368
|
+
logger6.debug(`handling SIOP link: ${url}`);
|
|
2369
2369
|
const siopv2Machine = await this.context.agent.siopGetMachineInterpreter({
|
|
2370
2370
|
url,
|
|
2371
2371
|
idOpts: opts?.idOpts ?? this.idOpts,
|
|
@@ -2381,10 +2381,10 @@ var Siopv2OID4VPLinkHandler = class extends LinkHandlerAdapter {
|
|
|
2381
2381
|
singletonCheck: true,
|
|
2382
2382
|
noRegistration: this.noStateMachinePersistence
|
|
2383
2383
|
});
|
|
2384
|
-
|
|
2384
|
+
logger6.debug(`SIOP machine started for link: ${url}`, init);
|
|
2385
2385
|
} else {
|
|
2386
2386
|
interpreter.start(opts?.machineState);
|
|
2387
|
-
|
|
2387
|
+
logger6.debug(`SIOP machine started for link: ${url}`);
|
|
2388
2388
|
}
|
|
2389
2389
|
}
|
|
2390
2390
|
};
|