@koi-design/callkit 1.0.24-beta.26 → 1.0.24-beta.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.d.ts +18 -0
- package/dist/index.global.js +181 -5
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +150 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +150 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -189,15 +189,30 @@ declare const EncryptionMethod: {
|
|
|
189
189
|
INTERNAL: string;
|
|
190
190
|
};
|
|
191
191
|
type EncryptionMethodType = (typeof EncryptionMethod)[keyof typeof EncryptionMethod];
|
|
192
|
+
declare const LogGatherEnum: {
|
|
193
|
+
ENABLE: number;
|
|
194
|
+
DISABLE: number;
|
|
195
|
+
};
|
|
196
|
+
type LogGatherEnumType = (typeof LogGatherEnum)[keyof typeof LogGatherEnum];
|
|
192
197
|
declare const CallSourceType: {
|
|
193
198
|
phoneNum: number;
|
|
194
199
|
workOrderId: number;
|
|
195
200
|
};
|
|
196
201
|
|
|
197
202
|
type LoggerLevel = 'debug' | 'log' | 'warn' | 'error' | 'silent';
|
|
203
|
+
declare const LogDataEnum: {
|
|
204
|
+
readonly SIP: "sip";
|
|
205
|
+
readonly INCALL: "incall";
|
|
206
|
+
readonly AJAX: "ajax";
|
|
207
|
+
readonly ERROR: "error";
|
|
208
|
+
readonly RECONNECT: "reconnect";
|
|
209
|
+
readonly ALL: "all";
|
|
210
|
+
};
|
|
211
|
+
type LogDataType = (typeof LogDataEnum)[keyof typeof LogDataEnum];
|
|
198
212
|
declare class Logger {
|
|
199
213
|
prefix: string;
|
|
200
214
|
level: LoggerLevel;
|
|
215
|
+
logData: Record<LogDataType, any[]>;
|
|
201
216
|
private callKit;
|
|
202
217
|
constructor(callKit: CallKit, level?: LoggerLevel);
|
|
203
218
|
setLevel(level: LoggerLevel): void;
|
|
@@ -205,6 +220,7 @@ declare class Logger {
|
|
|
205
220
|
log(msg: any, extra?: Object): void;
|
|
206
221
|
warn(msg: any, extra?: Object): void;
|
|
207
222
|
error(msg: any, extra?: any): void;
|
|
223
|
+
addLogData(type: LogDataType, data: any): void;
|
|
208
224
|
output(color: string): (msg: any, extra?: Object) => void;
|
|
209
225
|
}
|
|
210
226
|
|
|
@@ -288,6 +304,7 @@ interface IConfig {
|
|
|
288
304
|
iceInfo: string[];
|
|
289
305
|
iceGatheringTimeout: number;
|
|
290
306
|
encryptionMethod: EncryptionMethodType;
|
|
307
|
+
logGather: LogGatherEnumType;
|
|
291
308
|
};
|
|
292
309
|
isAutoUpdateUserStatus: boolean;
|
|
293
310
|
}
|
|
@@ -301,6 +318,7 @@ declare class Config {
|
|
|
301
318
|
reset: () => void;
|
|
302
319
|
validate: () => boolean;
|
|
303
320
|
isLogin: () => boolean;
|
|
321
|
+
isLogGatherEnable: () => boolean;
|
|
304
322
|
check(): boolean;
|
|
305
323
|
}
|
|
306
324
|
|
package/dist/index.global.js
CHANGED
|
@@ -3161,6 +3161,37 @@ var WebCall = (() => {
|
|
|
3161
3161
|
}
|
|
3162
3162
|
});
|
|
3163
3163
|
|
|
3164
|
+
// node_modules/.pnpm/json-stringify-safe@5.0.1/node_modules/json-stringify-safe/stringify.js
|
|
3165
|
+
var require_stringify = __commonJS({
|
|
3166
|
+
"node_modules/.pnpm/json-stringify-safe@5.0.1/node_modules/json-stringify-safe/stringify.js"(exports, module) {
|
|
3167
|
+
exports = module.exports = stringify2;
|
|
3168
|
+
exports.getSerialize = serializer;
|
|
3169
|
+
function stringify2(obj, replacer, spaces, cycleReplacer) {
|
|
3170
|
+
return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces);
|
|
3171
|
+
}
|
|
3172
|
+
function serializer(replacer, cycleReplacer) {
|
|
3173
|
+
var stack = [], keys = [];
|
|
3174
|
+
if (cycleReplacer == null)
|
|
3175
|
+
cycleReplacer = function(key, value) {
|
|
3176
|
+
if (stack[0] === value)
|
|
3177
|
+
return "[Circular ~]";
|
|
3178
|
+
return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]";
|
|
3179
|
+
};
|
|
3180
|
+
return function(key, value) {
|
|
3181
|
+
if (stack.length > 0) {
|
|
3182
|
+
var thisPos = stack.indexOf(this);
|
|
3183
|
+
~thisPos ? stack.splice(thisPos + 1) : stack.push(this);
|
|
3184
|
+
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key);
|
|
3185
|
+
if (~stack.indexOf(value))
|
|
3186
|
+
value = cycleReplacer.call(this, key, value);
|
|
3187
|
+
} else
|
|
3188
|
+
stack.push(value);
|
|
3189
|
+
return replacer == null ? value : replacer.call(this, key, value);
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
});
|
|
3194
|
+
|
|
3164
3195
|
// package/index.ts
|
|
3165
3196
|
var package_exports = {};
|
|
3166
3197
|
__export(package_exports, {
|
|
@@ -3578,6 +3609,10 @@ var WebCall = (() => {
|
|
|
3578
3609
|
NONE: "NONE",
|
|
3579
3610
|
INTERNAL: "INTERNAL"
|
|
3580
3611
|
};
|
|
3612
|
+
var LogGatherEnum = {
|
|
3613
|
+
ENABLE: 1,
|
|
3614
|
+
DISABLE: 2
|
|
3615
|
+
};
|
|
3581
3616
|
var constrainsDefault = {
|
|
3582
3617
|
audio: {
|
|
3583
3618
|
autoGainControl: true,
|
|
@@ -3597,9 +3632,11 @@ var WebCall = (() => {
|
|
|
3597
3632
|
SIP_CONNECT_ERROR: "SIP_CONNECT_ERROR",
|
|
3598
3633
|
SIP_RECONNECT_ERROR: "SIP_RECONNECT_ERROR",
|
|
3599
3634
|
SIP_RECONNECT_SUCCESS: "SIP_RECONNECT_SUCCESS",
|
|
3635
|
+
SIP_RECONNECTING: "SIP_RECONNECTING",
|
|
3600
3636
|
INCALL_CONNECT_ERROR: "INCALL_CONNECT_ERROR",
|
|
3601
3637
|
INCALL_RECONNECT_ERROR: "INCALL_RECONNECT_ERROR",
|
|
3602
3638
|
INCALL_RECONNECT_SUCCESS: "INCALL_RECONNECT_SUCCESS",
|
|
3639
|
+
INCALL_RECONNECTING: "INCALL_RECONNECTING",
|
|
3603
3640
|
OPTIONS_HEARTBEAT_EXPIRED: "OPTIONS_HEARTBEAT_EXPIRED",
|
|
3604
3641
|
USER_AGENT_START_ERROR: "USER_AGENT_START_ERROR"
|
|
3605
3642
|
};
|
|
@@ -3723,7 +3760,8 @@ var WebCall = (() => {
|
|
|
3723
3760
|
fsPort: "",
|
|
3724
3761
|
iceInfo: [],
|
|
3725
3762
|
iceGatheringTimeout: 0,
|
|
3726
|
-
encryptionMethod: EncryptionMethod.INTERNAL
|
|
3763
|
+
encryptionMethod: EncryptionMethod.INTERNAL,
|
|
3764
|
+
logGather: LogGatherEnum.DISABLE
|
|
3727
3765
|
},
|
|
3728
3766
|
// EXECUTE setUserStatus
|
|
3729
3767
|
isAutoUpdateUserStatus: true
|
|
@@ -3755,7 +3793,8 @@ var WebCall = (() => {
|
|
|
3755
3793
|
fsPort: "",
|
|
3756
3794
|
iceInfo: [],
|
|
3757
3795
|
iceGatheringTimeout: this.config.userInfo.iceGatheringTimeout,
|
|
3758
|
-
encryptionMethod: EncryptionMethod.INTERNAL
|
|
3796
|
+
encryptionMethod: EncryptionMethod.INTERNAL,
|
|
3797
|
+
logGather: LogGatherEnum.DISABLE
|
|
3759
3798
|
};
|
|
3760
3799
|
this.callKit.trigger(KitEvent.KIT_LOGIN_CHANGE, false);
|
|
3761
3800
|
}
|
|
@@ -3768,6 +3807,7 @@ var WebCall = (() => {
|
|
|
3768
3807
|
return true;
|
|
3769
3808
|
};
|
|
3770
3809
|
isLogin = () => this.validate();
|
|
3810
|
+
isLogGatherEnable = () => this.config.userInfo.logGather === LogGatherEnum.ENABLE;
|
|
3771
3811
|
check() {
|
|
3772
3812
|
if (!this.isLogin()) {
|
|
3773
3813
|
this.callKit.logger.error("User not logged in", {
|
|
@@ -3780,16 +3820,41 @@ var WebCall = (() => {
|
|
|
3780
3820
|
};
|
|
3781
3821
|
|
|
3782
3822
|
// package/logger.ts
|
|
3823
|
+
var import_json_stringify_safe = __toESM(require_stringify());
|
|
3824
|
+
var LogDataEnum = {
|
|
3825
|
+
SIP: "sip",
|
|
3826
|
+
INCALL: "incall",
|
|
3827
|
+
AJAX: "ajax",
|
|
3828
|
+
ERROR: "error",
|
|
3829
|
+
RECONNECT: "reconnect",
|
|
3830
|
+
ALL: "all"
|
|
3831
|
+
};
|
|
3783
3832
|
function getLevel(level) {
|
|
3784
3833
|
return LoggerLevelMap[level];
|
|
3785
3834
|
}
|
|
3786
3835
|
var Logger = class {
|
|
3787
3836
|
prefix = "CallKit";
|
|
3788
3837
|
level = "debug";
|
|
3838
|
+
logData = {
|
|
3839
|
+
sip: [],
|
|
3840
|
+
incall: [],
|
|
3841
|
+
ajax: [],
|
|
3842
|
+
error: [],
|
|
3843
|
+
reconnect: [],
|
|
3844
|
+
all: []
|
|
3845
|
+
};
|
|
3789
3846
|
callKit;
|
|
3790
3847
|
constructor(callKit, level) {
|
|
3791
3848
|
this.callKit = callKit;
|
|
3792
3849
|
this.level = level || "debug";
|
|
3850
|
+
this.logData = {
|
|
3851
|
+
sip: [],
|
|
3852
|
+
incall: [],
|
|
3853
|
+
ajax: [],
|
|
3854
|
+
error: [],
|
|
3855
|
+
reconnect: [],
|
|
3856
|
+
all: []
|
|
3857
|
+
};
|
|
3793
3858
|
}
|
|
3794
3859
|
setLevel(level) {
|
|
3795
3860
|
this.level = level;
|
|
@@ -3828,6 +3893,60 @@ var WebCall = (() => {
|
|
|
3828
3893
|
error.data = rest;
|
|
3829
3894
|
throw error;
|
|
3830
3895
|
}
|
|
3896
|
+
addLogData(type, data) {
|
|
3897
|
+
if (!this.callKit.config.isLogGatherEnable()) {
|
|
3898
|
+
return;
|
|
3899
|
+
}
|
|
3900
|
+
let logData = {
|
|
3901
|
+
type,
|
|
3902
|
+
timestamp: (/* @__PURE__ */ new Date()).valueOf(),
|
|
3903
|
+
time: (/* @__PURE__ */ new Date()).toLocaleString(),
|
|
3904
|
+
data: ""
|
|
3905
|
+
};
|
|
3906
|
+
if (type === LogDataEnum.INCALL) {
|
|
3907
|
+
logData.data = (0, import_json_stringify_safe.default)({
|
|
3908
|
+
// customField
|
|
3909
|
+
callKitSocketFrom: data?.callKitSocketFrom,
|
|
3910
|
+
type: data?.ev.type || data?.type,
|
|
3911
|
+
data: data?.ev.data || data?.data,
|
|
3912
|
+
// message
|
|
3913
|
+
origin: data?.ev.origin || data?.origin,
|
|
3914
|
+
lastEventId: data?.ev.lastEventId || data?.lastEventId,
|
|
3915
|
+
isTrusted: data?.ev.isTrusted || data?.isTrusted,
|
|
3916
|
+
// close
|
|
3917
|
+
code: data?.ev.code || data?.code,
|
|
3918
|
+
reason: data?.ev.reason || data?.reason,
|
|
3919
|
+
wasClean: data?.ev.wasClean || data?.wasClean
|
|
3920
|
+
});
|
|
3921
|
+
} else {
|
|
3922
|
+
logData = (0, import_json_stringify_safe.default)(data);
|
|
3923
|
+
}
|
|
3924
|
+
switch (type) {
|
|
3925
|
+
case LogDataEnum.SIP:
|
|
3926
|
+
this.logData.sip.push(logData);
|
|
3927
|
+
this.logData.all.push(logData);
|
|
3928
|
+
break;
|
|
3929
|
+
case LogDataEnum.INCALL:
|
|
3930
|
+
this.logData.incall.push(logData);
|
|
3931
|
+
this.logData.all.push(logData);
|
|
3932
|
+
break;
|
|
3933
|
+
case LogDataEnum.AJAX:
|
|
3934
|
+
this.logData.ajax.push(logData);
|
|
3935
|
+
this.logData.all.push(logData);
|
|
3936
|
+
break;
|
|
3937
|
+
case LogDataEnum.RECONNECT:
|
|
3938
|
+
this.logData.reconnect.push(logData);
|
|
3939
|
+
this.logData.all.push(logData);
|
|
3940
|
+
break;
|
|
3941
|
+
case LogDataEnum.ERROR:
|
|
3942
|
+
this.logData.error.push(logData);
|
|
3943
|
+
this.logData.all.push(logData);
|
|
3944
|
+
break;
|
|
3945
|
+
default:
|
|
3946
|
+
this.logData.all.push(logData);
|
|
3947
|
+
break;
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3831
3950
|
output(color) {
|
|
3832
3951
|
return (msg, extra = {}) => {
|
|
3833
3952
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -18520,6 +18639,11 @@ var WebCall = (() => {
|
|
|
18520
18639
|
lastOptionsUpdateTime: that.lastOptionsUpdateTime,
|
|
18521
18640
|
now
|
|
18522
18641
|
});
|
|
18642
|
+
that.callKit.logger.addLogData(LogDataEnum.RECONNECT, {
|
|
18643
|
+
event: ConnectEvent.OPTIONS_HEARTBEAT_EXPIRED,
|
|
18644
|
+
lastOptionsUpdateTime: that.lastOptionsUpdateTime,
|
|
18645
|
+
now
|
|
18646
|
+
});
|
|
18523
18647
|
that.clearObserveOptionsHeartbeatInterval();
|
|
18524
18648
|
}
|
|
18525
18649
|
}
|
|
@@ -18531,6 +18655,7 @@ var WebCall = (() => {
|
|
|
18531
18655
|
if (request2.method === "OPTIONS") {
|
|
18532
18656
|
that.lastOptionsUpdateTime = (/* @__PURE__ */ new Date()).getTime();
|
|
18533
18657
|
}
|
|
18658
|
+
that.callKit.logger.addLogData(LogDataEnum.SIP, request2);
|
|
18534
18659
|
return originalReceiveRequest(request2);
|
|
18535
18660
|
};
|
|
18536
18661
|
};
|
|
@@ -18679,7 +18804,7 @@ var WebCall = (() => {
|
|
|
18679
18804
|
await this.unregister();
|
|
18680
18805
|
await this.stop();
|
|
18681
18806
|
await this.register();
|
|
18682
|
-
this.callKit.logger.debug(
|
|
18807
|
+
this.callKit.logger.debug(ConnectEvent.SIP_RECONNECT_SUCCESS);
|
|
18683
18808
|
this.callKit.trigger(KitEvent.CONNECT_EVENT, {
|
|
18684
18809
|
event: ConnectEvent.SIP_RECONNECT_SUCCESS
|
|
18685
18810
|
});
|
|
@@ -18961,12 +19086,21 @@ var WebCall = (() => {
|
|
|
18961
19086
|
"socket reconnect times",
|
|
18962
19087
|
this.socketConfig.maxAttempts
|
|
18963
19088
|
);
|
|
19089
|
+
this.callKit.logger.addLogData(LogDataEnum.RECONNECT, {
|
|
19090
|
+
event: ConnectEvent.INCALL_RECONNECTING,
|
|
19091
|
+
attempts: this.reconnectAttempts,
|
|
19092
|
+
err: ev
|
|
19093
|
+
});
|
|
18964
19094
|
this.attemptReconnect();
|
|
18965
19095
|
} else if (this.reconnectAttempts >= this.socketConfig.maxAttempts) {
|
|
18966
19096
|
this.callKit.trigger(KitEvent.CONNECT_EVENT, {
|
|
18967
19097
|
event: ConnectEvent.INCALL_RECONNECT_ERROR,
|
|
18968
19098
|
err: ev
|
|
18969
19099
|
});
|
|
19100
|
+
this.callKit.logger.addLogData(LogDataEnum.RECONNECT, {
|
|
19101
|
+
event: ConnectEvent.INCALL_RECONNECT_ERROR,
|
|
19102
|
+
err: ev
|
|
19103
|
+
});
|
|
18970
19104
|
this.reset();
|
|
18971
19105
|
this.callKit.logger.error(
|
|
18972
19106
|
"Reconnection failed, maximum retry attempts reached",
|
|
@@ -18985,6 +19119,10 @@ var WebCall = (() => {
|
|
|
18985
19119
|
}
|
|
18986
19120
|
onOpen(ev) {
|
|
18987
19121
|
this.callKit.logger.debug("socket onOpen", ev);
|
|
19122
|
+
this.callKit.logger.addLogData(LogDataEnum.INCALL, {
|
|
19123
|
+
callKitSocketFrom: "onOpen",
|
|
19124
|
+
ev
|
|
19125
|
+
});
|
|
18988
19126
|
this.isConnected = true;
|
|
18989
19127
|
this.lastPingTime = Date.now();
|
|
18990
19128
|
this.checkPing();
|
|
@@ -19003,11 +19141,19 @@ var WebCall = (() => {
|
|
|
19003
19141
|
}
|
|
19004
19142
|
onClose(ev) {
|
|
19005
19143
|
this.callKit.logger.debug("socket onClose", ev);
|
|
19144
|
+
this.callKit.logger.addLogData(LogDataEnum.INCALL, {
|
|
19145
|
+
callKitSocketFrom: "onClose",
|
|
19146
|
+
ev
|
|
19147
|
+
});
|
|
19006
19148
|
if ((ev.code !== 1e3 || !ev.wasClean) && this.callKit.connect.isRegistered()) {
|
|
19007
19149
|
this.callKit.trigger(KitEvent.CONNECT_EVENT, {
|
|
19008
19150
|
event: ConnectEvent.INCALL_CONNECT_ERROR,
|
|
19009
19151
|
err: ev
|
|
19010
19152
|
});
|
|
19153
|
+
this.callKit.logger.addLogData(LogDataEnum.RECONNECT, {
|
|
19154
|
+
event: ConnectEvent.INCALL_CONNECT_ERROR,
|
|
19155
|
+
err: ev
|
|
19156
|
+
});
|
|
19011
19157
|
this.reconnect(ev);
|
|
19012
19158
|
} else {
|
|
19013
19159
|
this.isConnected = false;
|
|
@@ -19025,6 +19171,10 @@ var WebCall = (() => {
|
|
|
19025
19171
|
errCode: ErrorCode.SOCKET_CONNECT_ERROR,
|
|
19026
19172
|
data: ev
|
|
19027
19173
|
});
|
|
19174
|
+
this.callKit.logger.addLogData(LogDataEnum.INCALL, {
|
|
19175
|
+
callKitSocketFrom: "onError",
|
|
19176
|
+
ev
|
|
19177
|
+
});
|
|
19028
19178
|
if (this.isReconnecting) {
|
|
19029
19179
|
this.attemptReconnect();
|
|
19030
19180
|
}
|
|
@@ -19032,6 +19182,10 @@ var WebCall = (() => {
|
|
|
19032
19182
|
onMessage(ev) {
|
|
19033
19183
|
const data = JSON.parse(ev.data);
|
|
19034
19184
|
this.callKit.logger.debug("socket onMessage", data);
|
|
19185
|
+
this.callKit.logger.addLogData(LogDataEnum.INCALL, {
|
|
19186
|
+
callKitSocketFrom: "onMessage",
|
|
19187
|
+
ev
|
|
19188
|
+
});
|
|
19035
19189
|
if (data.event === SocketReceiveEvent.PONG) {
|
|
19036
19190
|
this.lastPingTime = Date.now();
|
|
19037
19191
|
return;
|
|
@@ -19288,16 +19442,37 @@ var WebCall = (() => {
|
|
|
19288
19442
|
this.callKit.trigger(KitEvent.KIT_USER_STATUS_CHANGE, UserStatus.offline);
|
|
19289
19443
|
return;
|
|
19290
19444
|
}
|
|
19291
|
-
|
|
19445
|
+
const postData = {
|
|
19292
19446
|
agentId,
|
|
19293
19447
|
userStatus: status
|
|
19294
|
-
}
|
|
19448
|
+
};
|
|
19449
|
+
const logData = {
|
|
19450
|
+
postData,
|
|
19451
|
+
url: "/agent/user/changeStatus",
|
|
19452
|
+
status: "send",
|
|
19453
|
+
startTimeStamp: (/* @__PURE__ */ new Date()).valueOf(),
|
|
19454
|
+
startTime: (/* @__PURE__ */ new Date()).toLocaleString()
|
|
19455
|
+
};
|
|
19456
|
+
this.callKit.logger.addLogData(LogDataEnum.AJAX, logData);
|
|
19457
|
+
await this.callKit.api.setUserStatus(postData).then(() => {
|
|
19295
19458
|
this.userStatus = status;
|
|
19296
19459
|
this.callKit.trigger(KitEvent.KIT_USER_STATUS_CHANGE, status);
|
|
19460
|
+
this.callKit.logger.addLogData(LogDataEnum.AJAX, {
|
|
19461
|
+
...logData,
|
|
19462
|
+
status: "success",
|
|
19463
|
+
endTimeStamp: (/* @__PURE__ */ new Date()).valueOf(),
|
|
19464
|
+
endTime: (/* @__PURE__ */ new Date()).toLocaleString()
|
|
19465
|
+
});
|
|
19297
19466
|
}).catch((err) => {
|
|
19298
19467
|
this.callKit.logger.error(err, {
|
|
19299
19468
|
errCode: ErrorCode.API_USER_STATUS_UPDATE_ERROR
|
|
19300
19469
|
});
|
|
19470
|
+
this.callKit.logger.addLogData(LogDataEnum.AJAX, {
|
|
19471
|
+
...logData,
|
|
19472
|
+
status: "error",
|
|
19473
|
+
endTimeStamp: (/* @__PURE__ */ new Date()).valueOf(),
|
|
19474
|
+
endTime: (/* @__PURE__ */ new Date()).toLocaleString()
|
|
19475
|
+
});
|
|
19301
19476
|
});
|
|
19302
19477
|
}
|
|
19303
19478
|
async sendHangUpReason(data) {
|
|
@@ -19390,6 +19565,7 @@ var WebCall = (() => {
|
|
|
19390
19565
|
fsPort: user.fsPort,
|
|
19391
19566
|
iceInfo: user.iceInfo,
|
|
19392
19567
|
iceGatheringTimeout: user.iceGatheringTimeout,
|
|
19568
|
+
logGather: user.logGather,
|
|
19393
19569
|
// encryptionType is in extra
|
|
19394
19570
|
...extra
|
|
19395
19571
|
});
|