@camstack/addon-provider-bosch 0.1.20 → 0.1.22
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/{account-integration-offline-DvuqXgc8.mjs → account-integration-offline-34gaYDfb.mjs} +44 -2
- package/dist/{account-integration-offline-7DUtQrPy.js → account-integration-offline-DZLHsBZF.js} +55 -1
- package/dist/{dist-CYBUFKeW.js → dist-CRlugVId.js} +569 -104
- package/dist/{dist-B75tWQLF.mjs → dist-DGlq0nuG.mjs} +552 -105
- package/dist/home-connect/addon.js +128 -106
- package/dist/home-connect/addon.mjs +128 -106
- package/dist/homecom/addon.js +557 -433
- package/dist/homecom/addon.mjs +557 -433
- package/dist/shc/addon.js +331 -38
- package/dist/shc/addon.mjs +331 -38
- package/i18n/en.json +23 -19
- package/i18n/it.json +23 -19
- package/package.json +4 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dist = require("../dist-
|
|
3
|
-
const require_account_integration_offline = require("../account-integration-offline-
|
|
2
|
+
const require_dist = require("../dist-CRlugVId.js");
|
|
3
|
+
const require_account_integration_offline = require("../account-integration-offline-DZLHsBZF.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_tls = require("node:tls");
|
|
6
6
|
let node_zlib = require("node:zlib");
|
|
@@ -8,21 +8,20 @@ let node_dns_promises = require("node:dns/promises");
|
|
|
8
8
|
//#region src/home-connect/custom-actions.ts
|
|
9
9
|
var homeConnectActions = require_dist.defineCustomActions({
|
|
10
10
|
beginSignIn: require_dist.customAction(require_dist.object({}), require_dist.object({
|
|
11
|
+
attemptId: require_dist.string().min(1),
|
|
11
12
|
authorizeUrl: require_dist.string().min(1),
|
|
12
|
-
verifier: require_dist.string().min(1),
|
|
13
|
-
state: require_dist.string().min(1),
|
|
14
13
|
summary: require_dist.string()
|
|
15
14
|
}), {
|
|
16
15
|
kind: "mutation",
|
|
17
16
|
auth: "admin"
|
|
18
17
|
}),
|
|
19
18
|
finishSignIn: require_dist.customAction(require_dist.object({
|
|
20
|
-
|
|
21
|
-
callback: require_dist.string().min(1)
|
|
22
|
-
state: require_dist.string().min(1)
|
|
19
|
+
attemptId: require_dist.string().min(1),
|
|
20
|
+
callback: require_dist.string().min(1)
|
|
23
21
|
}), require_dist.object({
|
|
24
22
|
token: require_dist.string().min(1).meta({ secret: true }),
|
|
25
23
|
refresh: require_dist.string().optional().meta({ secret: true }),
|
|
24
|
+
accountLabel: require_dist.string(),
|
|
26
25
|
/** JSON of the paired appliances — no credential on it. */
|
|
27
26
|
appliances: require_dist.string().min(1),
|
|
28
27
|
/** JSON of `haId -> { psk, iv64? }`; stored in the keychain, never in `appliances`. */
|
|
@@ -31,6 +30,13 @@ var homeConnectActions = require_dist.defineCustomActions({
|
|
|
31
30
|
}), {
|
|
32
31
|
kind: "mutation",
|
|
33
32
|
auth: "admin"
|
|
33
|
+
}),
|
|
34
|
+
signOut: require_dist.customAction(require_dist.object({}), require_dist.object({
|
|
35
|
+
signedOut: require_dist.literal(true),
|
|
36
|
+
summary: require_dist.string()
|
|
37
|
+
}), {
|
|
38
|
+
kind: "mutation",
|
|
39
|
+
auth: "admin"
|
|
34
40
|
})
|
|
35
41
|
});
|
|
36
42
|
//#endregion
|
|
@@ -602,11 +608,15 @@ var HOME_CONNECT_IDDF_URL = (haId) => `https://eu.services.home-connect.com/api/
|
|
|
602
608
|
//#endregion
|
|
603
609
|
//#region src/home-connect/home-connect-login.ts
|
|
604
610
|
/** Public OAuth client ids from the Home Connect app login (hcpy). */
|
|
605
|
-
var
|
|
606
|
-
var
|
|
611
|
+
var HOME_CONNECT_CLIENT_ID = "9B75AC9EC512F36C84256AC47D813E2C1DD0D6520DF774B020E1E6E2EB29B1F3";
|
|
612
|
+
var HOME_CONNECT_OAUTH_BASE = "https://api.home-connect.com/security/oauth/";
|
|
607
613
|
var ASSET_BASE = "https://eu.services.home-connect.com/";
|
|
608
|
-
var
|
|
609
|
-
var
|
|
614
|
+
var HOME_CONNECT_REDIRECT_URI = "hcauth://auth/prod";
|
|
615
|
+
var HOME_CONNECT_AUTHORIZE_SCOPE = "Control DeleteAppliance IdentifyAppliance Images Monitor ReadAccount ReadOrigApi Settings WriteAppliance WriteOrigApi";
|
|
616
|
+
var APP_ID$1 = HOME_CONNECT_CLIENT_ID;
|
|
617
|
+
var OAUTH_BASE = HOME_CONNECT_OAUTH_BASE;
|
|
618
|
+
var REDIRECT_URI = HOME_CONNECT_REDIRECT_URI;
|
|
619
|
+
var AUTHORIZE_SCOPE = HOME_CONNECT_AUTHORIZE_SCOPE;
|
|
610
620
|
var CookieJar = class {
|
|
611
621
|
#cookies = /* @__PURE__ */ new Map();
|
|
612
622
|
absorb(headers) {
|
|
@@ -812,6 +822,7 @@ async function fetchHomeConnectAccount(creds, fetchImpl = homeConnectLoginFetch)
|
|
|
812
822
|
token,
|
|
813
823
|
refresh,
|
|
814
824
|
appliances,
|
|
825
|
+
accountId: homeConnectAccountLabel(token),
|
|
815
826
|
summary: accountSummary(appliances)
|
|
816
827
|
};
|
|
817
828
|
} catch (err) {
|
|
@@ -824,9 +835,18 @@ async function fetchHomeConnectAccount(creds, fetchImpl = homeConnectLoginFetch)
|
|
|
824
835
|
token: next.token,
|
|
825
836
|
refresh: next.refresh,
|
|
826
837
|
appliances,
|
|
838
|
+
accountId: homeConnectAccountLabel(next.token),
|
|
827
839
|
summary: accountSummary(appliances)
|
|
828
840
|
};
|
|
829
841
|
}
|
|
842
|
+
/** The JWT `sub`, or empty when the token has none. Never throws. */
|
|
843
|
+
function homeConnectAccountLabel(token) {
|
|
844
|
+
try {
|
|
845
|
+
return accountIdFromAccessToken(token);
|
|
846
|
+
} catch {
|
|
847
|
+
return "";
|
|
848
|
+
}
|
|
849
|
+
}
|
|
830
850
|
/**
|
|
831
851
|
* Exchange the refresh token for a new access token. Never throws; a failure says why and
|
|
832
852
|
* carries no credential. A response without a new refresh token keeps the one that was used.
|
|
@@ -949,6 +969,24 @@ async function completeHomeConnectSignIn(input, fetchImpl = homeConnectLoginFetc
|
|
|
949
969
|
}
|
|
950
970
|
return fetchHomeConnectAccount(tokensFromBody(token.body), fetchImpl);
|
|
951
971
|
}
|
|
972
|
+
/** What the form is allowed to see. The verifier stays in `store`. */
|
|
973
|
+
function publishHomeConnectSignIn(store, nowMs) {
|
|
974
|
+
const started = beginHomeConnectSignIn();
|
|
975
|
+
return {
|
|
976
|
+
attemptId: store.put(started.verifier, started.state, nowMs),
|
|
977
|
+
authorizeUrl: started.authorizeUrl
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
async function finishHomeConnectSignIn(input, store, fetchImpl = homeConnectLoginFetch) {
|
|
981
|
+
const taken = store.take(input.attemptId, input.nowMs);
|
|
982
|
+
if (!taken.ok) throw new HomeConnectLoginError(require_account_integration_offline.loginFailureForAttempt(taken.reason));
|
|
983
|
+
return completeHomeConnectSignIn({
|
|
984
|
+
verifier: taken.attempt.verifier,
|
|
985
|
+
callback: input.callback,
|
|
986
|
+
state: taken.attempt.state
|
|
987
|
+
}, fetchImpl);
|
|
988
|
+
}
|
|
989
|
+
var homeConnectSignInAttempts = require_account_integration_offline.createSignInAttemptStore();
|
|
952
990
|
//#endregion
|
|
953
991
|
//#region src/home-connect/home-connect-discovery.ts
|
|
954
992
|
/** Dishwasher accessory label: `Dishwasher` plus the appliance name. */
|
|
@@ -1192,7 +1230,9 @@ var homeConnectStoredApplianceSchema = homeConnectApplianceRowSchema.extend({
|
|
|
1192
1230
|
var homeConnectConfigSchema = require_dist.object({
|
|
1193
1231
|
token: require_dist.string().min(1).describe("Home Connect access token").meta({ secret: true }),
|
|
1194
1232
|
refresh: require_dist.string().optional().describe("Home Connect refresh token").meta({ secret: true }),
|
|
1195
|
-
appliances: require_dist.array(homeConnectStoredApplianceSchema).optional()
|
|
1233
|
+
appliances: require_dist.array(homeConnectStoredApplianceSchema).optional(),
|
|
1234
|
+
/** Shown on the sign-in field. The JWT subject, not a credential. */
|
|
1235
|
+
accountLabel: require_dist.string().optional()
|
|
1196
1236
|
});
|
|
1197
1237
|
function parseJsonValue(raw) {
|
|
1198
1238
|
if (typeof raw !== "string") return raw;
|
|
@@ -1393,103 +1433,49 @@ function sameHomeConnectConnection(a, b) {
|
|
|
1393
1433
|
var HOME_CONNECT_PAIRED_ONLY = "__paired_only__";
|
|
1394
1434
|
function buildHomeConnectFormSchema() {
|
|
1395
1435
|
const pairedOnly = {
|
|
1396
|
-
field: "
|
|
1436
|
+
field: "__hidden__",
|
|
1397
1437
|
equals: HOME_CONNECT_PAIRED_ONLY
|
|
1398
1438
|
};
|
|
1399
1439
|
return { sections: [{
|
|
1400
1440
|
id: "gateway",
|
|
1401
1441
|
title: "Home Connect",
|
|
1402
1442
|
titleKey: "provider-bosch:home-connect.s_gateway.title",
|
|
1403
|
-
description: "
|
|
1443
|
+
description: "Sign in with your Home Connect account. Paste the address the browser stops on (hcauth://auth/prod?code=…).",
|
|
1404
1444
|
descriptionKey: "provider-bosch:home-connect.s_gateway.description",
|
|
1405
1445
|
columns: 1,
|
|
1406
1446
|
fields: [
|
|
1407
1447
|
{
|
|
1408
|
-
type: "
|
|
1448
|
+
type: "account-sign-in",
|
|
1409
1449
|
addonId: "bosch-home-connect",
|
|
1410
|
-
key: "__home-connect-
|
|
1450
|
+
key: "__home-connect-sign-in",
|
|
1411
1451
|
label: "Home Connect",
|
|
1412
|
-
labelKey: "provider-bosch:home-connect.s_gateway.__home-connect-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
label: "Login link",
|
|
1427
|
-
labelKey: "provider-bosch:home-connect.s_gateway.authorizeUrl.label",
|
|
1428
|
-
required: false,
|
|
1429
|
-
omitFromSettings: true
|
|
1430
|
-
},
|
|
1431
|
-
{
|
|
1432
|
-
type: "textarea",
|
|
1433
|
-
key: "callback",
|
|
1434
|
-
label: "Redirect address or code",
|
|
1435
|
-
labelKey: "provider-bosch:home-connect.s_gateway.callback.label",
|
|
1436
|
-
required: false,
|
|
1437
|
-
placeholder: "hcauth://auth/prod?code=…&state=…",
|
|
1438
|
-
omitFromSettings: true
|
|
1439
|
-
},
|
|
1440
|
-
{
|
|
1441
|
-
type: "password",
|
|
1442
|
-
key: "state",
|
|
1443
|
-
label: "Login state",
|
|
1444
|
-
labelKey: "provider-bosch:home-connect.s_gateway.state.label",
|
|
1445
|
-
required: false,
|
|
1446
|
-
omitFromSettings: true,
|
|
1447
|
-
showWhen: pairedOnly
|
|
1448
|
-
},
|
|
1449
|
-
{
|
|
1450
|
-
type: "password",
|
|
1451
|
-
key: "verifier",
|
|
1452
|
-
label: "Login verifier",
|
|
1453
|
-
labelKey: "provider-bosch:home-connect.s_gateway.verifier.label",
|
|
1454
|
-
required: false,
|
|
1455
|
-
omitFromSettings: true,
|
|
1456
|
-
showWhen: pairedOnly
|
|
1457
|
-
},
|
|
1458
|
-
{
|
|
1459
|
-
type: "addon-action-button",
|
|
1460
|
-
addonId: "bosch-home-connect",
|
|
1461
|
-
key: "__home-connect-finish",
|
|
1462
|
-
label: "Finish",
|
|
1463
|
-
labelKey: "provider-bosch:home-connect.s_gateway.__home-connect-finish.label",
|
|
1464
|
-
buttonLabel: "Complete sign-in",
|
|
1465
|
-
action: "finishSignIn",
|
|
1466
|
-
variant: "primary",
|
|
1467
|
-
paramsFromForm: {
|
|
1468
|
-
verifier: "verifier",
|
|
1469
|
-
callback: "callback",
|
|
1470
|
-
state: "state"
|
|
1471
|
-
},
|
|
1452
|
+
labelKey: "provider-bosch:home-connect.s_gateway.__home-connect-sign-in.label",
|
|
1453
|
+
providerLabel: "Home Connect",
|
|
1454
|
+
beginAction: "beginSignIn",
|
|
1455
|
+
completeAction: "finishSignIn",
|
|
1456
|
+
signOutAction: "signOut",
|
|
1457
|
+
accountKey: "accountLabel",
|
|
1458
|
+
countFromKey: "appliances",
|
|
1459
|
+
countNoun: "appliance",
|
|
1460
|
+
pastePlaceholder: "hcauth://auth/prod?code=…&state=…",
|
|
1461
|
+
secretKeys: [
|
|
1462
|
+
"token",
|
|
1463
|
+
"refresh",
|
|
1464
|
+
APPLIANCE_SECRETS_KEY
|
|
1465
|
+
],
|
|
1472
1466
|
assignResult: {
|
|
1473
1467
|
token: "token",
|
|
1474
1468
|
refresh: "refresh",
|
|
1469
|
+
accountLabel: "accountLabel",
|
|
1475
1470
|
appliances: "appliances",
|
|
1476
1471
|
[APPLIANCE_SECRETS_KEY]: APPLIANCE_SECRETS_KEY
|
|
1477
|
-
}
|
|
1478
|
-
successMessage: "{summary}"
|
|
1479
|
-
},
|
|
1480
|
-
{
|
|
1481
|
-
type: "password",
|
|
1482
|
-
key: "token",
|
|
1483
|
-
label: "Access token",
|
|
1484
|
-
labelKey: "provider-bosch:home-connect.s_gateway.token.label",
|
|
1485
|
-
required: true,
|
|
1486
|
-
showWhen: pairedOnly
|
|
1472
|
+
}
|
|
1487
1473
|
},
|
|
1488
1474
|
{
|
|
1489
|
-
type: "
|
|
1490
|
-
key: "
|
|
1491
|
-
label: "
|
|
1492
|
-
labelKey: "provider-bosch:home-connect.s_gateway.
|
|
1475
|
+
type: "text",
|
|
1476
|
+
key: "accountLabel",
|
|
1477
|
+
label: "Account",
|
|
1478
|
+
labelKey: "provider-bosch:home-connect.s_gateway.accountLabel.label",
|
|
1493
1479
|
required: false,
|
|
1494
1480
|
showWhen: pairedOnly
|
|
1495
1481
|
},
|
|
@@ -1498,15 +1484,38 @@ function buildHomeConnectFormSchema() {
|
|
|
1498
1484
|
key: "appliances",
|
|
1499
1485
|
label: "Appliances",
|
|
1500
1486
|
labelKey: "provider-bosch:home-connect.s_gateway.appliances.label",
|
|
1501
|
-
required:
|
|
1487
|
+
required: false,
|
|
1502
1488
|
showWhen: pairedOnly
|
|
1503
1489
|
},
|
|
1504
1490
|
{
|
|
1505
1491
|
type: "password",
|
|
1506
1492
|
key: APPLIANCE_SECRETS_KEY,
|
|
1507
1493
|
label: "Appliance keys",
|
|
1508
|
-
required:
|
|
1494
|
+
required: false,
|
|
1509
1495
|
showWhen: pairedOnly
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
type: "group",
|
|
1499
|
+
key: "__home-connect-advanced",
|
|
1500
|
+
label: "Advanced",
|
|
1501
|
+
labelKey: "provider-bosch:home-connect.s_gateway.__home-connect-advanced.label",
|
|
1502
|
+
style: "accordion",
|
|
1503
|
+
defaultCollapsed: true,
|
|
1504
|
+
fields: [{
|
|
1505
|
+
type: "password",
|
|
1506
|
+
key: "token",
|
|
1507
|
+
label: "Access token",
|
|
1508
|
+
labelKey: "provider-bosch:home-connect.s_gateway.token.label",
|
|
1509
|
+
required: false,
|
|
1510
|
+
showToggle: false
|
|
1511
|
+
}, {
|
|
1512
|
+
type: "password",
|
|
1513
|
+
key: "refresh",
|
|
1514
|
+
label: "Refresh token",
|
|
1515
|
+
labelKey: "provider-bosch:home-connect.s_gateway.refresh.label",
|
|
1516
|
+
required: false,
|
|
1517
|
+
showToggle: false
|
|
1518
|
+
}]
|
|
1510
1519
|
}
|
|
1511
1520
|
]
|
|
1512
1521
|
}] };
|
|
@@ -4612,6 +4621,7 @@ var homeConnectDishwasherSchema = require_dist.object({
|
|
|
4612
4621
|
});
|
|
4613
4622
|
var DISH_CAP = "dishwasher";
|
|
4614
4623
|
var CONSUMABLES_CAP = "consumables";
|
|
4624
|
+
var RUNNING_CAP = "running-state";
|
|
4615
4625
|
var CONSUMABLES_EMPTY = {
|
|
4616
4626
|
items: [],
|
|
4617
4627
|
lastChangedAt: 0,
|
|
@@ -4647,7 +4657,9 @@ var HomeConnectDishwasherDevice = class extends require_dist.BaseDevice {
|
|
|
4647
4657
|
await super.onActivate();
|
|
4648
4658
|
this.registerDishwasherCap();
|
|
4649
4659
|
this.registerConsumablesCap();
|
|
4660
|
+
this.registerRunningCap();
|
|
4650
4661
|
this.runtimeState.setCapState(DISH_CAP, COLD_DISHWASHER_STATUS);
|
|
4662
|
+
this.runtimeState.setCapState(RUNNING_CAP, require_dist.RUNNING_STATE_COLD);
|
|
4651
4663
|
this.markOnline(false);
|
|
4652
4664
|
this.#unwatch = homeConnectConnections.watch(this.#brokerId, this.#haId, (event) => this.recomputeSlice(event));
|
|
4653
4665
|
this.recomputeSlice({ kind: "client" });
|
|
@@ -4677,6 +4689,7 @@ var HomeConnectDishwasherDevice = class extends require_dist.BaseDevice {
|
|
|
4677
4689
|
lastValueAt
|
|
4678
4690
|
});
|
|
4679
4691
|
this.runtimeState.setCapState(DISH_CAP, slice);
|
|
4692
|
+
this.publishRunningSlice(slice);
|
|
4680
4693
|
this.writeConsumables(dishwasherConsumablesFromFeatures(view));
|
|
4681
4694
|
this.markOnline(slice.availability !== "offline");
|
|
4682
4695
|
}
|
|
@@ -4779,6 +4792,15 @@ var HomeConnectDishwasherDevice = class extends require_dist.BaseDevice {
|
|
|
4779
4792
|
setOption: async ({ key, value }) => this.writeKey(this.requireClient(), key, value)
|
|
4780
4793
|
});
|
|
4781
4794
|
}
|
|
4795
|
+
registerRunningCap() {
|
|
4796
|
+
this.ctx.registerNativeCap(require_dist.runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP) ?? require_dist.RUNNING_STATE_COLD });
|
|
4797
|
+
}
|
|
4798
|
+
publishRunningSlice(slice) {
|
|
4799
|
+
const fetchedAt = Date.now();
|
|
4800
|
+
const previous = this.runtimeState.getCapState(RUNNING_CAP);
|
|
4801
|
+
const next = require_dist.runningFromDishwasherOperation(slice.operationState, slice.program, previous, fetchedAt, slice.remainingSeconds);
|
|
4802
|
+
this.runtimeState.setCapState(RUNNING_CAP, next);
|
|
4803
|
+
}
|
|
4782
4804
|
};
|
|
4783
4805
|
//#endregion
|
|
4784
4806
|
//#region src/home-connect/devices/home-connect-event-device.ts
|
|
@@ -5242,21 +5264,16 @@ var BoschHomeConnectProviderAddon = class extends require_dist.BaseDeviceProvide
|
|
|
5242
5264
|
],
|
|
5243
5265
|
customActions: homeConnectActions,
|
|
5244
5266
|
actionHandlers: {
|
|
5245
|
-
beginSignIn: async () => {
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
verifier: started.verifier,
|
|
5250
|
-
state: started.state,
|
|
5251
|
-
summary: "Open the link in the browser and sign in."
|
|
5252
|
-
};
|
|
5253
|
-
},
|
|
5267
|
+
beginSignIn: async () => ({
|
|
5268
|
+
...publishHomeConnectSignIn(homeConnectSignInAttempts, Date.now()),
|
|
5269
|
+
summary: "Open the link in the browser and sign in."
|
|
5270
|
+
}),
|
|
5254
5271
|
finishSignIn: async (input) => {
|
|
5255
|
-
const account = await
|
|
5256
|
-
|
|
5272
|
+
const account = await finishHomeConnectSignIn({
|
|
5273
|
+
attemptId: input.attemptId,
|
|
5257
5274
|
callback: input.callback,
|
|
5258
|
-
|
|
5259
|
-
});
|
|
5275
|
+
nowMs: Date.now()
|
|
5276
|
+
}, homeConnectSignInAttempts);
|
|
5260
5277
|
const { rows, secrets } = splitApplianceSecrets(account.appliances.map((app) => ({
|
|
5261
5278
|
haId: app.haId,
|
|
5262
5279
|
name: app.name,
|
|
@@ -5270,11 +5287,16 @@ var BoschHomeConnectProviderAddon = class extends require_dist.BaseDeviceProvide
|
|
|
5270
5287
|
return {
|
|
5271
5288
|
token: account.token,
|
|
5272
5289
|
...account.refresh.length > 0 ? { refresh: account.refresh } : {},
|
|
5290
|
+
accountLabel: account.accountId,
|
|
5273
5291
|
appliances: JSON.stringify(rows),
|
|
5274
5292
|
applianceSecrets: JSON.stringify(secrets),
|
|
5275
5293
|
summary: account.summary
|
|
5276
5294
|
};
|
|
5277
|
-
}
|
|
5295
|
+
},
|
|
5296
|
+
signOut: async () => ({
|
|
5297
|
+
signedOut: true,
|
|
5298
|
+
summary: "Signed out."
|
|
5299
|
+
})
|
|
5278
5300
|
}
|
|
5279
5301
|
};
|
|
5280
5302
|
}
|