livekit-client 2.1.2 → 2.1.4
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/livekit-client.esm.mjs +153 -49
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts +1 -0
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts +1 -0
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +5 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +3 -0
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +4 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +4 -0
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts +1 -0
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +1 -1
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/ts4.2/src/room/RegionUrlProvider.d.ts +1 -0
- package/dist/ts4.2/src/room/participant/publishUtils.d.ts +1 -0
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +5 -1
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +3 -0
- package/dist/ts4.2/src/room/track/Track.d.ts +4 -1
- package/dist/ts4.2/src/room/track/options.d.ts +4 -0
- package/dist/ts4.2/src/room/track/utils.d.ts +1 -0
- package/dist/ts4.2/src/room/types.d.ts +1 -1
- package/dist/ts4.2/src/room/utils.d.ts +1 -0
- package/dist/ts4.2/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/api/SignalClient.ts +3 -1
- package/src/room/PCTransport.ts +10 -7
- package/src/room/RTCEngine.ts +47 -10
- package/src/room/RegionUrlProvider.ts +5 -0
- package/src/room/Room.ts +9 -2
- package/src/room/participant/LocalParticipant.ts +7 -16
- package/src/room/participant/publishUtils.ts +20 -0
- package/src/room/track/LocalTrack.ts +21 -2
- package/src/room/track/LocalVideoTrack.ts +23 -0
- package/src/room/track/RemoteTrack.ts +11 -5
- package/src/room/track/Track.ts +1 -1
- package/src/room/track/options.ts +5 -0
- package/src/room/track/utils.ts +4 -0
- package/src/room/types.ts +3 -1
- package/src/room/utils.ts +4 -2
- package/src/version.ts +1 -1
package/src/room/RTCEngine.ts
CHANGED
@@ -9,6 +9,7 @@ import {
|
|
9
9
|
DisconnectReason,
|
10
10
|
type JoinResponse,
|
11
11
|
type LeaveRequest,
|
12
|
+
LeaveRequest_Action,
|
12
13
|
ParticipantInfo,
|
13
14
|
ReconnectReason,
|
14
15
|
type ReconnectResponse,
|
@@ -435,7 +436,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
435
436
|
this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
|
436
437
|
};
|
437
438
|
|
438
|
-
|
439
|
+
if (!supportOptionalDatachannel(joinResponse.serverInfo?.protocol)) {
|
440
|
+
this.createDataChannels();
|
441
|
+
}
|
439
442
|
}
|
440
443
|
|
441
444
|
private setupSignalClientCallbacks() {
|
@@ -504,16 +507,28 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
504
507
|
this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
|
505
508
|
};
|
506
509
|
|
507
|
-
this.client.onLeave = (leave
|
508
|
-
if (leave?.canReconnect) {
|
509
|
-
this.fullReconnectOnNext = true;
|
510
|
-
// reconnect immediately instead of waiting for next attempt
|
511
|
-
this.handleDisconnect(leaveReconnect);
|
512
|
-
} else {
|
513
|
-
this.emit(EngineEvent.Disconnected, leave?.reason);
|
514
|
-
this.close();
|
515
|
-
}
|
510
|
+
this.client.onLeave = (leave: LeaveRequest) => {
|
516
511
|
this.log.debug('client leave request', { ...this.logContext, reason: leave?.reason });
|
512
|
+
if (leave.regions && this.regionUrlProvider) {
|
513
|
+
this.log.debug('updating regions', this.logContext);
|
514
|
+
this.regionUrlProvider.setServerReportedRegions(leave.regions);
|
515
|
+
}
|
516
|
+
switch (leave.action) {
|
517
|
+
case LeaveRequest_Action.DISCONNECT:
|
518
|
+
this.emit(EngineEvent.Disconnected, leave?.reason);
|
519
|
+
this.close();
|
520
|
+
break;
|
521
|
+
case LeaveRequest_Action.RECONNECT:
|
522
|
+
this.fullReconnectOnNext = true;
|
523
|
+
// reconnect immediately instead of waiting for next attempt
|
524
|
+
this.handleDisconnect(leaveReconnect);
|
525
|
+
break;
|
526
|
+
case LeaveRequest_Action.RESUME:
|
527
|
+
// reconnect immediately instead of waiting for next attempt
|
528
|
+
this.handleDisconnect(leaveReconnect);
|
529
|
+
default:
|
530
|
+
break;
|
531
|
+
}
|
517
532
|
};
|
518
533
|
}
|
519
534
|
|
@@ -1103,11 +1118,21 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1103
1118
|
throw new ConnectionError(`${transportName} connection not set`);
|
1104
1119
|
}
|
1105
1120
|
|
1121
|
+
let needNegotiation = false;
|
1122
|
+
if (!subscriber && !this.dataChannelForKind(kind, subscriber)) {
|
1123
|
+
this.createDataChannels();
|
1124
|
+
needNegotiation = true;
|
1125
|
+
}
|
1126
|
+
|
1106
1127
|
if (
|
1128
|
+
!needNegotiation &&
|
1107
1129
|
!subscriber &&
|
1108
1130
|
!this.pcManager.publisher.isICEConnected &&
|
1109
1131
|
this.pcManager.publisher.getICEConnectionState() !== 'checking'
|
1110
1132
|
) {
|
1133
|
+
needNegotiation = true;
|
1134
|
+
}
|
1135
|
+
if (needNegotiation) {
|
1111
1136
|
// start negotiation
|
1112
1137
|
this.negotiate();
|
1113
1138
|
}
|
@@ -1168,6 +1193,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1168
1193
|
}
|
1169
1194
|
|
1170
1195
|
this.pcManager.requirePublisher();
|
1196
|
+
// don't negotiate without any transceivers or data channel, it will generate sdp without ice frag then negotiate failed
|
1197
|
+
if (
|
1198
|
+
this.pcManager.publisher.getTransceivers().length == 0 &&
|
1199
|
+
!this.lossyDC &&
|
1200
|
+
!this.reliableDC
|
1201
|
+
) {
|
1202
|
+
this.createDataChannels();
|
1203
|
+
}
|
1171
1204
|
|
1172
1205
|
const abortController = new AbortController();
|
1173
1206
|
|
@@ -1378,3 +1411,7 @@ export type EngineEventCallbacks = {
|
|
1378
1411
|
remoteMute: (trackSid: string, muted: boolean) => void;
|
1379
1412
|
offline: () => void;
|
1380
1413
|
};
|
1414
|
+
|
1415
|
+
function supportOptionalDatachannel(protocol: number | undefined): boolean {
|
1416
|
+
return protocol !== undefined && protocol > 13;
|
1417
|
+
}
|
package/src/room/Room.ts
CHANGED
@@ -4,6 +4,7 @@ import {
|
|
4
4
|
DisconnectReason,
|
5
5
|
JoinResponse,
|
6
6
|
LeaveRequest,
|
7
|
+
LeaveRequest_Action,
|
7
8
|
ParticipantInfo,
|
8
9
|
ParticipantInfo_State,
|
9
10
|
ParticipantPermission,
|
@@ -859,7 +860,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
859
860
|
onLeave(
|
860
861
|
new LeaveRequest({
|
861
862
|
reason: DisconnectReason.CLIENT_INITIATED,
|
862
|
-
|
863
|
+
action: LeaveRequest_Action.RECONNECT,
|
863
864
|
}),
|
864
865
|
);
|
865
866
|
}
|
@@ -876,7 +877,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
876
877
|
},
|
877
878
|
});
|
878
879
|
break;
|
879
|
-
|
880
|
+
case 'leave-full-reconnect':
|
881
|
+
req = new SimulateScenario({
|
882
|
+
scenario: {
|
883
|
+
case: 'leaveRequestFullReconnect',
|
884
|
+
value: true,
|
885
|
+
},
|
886
|
+
});
|
880
887
|
default:
|
881
888
|
}
|
882
889
|
if (req) {
|
@@ -54,6 +54,7 @@ import { trackPermissionToProto } from './ParticipantTrackPermission';
|
|
54
54
|
import {
|
55
55
|
computeTrackBackupEncodings,
|
56
56
|
computeVideoEncodings,
|
57
|
+
getDefaultDegradationPreference,
|
57
58
|
mediaTrackToLocalTrack,
|
58
59
|
} from './publishUtils';
|
59
60
|
|
@@ -857,6 +858,11 @@ export default class LocalParticipant extends Participant {
|
|
857
858
|
|
858
859
|
track.sender = await this.engine.createSender(track, opts, encodings);
|
859
860
|
|
861
|
+
if (track instanceof LocalVideoTrack) {
|
862
|
+
opts.degradationPreference ??= getDefaultDegradationPreference(track);
|
863
|
+
track.setDegradationPreference(opts.degradationPreference);
|
864
|
+
}
|
865
|
+
|
860
866
|
if (encodings) {
|
861
867
|
if (isFireFox() && track.kind === Track.Kind.Audio) {
|
862
868
|
/* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
|
@@ -880,8 +886,7 @@ export default class LocalParticipant extends Participant {
|
|
880
886
|
maxbr: encodings[0]?.maxBitrate ? encodings[0].maxBitrate / 1000 : 0,
|
881
887
|
});
|
882
888
|
}
|
883
|
-
} else if (track.codec && track.codec
|
884
|
-
// AV1 requires setting x-start-bitrate in SDP
|
889
|
+
} else if (track.codec && isSVCCodec(track.codec) && encodings[0]?.maxBitrate) {
|
885
890
|
this.engine.pcManager.publisher.setTrackCodecBitrate({
|
886
891
|
cid: req.cid,
|
887
892
|
codec: track.codec,
|
@@ -890,20 +895,6 @@ export default class LocalParticipant extends Participant {
|
|
890
895
|
}
|
891
896
|
}
|
892
897
|
|
893
|
-
if (track.kind === Track.Kind.Video && track.source === Track.Source.ScreenShare) {
|
894
|
-
// a few of reasons we are forcing this setting without allowing overrides:
|
895
|
-
// 1. without this, Chrome seems to aggressively resize the SVC video stating `quality-limitation: bandwidth` even when BW isn't an issue
|
896
|
-
// 2. since we are overriding contentHint to motion (to workaround L1T3 publishing), it overrides the default degradationPreference to `balanced`
|
897
|
-
try {
|
898
|
-
this.log.debug(`setting degradationPreference to maintain-resolution`);
|
899
|
-
const params = track.sender.getParameters();
|
900
|
-
params.degradationPreference = 'maintain-resolution';
|
901
|
-
await track.sender.setParameters(params);
|
902
|
-
} catch (e) {
|
903
|
-
this.log.warn(`failed to set degradationPreference: ${e}`);
|
904
|
-
}
|
905
|
-
}
|
906
|
-
|
907
898
|
await this.engine.negotiate();
|
908
899
|
|
909
900
|
if (track instanceof LocalVideoTrack) {
|
@@ -19,6 +19,7 @@ import {
|
|
19
19
|
isReactNative,
|
20
20
|
isSVCCodec,
|
21
21
|
isSafari,
|
22
|
+
unwrapConstraint,
|
22
23
|
} from '../utils';
|
23
24
|
|
24
25
|
/** @internal */
|
@@ -171,6 +172,11 @@ export function computeVideoEncodings(
|
|
171
172
|
});
|
172
173
|
}
|
173
174
|
|
175
|
+
if (original.encoding.priority) {
|
176
|
+
encodings[0].priority = original.encoding.priority;
|
177
|
+
encodings[0].networkPriority = original.encoding.priority;
|
178
|
+
}
|
179
|
+
|
174
180
|
log.debug(`using svc encoding`, { encodings });
|
175
181
|
return encodings;
|
176
182
|
}
|
@@ -435,3 +441,17 @@ export class ScalabilityMode {
|
|
435
441
|
return `L${this.spatial}T${this.temporal}${this.suffix ?? ''}`;
|
436
442
|
}
|
437
443
|
}
|
444
|
+
|
445
|
+
export function getDefaultDegradationPreference(track: LocalVideoTrack): RTCDegradationPreference {
|
446
|
+
// a few of reasons we have different default paths:
|
447
|
+
// 1. without this, Chrome seems to aggressively resize the SVC video stating `quality-limitation: bandwidth` even when BW isn't an issue
|
448
|
+
// 2. since we are overriding contentHint to motion (to workaround L1T3 publishing), it overrides the default degradationPreference to `balanced`
|
449
|
+
if (
|
450
|
+
track.source === Track.Source.ScreenShare ||
|
451
|
+
(track.constraints.height && unwrapConstraint(track.constraints.height) >= 1080)
|
452
|
+
) {
|
453
|
+
return 'maintain-resolution';
|
454
|
+
} else {
|
455
|
+
return 'balanced';
|
456
|
+
}
|
457
|
+
}
|
@@ -15,8 +15,17 @@ const defaultDimensionsTimeout = 1000;
|
|
15
15
|
export default abstract class LocalTrack<
|
16
16
|
TrackKind extends Track.Kind = Track.Kind,
|
17
17
|
> extends Track<TrackKind> {
|
18
|
+
protected _sender?: RTCRtpSender;
|
19
|
+
|
18
20
|
/** @internal */
|
19
|
-
sender
|
21
|
+
get sender(): RTCRtpSender | undefined {
|
22
|
+
return this._sender;
|
23
|
+
}
|
24
|
+
|
25
|
+
/** @internal */
|
26
|
+
set sender(sender: RTCRtpSender | undefined) {
|
27
|
+
this._sender = sender;
|
28
|
+
}
|
20
29
|
|
21
30
|
/** @internal */
|
22
31
|
codec?: VideoCodec;
|
@@ -43,6 +52,8 @@ export default abstract class LocalTrack<
|
|
43
52
|
|
44
53
|
protected audioContext?: AudioContext;
|
45
54
|
|
55
|
+
protected manuallyStopped: boolean = false;
|
56
|
+
|
46
57
|
private restartLock: Mutex;
|
47
58
|
|
48
59
|
/**
|
@@ -259,6 +270,7 @@ export default abstract class LocalTrack<
|
|
259
270
|
}
|
260
271
|
|
261
272
|
protected async restart(constraints?: MediaTrackConstraints) {
|
273
|
+
this.manuallyStopped = false;
|
262
274
|
const unlock = await this.restartLock.lock();
|
263
275
|
try {
|
264
276
|
if (!constraints) {
|
@@ -296,8 +308,14 @@ export default abstract class LocalTrack<
|
|
296
308
|
|
297
309
|
await this.setMediaStreamTrack(newTrack);
|
298
310
|
this._constraints = constraints;
|
299
|
-
|
300
311
|
this.emit(TrackEvent.Restarted, this);
|
312
|
+
if (this.manuallyStopped) {
|
313
|
+
this.log.warn(
|
314
|
+
'track was stopped during a restart, stopping restarted track',
|
315
|
+
this.logContext,
|
316
|
+
);
|
317
|
+
this.stop();
|
318
|
+
}
|
301
319
|
return this;
|
302
320
|
} finally {
|
303
321
|
unlock();
|
@@ -361,6 +379,7 @@ export default abstract class LocalTrack<
|
|
361
379
|
};
|
362
380
|
|
363
381
|
stop() {
|
382
|
+
this.manuallyStopped = true;
|
364
383
|
super.stop();
|
365
384
|
|
366
385
|
this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
|
@@ -53,6 +53,15 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
53
53
|
// a missing `getParameter` call.
|
54
54
|
private senderLock: Mutex;
|
55
55
|
|
56
|
+
private degradationPreference: RTCDegradationPreference = 'balanced';
|
57
|
+
|
58
|
+
override set sender(sender: RTCRtpSender | undefined) {
|
59
|
+
this._sender = sender;
|
60
|
+
if (this.degradationPreference) {
|
61
|
+
this.setDegradationPreference(this.degradationPreference);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
56
65
|
/**
|
57
66
|
*
|
58
67
|
* @param mediaTrack
|
@@ -273,6 +282,20 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
273
282
|
}
|
274
283
|
}
|
275
284
|
|
285
|
+
async setDegradationPreference(preference: RTCDegradationPreference) {
|
286
|
+
this.degradationPreference = preference;
|
287
|
+
if (this.sender) {
|
288
|
+
try {
|
289
|
+
this.log.debug(`setting degradationPreference to ${preference}`, this.logContext);
|
290
|
+
const params = this.sender.getParameters();
|
291
|
+
params.degradationPreference = preference;
|
292
|
+
this.sender.setParameters(params);
|
293
|
+
} catch (e: any) {
|
294
|
+
this.log.warn(`failed to set degradationPreference`, { error: e, ...this.logContext });
|
295
|
+
}
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
276
299
|
addSimulcastTrack(
|
277
300
|
codec: VideoCodec,
|
278
301
|
encodings?: RTCRtpEncodingParameters[],
|
@@ -2,6 +2,7 @@ import { TrackEvent } from '../events';
|
|
2
2
|
import { monitorFrequency } from '../stats';
|
3
3
|
import type { LoggerOptions } from '../types';
|
4
4
|
import { Track } from './Track';
|
5
|
+
import { supportsSynchronizationSources } from './utils';
|
5
6
|
|
6
7
|
export default abstract class RemoteTrack<
|
7
8
|
TrackKind extends Track.Kind = Track.Kind,
|
@@ -77,7 +78,9 @@ export default abstract class RemoteTrack<
|
|
77
78
|
if (!this.monitorInterval) {
|
78
79
|
this.monitorInterval = setInterval(() => this.monitorReceiver(), monitorFrequency);
|
79
80
|
}
|
80
|
-
|
81
|
+
if (supportsSynchronizationSources()) {
|
82
|
+
this.registerTimeSyncUpdate();
|
83
|
+
}
|
81
84
|
}
|
82
85
|
|
83
86
|
protected abstract monitorReceiver(): void;
|
@@ -85,10 +88,13 @@ export default abstract class RemoteTrack<
|
|
85
88
|
registerTimeSyncUpdate() {
|
86
89
|
const loop = () => {
|
87
90
|
this.timeSyncHandle = requestAnimationFrame(() => loop());
|
88
|
-
const
|
89
|
-
if (
|
90
|
-
|
91
|
-
this.rtpTimestamp
|
91
|
+
const sources = this.receiver?.getSynchronizationSources()[0];
|
92
|
+
if (sources) {
|
93
|
+
const { timestamp, rtpTimestamp } = sources;
|
94
|
+
if (rtpTimestamp && this.rtpTimestamp !== rtpTimestamp) {
|
95
|
+
this.emit(TrackEvent.TimeSyncUpdate, { timestamp, rtpTimestamp });
|
96
|
+
this.rtpTimestamp = rtpTimestamp;
|
97
|
+
}
|
92
98
|
}
|
93
99
|
};
|
94
100
|
loop();
|
package/src/room/track/Track.ts
CHANGED
@@ -525,5 +525,5 @@ export type TrackEventCallbacks = {
|
|
525
525
|
upstreamResumed: (track: any) => void;
|
526
526
|
trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
|
527
527
|
audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
|
528
|
-
timeSyncUpdate: (timestamp: number) => void;
|
528
|
+
timeSyncUpdate: (update: { timestamp: number; rtpTimestamp: number }) => void;
|
529
529
|
};
|
@@ -64,6 +64,11 @@ export interface TrackPublishDefaults {
|
|
64
64
|
*/
|
65
65
|
scalabilityMode?: ScalabilityMode;
|
66
66
|
|
67
|
+
/**
|
68
|
+
* degradation preference
|
69
|
+
*/
|
70
|
+
degradationPreference?: RTCDegradationPreference;
|
71
|
+
|
67
72
|
/**
|
68
73
|
* Up to two additional simulcast layers to publish in addition to the original
|
69
74
|
* Track.
|
package/src/room/track/utils.ts
CHANGED
@@ -239,3 +239,7 @@ export function getLogContextFromTrack(track: Track | TrackPublication): Record<
|
|
239
239
|
};
|
240
240
|
}
|
241
241
|
}
|
242
|
+
|
243
|
+
export function supportsSynchronizationSources(): boolean {
|
244
|
+
return typeof RTCRtpReceiver !== 'undefined' && 'getSynchronizationSources' in RTCRtpReceiver;
|
245
|
+
}
|
package/src/room/types.ts
CHANGED
@@ -49,7 +49,9 @@ export type SimulationScenario =
|
|
49
49
|
// to disable congestion control entirely (by setting bandwidth to 100Mbps)
|
50
50
|
| 'subscriber-bandwidth'
|
51
51
|
| 'disconnect-signal-on-resume'
|
52
|
-
| 'disconnect-signal-on-resume-no-messages'
|
52
|
+
| 'disconnect-signal-on-resume-no-messages'
|
53
|
+
// instructs the server to send a full reconnect reconnect action to the client
|
54
|
+
| 'leave-full-reconnect';
|
53
55
|
|
54
56
|
export type LoggerOptions = {
|
55
57
|
loggerName?: string;
|
package/src/room/utils.ts
CHANGED
@@ -491,8 +491,10 @@ export function isVideoCodec(maybeCodec: string): maybeCodec is VideoCodec {
|
|
491
491
|
return videoCodecs.includes(maybeCodec as VideoCodec);
|
492
492
|
}
|
493
493
|
|
494
|
-
export function unwrapConstraint(constraint: ConstrainDOMString): string
|
495
|
-
|
494
|
+
export function unwrapConstraint(constraint: ConstrainDOMString): string;
|
495
|
+
export function unwrapConstraint(constraint: ConstrainULong): number;
|
496
|
+
export function unwrapConstraint(constraint: ConstrainDOMString | ConstrainULong): string | number {
|
497
|
+
if (typeof constraint === 'string' || typeof constraint === 'number') {
|
496
498
|
return constraint;
|
497
499
|
}
|
498
500
|
|
package/src/version.ts
CHANGED