Skip to content

Commit 227f90b

Browse files
Add API to allow expanding a single TURN URL into multiple (#3124)
1 parent 43d0cb5 commit 227f90b

8 files changed

Lines changed: 1900 additions & 1715 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Added additional APIs to underlying VoiceFocus library
13+
- Add API to allow expanding a single TURN URL into multiple. This should not be used by most applications.
1314

1415
### Removed
1516

docs/assets/js/search.js

Lines changed: 1712 additions & 1704 deletions
Large diffs are not rendered by default.

docs/classes/joinandreceiveindextask.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ <h3>constructor</h3>
123123
<aside class="tsd-sources">
124124
<p>Overrides <a href="basetask.html">BaseTask</a>.<a href="basetask.html#constructor">constructor</a></p>
125125
<ul>
126-
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/main/src/task/JoinAndReceiveIndexTask.ts#L30">src/task/JoinAndReceiveIndexTask.ts:30</a></li>
126+
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/main/src/task/JoinAndReceiveIndexTask.ts#L44">src/task/JoinAndReceiveIndexTask.ts:44</a></li>
127127
</ul>
128128
</aside>
129129
<h4 class="tsd-parameters-title">Parameters</h4>
@@ -154,7 +154,7 @@ <h3><span class="tsd-flag ts-flagProtected">Protected</span> task<wbr>Name</h3>
154154
<aside class="tsd-sources">
155155
<p>Overrides <a href="basetask.html">BaseTask</a>.<a href="basetask.html#taskname">taskName</a></p>
156156
<ul>
157-
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/main/src/task/JoinAndReceiveIndexTask.ts#L29">src/task/JoinAndReceiveIndexTask.ts:29</a></li>
157+
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/main/src/task/JoinAndReceiveIndexTask.ts#L43">src/task/JoinAndReceiveIndexTask.ts:43</a></li>
158158
</ul>
159159
</aside>
160160
</section>
@@ -172,7 +172,7 @@ <h3>cancel</h3>
172172
<aside class="tsd-sources">
173173
<p>Overrides <a href="basetask.html">BaseTask</a>.<a href="basetask.html#cancel">cancel</a></p>
174174
<ul>
175-
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/main/src/task/JoinAndReceiveIndexTask.ts#L36">src/task/JoinAndReceiveIndexTask.ts:36</a></li>
175+
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/main/src/task/JoinAndReceiveIndexTask.ts#L50">src/task/JoinAndReceiveIndexTask.ts:50</a></li>
176176
</ul>
177177
</aside>
178178
<div class="tsd-comment tsd-typography">
@@ -288,7 +288,7 @@ <h3>run</h3>
288288
<aside class="tsd-sources">
289289
<p>Overrides <a href="basetask.html">BaseTask</a>.<a href="basetask.html#run">run</a></p>
290290
<ul>
291-
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/main/src/task/JoinAndReceiveIndexTask.ts#L43">src/task/JoinAndReceiveIndexTask.ts:43</a></li>
291+
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/main/src/task/JoinAndReceiveIndexTask.ts#L57">src/task/JoinAndReceiveIndexTask.ts:57</a></li>
292292
</ul>
293293
</aside>
294294
<div class="tsd-comment tsd-typography">

docs/classes/meetingsessionurls.html

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ <h3>Constructors</h3>
9595
<h3>Properties</h3>
9696
<ul class="tsd-index-list">
9797
<li class="tsd-kind-property tsd-parent-kind-class"><a href="meetingsessionurls.html#urlrewriter" class="tsd-kind-icon">url<wbr>Rewriter</a></li>
98+
<li class="tsd-kind-property tsd-parent-kind-class"><a href="meetingsessionurls.html#urlrewritermulti" class="tsd-kind-icon">url<wbr>Rewriter<wbr>Multi</a></li>
9899
</ul>
99100
</section>
100101
<section class="tsd-index-section ">
@@ -165,6 +166,45 @@ <h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">string</s
165166
</ul>
166167
</div>
167168
</section>
169+
<section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class">
170+
<a name="urlrewritermulti" class="tsd-anchor"></a>
171+
<h3>url<wbr>Rewriter<wbr>Multi</h3>
172+
<div class="tsd-signature tsd-kind-icon">url<wbr>Rewriter<wbr>Multi<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span>url<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol"> =&gt; </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol"> = null</span></div>
173+
<aside class="tsd-sources">
174+
<ul>
175+
<li>Defined in <a href="https://github.com/aws/amazon-chime-sdk-js/blob/main/src/meetingsession/MeetingSessionURLs.ts#L94">src/meetingsession/MeetingSessionURLs.ts:94</a></li>
176+
</ul>
177+
</aside>
178+
<div class="tsd-type-declaration">
179+
<h4>Type declaration</h4>
180+
<ul class="tsd-parameters">
181+
<li class="tsd-parameter-signature">
182+
<ul class="tsd-signatures tsd-kind-type-literal tsd-parent-kind-class">
183+
<li class="tsd-signature tsd-kind-icon"><span class="tsd-signature-symbol">(</span>url<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">[]</span></li>
184+
</ul>
185+
<ul class="tsd-descriptions">
186+
<li class="tsd-description">
187+
<div class="tsd-comment tsd-typography">
188+
<div class="lead">
189+
<p>Optional function to transform a single URL into multiple URLs. This is currently only used for TURN URI processing.</p>
190+
</div>
191+
<p>There is not any good reason for a typical application to implement this.</p>
192+
<p>When set, this takes precedence over <a href="meetingsessionurls.html#urlrewriter">MeetingSessionURLs.urlRewriter</a> for TURN URI processing.
193+
The default is null, meaning <a href="meetingsessionurls.html#urlrewriter">MeetingSessionURLs.urlRewriter</a> will be used instead.</p>
194+
</div>
195+
<h4 class="tsd-parameters-title">Parameters</h4>
196+
<ul class="tsd-parameters">
197+
<li>
198+
<h5>url: <span class="tsd-signature-type">string</span></h5>
199+
</li>
200+
</ul>
201+
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">[]</span></h4>
202+
</li>
203+
</ul>
204+
</li>
205+
</ul>
206+
</div>
207+
</section>
168208
</section>
169209
<section class="tsd-panel-group tsd-member-group ">
170210
<h2>Accessors</h2>
@@ -361,6 +401,9 @@ <h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</spa
361401
<li class=" tsd-kind-property tsd-parent-kind-class">
362402
<a href="meetingsessionurls.html#urlrewriter" class="tsd-kind-icon">url<wbr>Rewriter</a>
363403
</li>
404+
<li class=" tsd-kind-property tsd-parent-kind-class">
405+
<a href="meetingsessionurls.html#urlrewritermulti" class="tsd-kind-icon">url<wbr>Rewriter<wbr>Multi</a>
406+
</li>
364407
<li class=" tsd-kind-accessor tsd-parent-kind-class">
365408
<a href="meetingsessionurls.html#audiohosturl" class="tsd-kind-icon">audio<wbr>HostURL</a>
366409
</li>

src/meetingsession/MeetingSessionURLs.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,14 @@ export default class MeetingSessionURLs {
8282
urlRewriter: (url: string | null) => string | null = (url: string | null) => {
8383
return url;
8484
};
85+
86+
/**
87+
* Optional function to transform a single URL into multiple URLs. This is currently only used for TURN URI processing.
88+
*
89+
* There is not any good reason for a typical application to implement this.
90+
*
91+
* When set, this takes precedence over {@link MeetingSessionURLs.urlRewriter} for TURN URI processing.
92+
* The default is null, meaning {@link MeetingSessionURLs.urlRewriter} will be used instead.
93+
*/
94+
urlRewriterMulti: ((url: string | null) => string[] | null) | null = null;
8595
}

src/task/JoinAndReceiveIndexTask.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import AudioVideoControllerState from '../audiovideocontroller/AudioVideoControl
55
import MeetingSessionStatus from '../meetingsession/MeetingSessionStatus';
66
import MeetingSessionStatusCode from '../meetingsession/MeetingSessionStatusCode';
77
import MeetingSessionTURNCredentials from '../meetingsession/MeetingSessionTURNCredentials';
8+
import MeetingSessionURLs from '../meetingsession/MeetingSessionURLs';
89
import ServerSideNetworkAdaption, {
910
convertServerSideNetworkAdaptionEnumFromSignaled,
1011
} from '../signalingclient/ServerSideNetworkAdaption';
@@ -21,6 +22,19 @@ import {
2122
import TaskCanceler from '../taskcanceler/TaskCanceler';
2223
import BaseTask from './BaseTask';
2324

25+
function rewriteTurnUris(uris: string[], urls: MeetingSessionURLs): string[] {
26+
const { urlRewriterMulti, urlRewriter } = urls;
27+
return uris
28+
.flatMap((uri: string): string[] => {
29+
if (urlRewriterMulti) {
30+
return urlRewriterMulti(uri) || [];
31+
}
32+
const rewritten = urlRewriter(uri);
33+
return rewritten ? [rewritten] : [];
34+
})
35+
.filter((uri: string) => !!uri);
36+
}
37+
2438
/*
2539
* [[JoinAndReceiveIndexTask]] sends the JoinFrame and asynchronously waits for the server to send the [[SdkIndexFrame]].
2640
* It should run with the [[TimeoutTask]] as the subtask so it can get canceled after timeout.
@@ -109,13 +123,10 @@ export default class JoinAndReceiveIndexTask extends BaseTask {
109123
context.turnCredentials.username = joinAckFrame.turnCredentials.username;
110124
context.turnCredentials.password = joinAckFrame.turnCredentials.password;
111125
context.turnCredentials.ttl = joinAckFrame.turnCredentials.ttl;
112-
context.turnCredentials.uris = joinAckFrame.turnCredentials.uris
113-
.map((uri: string): string => {
114-
return context.meetingSessionConfiguration.urls.urlRewriter(uri);
115-
})
116-
.filter((uri: string) => {
117-
return !!uri;
118-
});
126+
context.turnCredentials.uris = rewriteTurnUris(
127+
joinAckFrame.turnCredentials.uris,
128+
context.meetingSessionConfiguration.urls
129+
);
119130
} else {
120131
context.logger.error('missing TURN credentials in JoinAckFrame');
121132
}

test/meetingsession/MeetingSessionURLs.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,37 @@ describe('MeetingSessionURLs', () => {
3535
expect(urls.turnControlURL).to.eq('turn-control-rewritten-url');
3636
expect(urls.eventIngestionURL).to.eq('event-ingestion-rewritten-url');
3737
});
38+
39+
it('has urlRewriterMulti as null by default', () => {
40+
const urls = new MeetingSessionURLs();
41+
expect(urls.urlRewriterMulti).to.be.null;
42+
});
43+
44+
it('can use a custom urlRewriterMulti to expand URLs', () => {
45+
const urls = new MeetingSessionURLs();
46+
urls.urlRewriterMulti = (url: string | null) => {
47+
if (url === null) {
48+
return null;
49+
}
50+
return [`${url}-1`, `${url}-2`];
51+
};
52+
expect(urls.urlRewriterMulti(null)).to.be.null;
53+
expect(urls.urlRewriterMulti('turn:server')).to.deep.equal([
54+
'turn:server-1',
55+
'turn:server-2',
56+
]);
57+
});
58+
59+
it('can use urlRewriterMulti to filter out URLs by returning null', () => {
60+
const urls = new MeetingSessionURLs();
61+
urls.urlRewriterMulti = (url: string | null) => {
62+
if (url === null || url.includes('blocked')) {
63+
return null;
64+
}
65+
return [url];
66+
};
67+
expect(urls.urlRewriterMulti('turn:allowed')).to.deep.equal(['turn:allowed']);
68+
expect(urls.urlRewriterMulti('turn:blocked')).to.be.null;
69+
});
3870
});
3971
});

test/task/JoinAndReceiveIndexTask.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,86 @@ describe('JoinAndReceiveIndexTask', () => {
425425
await task.run();
426426
expect(context.indexFrame).to.not.equal(null);
427427
});
428+
429+
it('uses urlRewriterMulti to expand TURN URIs when set', async () => {
430+
context.meetingSessionConfiguration.urls.urlRewriterMulti = (url: string | null) => {
431+
if (!url) return null;
432+
return [`${url}-expanded-1`, `${url}-expanded-2`];
433+
};
434+
435+
await delay(behavior.asyncWaitMs + 10);
436+
expect(signalingClient.ready()).to.equal(true);
437+
new TimeoutScheduler(100).start(() => {
438+
webSocketAdapter.send(joinAckSignalBuffer);
439+
});
440+
new TimeoutScheduler(200).start(() => {
441+
webSocketAdapter.send(indexSignalBuffer);
442+
});
443+
await task.run();
444+
expect(context.turnCredentials.uris).to.deep.equal([
445+
'fake-turn-expanded-1',
446+
'fake-turn-expanded-2',
447+
'fake-turns-expanded-1',
448+
'fake-turns-expanded-2',
449+
]);
450+
});
451+
452+
it('uses urlRewriterMulti to filter out TURN URIs by returning null', async () => {
453+
context.meetingSessionConfiguration.urls.urlRewriterMulti = (url: string | null) => {
454+
if (!url || url.includes('turns')) return null;
455+
return [url];
456+
};
457+
458+
await delay(behavior.asyncWaitMs + 10);
459+
expect(signalingClient.ready()).to.equal(true);
460+
new TimeoutScheduler(100).start(() => {
461+
webSocketAdapter.send(joinAckSignalBuffer);
462+
});
463+
new TimeoutScheduler(200).start(() => {
464+
webSocketAdapter.send(indexSignalBuffer);
465+
});
466+
await task.run();
467+
expect(context.turnCredentials.uris).to.deep.equal(['fake-turn']);
468+
});
469+
470+
it('falls back to urlRewriter when urlRewriterMulti is not set', async () => {
471+
context.meetingSessionConfiguration.urls.urlRewriter = (url: string | null) => {
472+
if (!url) return null;
473+
return `${url}-rewritten`;
474+
};
475+
476+
await delay(behavior.asyncWaitMs + 10);
477+
expect(signalingClient.ready()).to.equal(true);
478+
new TimeoutScheduler(100).start(() => {
479+
webSocketAdapter.send(joinAckSignalBuffer);
480+
});
481+
new TimeoutScheduler(200).start(() => {
482+
webSocketAdapter.send(indexSignalBuffer);
483+
});
484+
await task.run();
485+
expect(context.turnCredentials.uris).to.deep.equal([
486+
'fake-turn-rewritten',
487+
'fake-turns-rewritten',
488+
]);
489+
});
490+
491+
it('filters out TURN URIs when urlRewriter returns null', async () => {
492+
context.meetingSessionConfiguration.urls.urlRewriter = (url: string | null) => {
493+
if (!url || url.includes('turns')) return null;
494+
return url;
495+
};
496+
497+
await delay(behavior.asyncWaitMs + 10);
498+
expect(signalingClient.ready()).to.equal(true);
499+
new TimeoutScheduler(100).start(() => {
500+
webSocketAdapter.send(joinAckSignalBuffer);
501+
});
502+
new TimeoutScheduler(200).start(() => {
503+
webSocketAdapter.send(indexSignalBuffer);
504+
});
505+
await task.run();
506+
expect(context.turnCredentials.uris).to.deep.equal(['fake-turn']);
507+
});
428508
});
429509

430510
describe('cancel', () => {

0 commit comments

Comments
 (0)