@wazo/sdk 0.37.35 → 0.37.36

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.
Files changed (94) hide show
  1. package/README.md +1 -0
  2. package/dist/wazo-sdk.js +2 -2
  3. package/esm/__tests__/api-client.test.js +7 -1
  4. package/esm/__tests__/web-rtc-client.test.js +7 -1
  5. package/esm/api/call-logd.js +1 -1
  6. package/esm/api/chatd.js +6 -0
  7. package/esm/api/confd.js +6 -0
  8. package/esm/api-client.js +7 -1
  9. package/esm/checker/Checker.js +7 -1
  10. package/esm/checker/checks/aor.js +7 -1
  11. package/esm/checker/checks/api.js +7 -1
  12. package/esm/checker/checks/index.js +7 -1
  13. package/esm/checker/checks/webrtc-transport.js +7 -1
  14. package/esm/checker/checks/webrtc.js +7 -1
  15. package/esm/domain/AdHocAPIConference.js +11 -1
  16. package/esm/domain/CallLog.js +1 -1
  17. package/esm/domain/CallSession.js +14 -0
  18. package/esm/domain/Features.js +7 -1
  19. package/esm/domain/Phone/CTIPhone.js +21 -1
  20. package/esm/domain/Phone/WebRTCPhone.js +42 -11
  21. package/esm/domain/Profile.js +6 -0
  22. package/esm/domain/__tests__/CallLog.test.js +5 -1
  23. package/esm/domain/__tests__/CallSession.test.js +14 -0
  24. package/esm/domain/__tests__/IndirectTransfer.test.js +14 -0
  25. package/esm/domain/__tests__/Profile.test.js +6 -0
  26. package/esm/domain/__tests__/Room.test.js +14 -0
  27. package/esm/domain/__tests__/Session.test.js +6 -0
  28. package/esm/index.js +59 -18
  29. package/esm/service/CallApi.js +7 -1
  30. package/esm/service/getApiClient.js +7 -1
  31. package/esm/simple/Auth.js +59 -18
  32. package/esm/simple/Configuration.js +59 -18
  33. package/esm/simple/Directory.js +7 -1
  34. package/esm/simple/Phone.js +59 -18
  35. package/esm/simple/index.js +59 -18
  36. package/esm/simple/room/LocalParticipant.js +59 -18
  37. package/esm/simple/room/Participant.js +59 -18
  38. package/esm/simple/room/RemoteParticipant.js +59 -18
  39. package/esm/simple/room/Room.js +59 -18
  40. package/esm/simple/room/SipRoom.js +59 -18
  41. package/esm/simple/room/Stream.js +59 -18
  42. package/esm/simple/room/__tests__/RemoteParticipant.test.js +59 -18
  43. package/esm/simple/utils.js +59 -18
  44. package/esm/web-rtc-client.js +7 -1
  45. package/lib/__tests__/api-client.test.js +7 -1
  46. package/lib/__tests__/web-rtc-client.test.js +7 -1
  47. package/lib/api/call-logd.js +1 -1
  48. package/lib/api/chatd.js +6 -0
  49. package/lib/api/confd.js +6 -0
  50. package/lib/api-client.js +7 -1
  51. package/lib/checker/Checker.js +7 -1
  52. package/lib/checker/checks/aor.js +7 -1
  53. package/lib/checker/checks/api.js +7 -1
  54. package/lib/checker/checks/index.js +7 -1
  55. package/lib/checker/checks/webrtc-transport.js +7 -1
  56. package/lib/checker/checks/webrtc.js +7 -1
  57. package/lib/domain/AdHocAPIConference.js +11 -1
  58. package/lib/domain/CallLog.js +1 -1
  59. package/lib/domain/CallSession.js +14 -0
  60. package/lib/domain/Features.js +7 -1
  61. package/lib/domain/Phone/CTIPhone.js +21 -1
  62. package/lib/domain/Phone/WebRTCPhone.js +42 -11
  63. package/lib/domain/Profile.js +6 -0
  64. package/lib/domain/__tests__/CallLog.test.js +5 -1
  65. package/lib/domain/__tests__/CallSession.test.js +14 -0
  66. package/lib/domain/__tests__/IndirectTransfer.test.js +14 -0
  67. package/lib/domain/__tests__/Profile.test.js +6 -0
  68. package/lib/domain/__tests__/Room.test.js +14 -0
  69. package/lib/domain/__tests__/Session.test.js +6 -0
  70. package/lib/index.js +59 -18
  71. package/lib/service/CallApi.js +7 -1
  72. package/lib/service/getApiClient.js +7 -1
  73. package/lib/simple/Auth.js +59 -18
  74. package/lib/simple/Configuration.js +59 -18
  75. package/lib/simple/Directory.js +7 -1
  76. package/lib/simple/Phone.js +59 -18
  77. package/lib/simple/index.js +59 -18
  78. package/lib/simple/room/LocalParticipant.js +59 -18
  79. package/lib/simple/room/Participant.js +59 -18
  80. package/lib/simple/room/RemoteParticipant.js +59 -18
  81. package/lib/simple/room/Room.js +59 -18
  82. package/lib/simple/room/SipRoom.js +59 -18
  83. package/lib/simple/room/Stream.js +59 -18
  84. package/lib/simple/room/__tests__/RemoteParticipant.test.js +59 -18
  85. package/lib/simple/utils.js +59 -18
  86. package/lib/web-rtc-client.js +7 -1
  87. package/package.json +1 -1
  88. package/src/domain/AdHocAPIConference.js +4 -0
  89. package/src/domain/CallLog.js +1 -1
  90. package/src/domain/CallSession.js +14 -0
  91. package/src/domain/Phone/WebRTCPhone.js +28 -11
  92. package/src/domain/Profile.js +6 -0
  93. package/src/domain/__tests__/CallLog.test.js +4 -0
  94. package/src/simple/Phone.js +6 -6
@@ -59,6 +59,7 @@ var updateFrom = (instance , from ) => {
59
59
 
60
60
 
61
61
 
62
+
62
63
 
63
64
 
64
65
  class CallSession {
@@ -121,6 +122,8 @@ class CallSession {
121
122
 
122
123
 
123
124
 
125
+
126
+
124
127
 
125
128
 
126
129
 
@@ -152,6 +155,7 @@ class CallSession {
152
155
  videoRemotelyDowngraded,
153
156
  sipSession,
154
157
  answerTime,
158
+ conference,
155
159
  } ) {
156
160
  this.callId = callId;
157
161
  this.sipCallId = sipCallId;
@@ -180,6 +184,7 @@ class CallSession {
180
184
  this.videoRemotelyDowngraded = videoRemotelyDowngraded;
181
185
  this.sipSession = sipSession;
182
186
  this.answerTime = answerTime || this.answerTime;
187
+ this.conference = conference;
183
188
 
184
189
  // Useful to compare instead of instanceof with minified code
185
190
  this.type = 'CallSession';
@@ -237,6 +242,10 @@ class CallSession {
237
242
  this.screensharing = false;
238
243
  }
239
244
 
245
+ setIsConference(conference ) {
246
+ this.conference = conference;
247
+ }
248
+
240
249
  isIncoming() {
241
250
  return !this.isCaller && !this.answered;
242
251
  }
@@ -313,6 +322,10 @@ class CallSession {
313
322
  return this.sipStatus === SessionState.Terminated;
314
323
  }
315
324
 
325
+ isConference() {
326
+ return this.conference;
327
+ }
328
+
316
329
  getElapsedTimeInSeconds() {
317
330
  if (!this.startTime) {
318
331
  return 0;
@@ -404,6 +417,7 @@ class CallSession {
404
417
  cameraEnabled: false,
405
418
  dialedExtension: call.dialedExtension,
406
419
  call,
420
+ conference: false, // @FIXME?
407
421
  });
408
422
  }
409
423
  }
@@ -205,6 +205,7 @@ const LINE_STATE = {
205
205
 
206
206
 
207
207
 
208
+
208
209
 
209
210
 
210
211
 
@@ -241,6 +242,7 @@ const LINE_STATE = {
241
242
 
242
243
 
243
244
 
245
+
244
246
 
245
247
 
246
248
 
@@ -272,6 +274,7 @@ class Profile {
272
274
 
273
275
 
274
276
 
277
+
275
278
 
276
279
 
277
280
 
@@ -288,6 +291,7 @@ class Profile {
288
291
  lines: plain.lines.map(line => Line.parse(line)),
289
292
  username: plain.username,
290
293
  mobileNumber: plain.mobile_phone_number || '',
294
+ ringSeconds: plain.ring_seconds,
291
295
  forwards: [
292
296
  ForwardOption.parse(plain.forwards.unconditional, FORWARD_KEYS.UNCONDITIONAL),
293
297
  ForwardOption.parse(plain.forwards.noanswer, FORWARD_KEYS.NO_ANSWER),
@@ -324,6 +328,7 @@ class Profile {
324
328
  switchboards,
325
329
  agent,
326
330
  status,
331
+ ringSeconds,
327
332
  sipLines,
328
333
  callPickupTargetUsers,
329
334
  onlineCallRecordEnabled,
@@ -345,6 +350,7 @@ class Profile {
345
350
  this.status = status;
346
351
  this.callPickupTargetUsers = callPickupTargetUsers;
347
352
  this.onlineCallRecordEnabled = onlineCallRecordEnabled;
353
+ this.ringSeconds = ringSeconds;
348
354
 
349
355
  this.sipLines = sipLines || [];
350
356
  }
package/esm/index.js CHANGED
@@ -1072,6 +1072,7 @@ const LINE_STATE = {
1072
1072
 
1073
1073
 
1074
1074
 
1075
+
1075
1076
 
1076
1077
 
1077
1078
 
@@ -1108,6 +1109,7 @@ const LINE_STATE = {
1108
1109
 
1109
1110
 
1110
1111
 
1112
+
1111
1113
 
1112
1114
 
1113
1115
 
@@ -1139,6 +1141,7 @@ class Profile {
1139
1141
 
1140
1142
 
1141
1143
 
1144
+
1142
1145
 
1143
1146
 
1144
1147
 
@@ -1155,6 +1158,7 @@ class Profile {
1155
1158
  lines: plain.lines.map(line => Line.parse(line)),
1156
1159
  username: plain.username,
1157
1160
  mobileNumber: plain.mobile_phone_number || '',
1161
+ ringSeconds: plain.ring_seconds,
1158
1162
  forwards: [
1159
1163
  ForwardOption.parse(plain.forwards.unconditional, FORWARD_KEYS.UNCONDITIONAL),
1160
1164
  ForwardOption.parse(plain.forwards.noanswer, FORWARD_KEYS.NO_ANSWER),
@@ -1191,6 +1195,7 @@ class Profile {
1191
1195
  switchboards,
1192
1196
  agent,
1193
1197
  status,
1198
+ ringSeconds,
1194
1199
  sipLines,
1195
1200
  callPickupTargetUsers,
1196
1201
  onlineCallRecordEnabled,
@@ -1212,6 +1217,7 @@ class Profile {
1212
1217
  this.status = status;
1213
1218
  this.callPickupTargetUsers = callPickupTargetUsers;
1214
1219
  this.onlineCallRecordEnabled = onlineCallRecordEnabled;
1220
+ this.ringSeconds = ringSeconds;
1215
1221
 
1216
1222
  this.sipLines = sipLines || [];
1217
1223
  }
@@ -3281,6 +3287,7 @@ var updateFrom = (instance , from ) => {
3281
3287
 
3282
3288
 
3283
3289
 
3290
+
3284
3291
 
3285
3292
 
3286
3293
  class CallSession {
@@ -3343,6 +3350,8 @@ class CallSession {
3343
3350
 
3344
3351
 
3345
3352
 
3353
+
3354
+
3346
3355
 
3347
3356
 
3348
3357
 
@@ -3374,6 +3383,7 @@ class CallSession {
3374
3383
  videoRemotelyDowngraded,
3375
3384
  sipSession,
3376
3385
  answerTime,
3386
+ conference,
3377
3387
  } ) {
3378
3388
  this.callId = callId;
3379
3389
  this.sipCallId = sipCallId;
@@ -3402,6 +3412,7 @@ class CallSession {
3402
3412
  this.videoRemotelyDowngraded = videoRemotelyDowngraded;
3403
3413
  this.sipSession = sipSession;
3404
3414
  this.answerTime = answerTime || this.answerTime;
3415
+ this.conference = conference;
3405
3416
 
3406
3417
  // Useful to compare instead of instanceof with minified code
3407
3418
  this.type = 'CallSession';
@@ -3459,6 +3470,10 @@ class CallSession {
3459
3470
  this.screensharing = false;
3460
3471
  }
3461
3472
 
3473
+ setIsConference(conference ) {
3474
+ this.conference = conference;
3475
+ }
3476
+
3462
3477
  isIncoming() {
3463
3478
  return !this.isCaller && !this.answered;
3464
3479
  }
@@ -3535,6 +3550,10 @@ class CallSession {
3535
3550
  return this.sipStatus === SessionState.Terminated;
3536
3551
  }
3537
3552
 
3553
+ isConference() {
3554
+ return this.conference;
3555
+ }
3556
+
3538
3557
  getElapsedTimeInSeconds() {
3539
3558
  if (!this.startTime) {
3540
3559
  return 0;
@@ -3626,6 +3645,7 @@ class CallSession {
3626
3645
  cameraEnabled: false,
3627
3646
  dialedExtension: call.dialedExtension,
3628
3647
  call,
3648
+ conference: false, // @FIXME?
3629
3649
  });
3630
3650
  }
3631
3651
  }
@@ -3721,7 +3741,7 @@ class Recording {
3721
3741
 
3722
3742
 
3723
3743
 
3724
-
3744
+
3725
3745
 
3726
3746
 
3727
3747
 
@@ -10092,7 +10112,11 @@ class WebRTCPhone extends Emitter {
10092
10112
  accept(callSession , cameraEnabled ) {
10093
10113
  logger$7.info('WebRTC phone - accept call', { id: callSession ? callSession.getId() : 'n/a', cameraEnabled });
10094
10114
  if (this.currentSipSession) {
10095
- this.holdSipSession(this.currentSipSession, this.currentCallSession, true);
10115
+ this.holdSipSession(
10116
+ this.currentSipSession,
10117
+ this.currentCallSession,
10118
+ true,
10119
+ );
10096
10120
  }
10097
10121
 
10098
10122
  if (!callSession) {
@@ -10185,22 +10209,22 @@ class WebRTCPhone extends Emitter {
10185
10209
  }
10186
10210
  }
10187
10211
 
10188
- hold(callSession , withEvent = true, isConference = false) {
10212
+ hold(callSession , withEvent = true) {
10189
10213
  logger$7.info('WebRTC hold', { id: callSession.getId() });
10190
10214
 
10191
10215
  const sipSession = this.findSipSession(callSession);
10192
10216
 
10193
- return sipSession ? this.holdSipSession(sipSession, callSession, withEvent, isConference) : null;
10217
+ return sipSession ? this.holdSipSession(sipSession, callSession, withEvent) : null;
10194
10218
  }
10195
10219
 
10196
10220
  // @Deprecated
10197
- unhold(callSession , withEvent = true, isConference = false) {
10221
+ unhold(callSession , withEvent = true) {
10198
10222
  console.warn('Please note that `phone.unhold()` is being deprecated; `phone.resume()` is the preferred method');
10199
10223
 
10200
- return this.resume(callSession, isConference, withEvent);
10224
+ return this.resume(callSession, withEvent);
10201
10225
  }
10202
10226
 
10203
- resume(callSession , isConference = false, withEvent = true) {
10227
+ resume(callSession , withEvent = true) {
10204
10228
  logger$7.info('WebRTC resume called', { id: callSession ? callSession.getId() : null });
10205
10229
 
10206
10230
  const sipSession = this.findSipSession(callSession);
@@ -10213,10 +10237,14 @@ class WebRTCPhone extends Emitter {
10213
10237
  // Hold current session if different from the current one (we don't want 2 sessions active at the same time).
10214
10238
  if (this.currentSipSession && this.currentSipSession.id !== sipSession.id) {
10215
10239
  logger$7.info('WebRTC hold call after resume', { id: this.currentSipSession.id });
10216
- this.holdSipSession(this.currentSipSession, this.currentCallSession);
10240
+ this.holdSipSession(
10241
+ this.currentSipSession,
10242
+ this.currentCallSession,
10243
+ withEvent,
10244
+ );
10217
10245
  }
10218
10246
 
10219
- const promise = this.unholdSipSession(sipSession, callSession, withEvent, isConference);
10247
+ const promise = this.unholdSipSession(sipSession, callSession, withEvent);
10220
10248
  this.currentSipSession = sipSession;
10221
10249
  if (callSession) {
10222
10250
  this.currentCallSession = callSession;
@@ -10229,7 +10257,6 @@ class WebRTCPhone extends Emitter {
10229
10257
  sipSession ,
10230
10258
  callSession ,
10231
10259
  withEvent = true,
10232
- isConference = false,
10233
10260
  ) {
10234
10261
  if (!sipSession) {
10235
10262
  return new Promise((resolve, reject) => reject(new Error('No session to hold')));
@@ -10250,6 +10277,8 @@ class WebRTCPhone extends Emitter {
10250
10277
  await this.client.downgradeToAudio(sipSession);
10251
10278
  }
10252
10279
 
10280
+ const isConference = !!callSession && callSession.isConference();
10281
+
10253
10282
  const promise = this.client.hold(sipSession, isConference, hasVideo);
10254
10283
  if (withEvent) {
10255
10284
  this.eventEmitter.emit(ON_CALL_HELD, this._createCallSession(sipSession, callSession));
@@ -10262,11 +10291,13 @@ class WebRTCPhone extends Emitter {
10262
10291
  sipSession ,
10263
10292
  callSession ,
10264
10293
  withEvent = true,
10265
- isConference = false,
10266
10294
  ) {
10267
10295
  if (!sipSession) {
10268
10296
  return new Promise((resolve, reject) => reject(new Error('No session to unhold')));
10269
10297
  }
10298
+
10299
+ const isConference = !!callSession && callSession.isConference();
10300
+
10270
10301
  const sessionId = this.getSipSessionId(sipSession);
10271
10302
  logger$7.info('WebRTC unhold sip session', { sessionId, isConference });
10272
10303
 
@@ -10408,7 +10439,11 @@ class WebRTCPhone extends Emitter {
10408
10439
  await this.client.register();
10409
10440
  }
10410
10441
  if (this.currentSipSession) {
10411
- this.holdSipSession(this.currentSipSession, this.currentCallSession, true).catch(e => {
10442
+ this.holdSipSession(
10443
+ this.currentSipSession,
10444
+ this.currentCallSession,
10445
+ true,
10446
+ ).catch(e => {
10412
10447
  logger$7.warn('Unable to hold current session when making another call', e);
10413
10448
  });
10414
10449
  }
@@ -10423,6 +10458,7 @@ class WebRTCPhone extends Emitter {
10423
10458
  return Promise.resolve(null);
10424
10459
  }
10425
10460
  const callSession = this._createOutgoingCallSession(sipSession, cameraEnabled || false);
10461
+ callSession.setIsConference(conference);
10426
10462
  callSession.creationTime = new Date();
10427
10463
  this._updateCallSession(callSession);
10428
10464
 
@@ -11001,6 +11037,7 @@ class WebRTCPhone extends Emitter {
11001
11037
  recording: fromSession ? fromSession.isRecording() : false,
11002
11038
  recordingPaused: false, // @TODO
11003
11039
  sipSession,
11040
+ conference: !!fromSession && fromSession.isConference(),
11004
11041
  ...extra,
11005
11042
  });
11006
11043
 
@@ -11990,6 +12027,10 @@ class AdHocAPIConference {
11990
12027
  this.conferenceId = conferenceId || '';
11991
12028
  this.muted = muted || false;
11992
12029
  this.paused = paused || false;
12030
+
12031
+ if (this.host) {
12032
+ this.host.setIsConference(true);
12033
+ }
11993
12034
  }
11994
12035
 
11995
12036
  async start() {
@@ -12284,16 +12325,16 @@ class Phone extends Emitter {
12284
12325
  }
12285
12326
  }
12286
12327
 
12287
- hold(callSession , isConference = false) {
12288
- return this.phone && this.phone.hold(callSession, true, isConference);
12328
+ hold(callSession ) {
12329
+ return this.phone && this.phone.hold(callSession, true);
12289
12330
  }
12290
12331
 
12291
- unhold(callSession , isConference = false) {
12292
- return this.phone && this.phone.unhold(callSession, true, isConference);
12332
+ unhold(callSession ) {
12333
+ return this.phone && this.phone.unhold(callSession, true);
12293
12334
  }
12294
12335
 
12295
- resume(callSession , isConference = false) {
12296
- return this.phone && this.phone.resume(callSession, isConference);
12336
+ resume(callSession ) {
12337
+ return this.phone && this.phone.resume(callSession);
12297
12338
  }
12298
12339
 
12299
12340
  reject(callSession ) {
@@ -558,6 +558,7 @@ const LINE_STATE = {
558
558
 
559
559
 
560
560
 
561
+
561
562
 
562
563
 
563
564
 
@@ -594,6 +595,7 @@ const LINE_STATE = {
594
595
 
595
596
 
596
597
 
598
+
597
599
 
598
600
 
599
601
 
@@ -625,6 +627,7 @@ class Profile {
625
627
 
626
628
 
627
629
 
630
+
628
631
 
629
632
 
630
633
 
@@ -641,6 +644,7 @@ class Profile {
641
644
  lines: plain.lines.map(line => Line.parse(line)),
642
645
  username: plain.username,
643
646
  mobileNumber: plain.mobile_phone_number || '',
647
+ ringSeconds: plain.ring_seconds,
644
648
  forwards: [
645
649
  ForwardOption.parse(plain.forwards.unconditional, FORWARD_KEYS.UNCONDITIONAL),
646
650
  ForwardOption.parse(plain.forwards.noanswer, FORWARD_KEYS.NO_ANSWER),
@@ -677,6 +681,7 @@ class Profile {
677
681
  switchboards,
678
682
  agent,
679
683
  status,
684
+ ringSeconds,
680
685
  sipLines,
681
686
  callPickupTargetUsers,
682
687
  onlineCallRecordEnabled,
@@ -698,6 +703,7 @@ class Profile {
698
703
  this.status = status;
699
704
  this.callPickupTargetUsers = callPickupTargetUsers;
700
705
  this.onlineCallRecordEnabled = onlineCallRecordEnabled;
706
+ this.ringSeconds = ringSeconds;
701
707
 
702
708
  this.sipLines = sipLines || [];
703
709
  }
@@ -1751,7 +1757,7 @@ class Recording {
1751
1757
 
1752
1758
 
1753
1759
 
1754
-
1760
+
1755
1761
 
1756
1762
 
1757
1763
 
@@ -1052,6 +1052,7 @@ const LINE_STATE = {
1052
1052
 
1053
1053
 
1054
1054
 
1055
+
1055
1056
 
1056
1057
 
1057
1058
 
@@ -1088,6 +1089,7 @@ const LINE_STATE = {
1088
1089
 
1089
1090
 
1090
1091
 
1092
+
1091
1093
 
1092
1094
 
1093
1095
 
@@ -1119,6 +1121,7 @@ class Profile {
1119
1121
 
1120
1122
 
1121
1123
 
1124
+
1122
1125
 
1123
1126
 
1124
1127
 
@@ -1135,6 +1138,7 @@ class Profile {
1135
1138
  lines: plain.lines.map(line => Line.parse(line)),
1136
1139
  username: plain.username,
1137
1140
  mobileNumber: plain.mobile_phone_number || '',
1141
+ ringSeconds: plain.ring_seconds,
1138
1142
  forwards: [
1139
1143
  ForwardOption.parse(plain.forwards.unconditional, FORWARD_KEYS.UNCONDITIONAL),
1140
1144
  ForwardOption.parse(plain.forwards.noanswer, FORWARD_KEYS.NO_ANSWER),
@@ -1171,6 +1175,7 @@ class Profile {
1171
1175
  switchboards,
1172
1176
  agent,
1173
1177
  status,
1178
+ ringSeconds,
1174
1179
  sipLines,
1175
1180
  callPickupTargetUsers,
1176
1181
  onlineCallRecordEnabled,
@@ -1192,6 +1197,7 @@ class Profile {
1192
1197
  this.status = status;
1193
1198
  this.callPickupTargetUsers = callPickupTargetUsers;
1194
1199
  this.onlineCallRecordEnabled = onlineCallRecordEnabled;
1200
+ this.ringSeconds = ringSeconds;
1195
1201
 
1196
1202
  this.sipLines = sipLines || [];
1197
1203
  }
@@ -3295,7 +3301,7 @@ class Recording {
3295
3301
 
3296
3302
 
3297
3303
 
3298
-
3304
+
3299
3305
 
3300
3306
 
3301
3307