Skip to content

Commit e9ea3ab

Browse files
authored
Accept prefixed topic names (#203)
* Accept prefixed topic names * Further validating topic names
1 parent 2e352bc commit e9ea3ab

2 files changed

Lines changed: 40 additions & 12 deletions

File tree

src/messaging/messaging.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,17 +429,24 @@ function validateMessage(message: Message) {
429429
}
430430

431431
const anyMessage = message as any;
432+
if (anyMessage.topic) {
433+
// If the topic name is prefixed, remove it.
434+
if (anyMessage.topic.startsWith('/topics/')) {
435+
anyMessage.topic = anyMessage.topic.replace(/^\/topics\//, '');
436+
}
437+
// Checks for illegal characters and empty string.
438+
if (!/^[a-zA-Z0-9-_.~%]+$/.test(anyMessage.topic)) {
439+
throw new FirebaseMessagingError(
440+
MessagingClientErrorCode.INVALID_PAYLOAD, 'Malformed topic name');
441+
}
442+
}
443+
432444
const targets = [anyMessage.token, anyMessage.topic, anyMessage.condition];
433445
if (targets.filter((v) => validator.isNonEmptyString(v)).length !== 1) {
434446
throw new FirebaseMessagingError(
435447
MessagingClientErrorCode.INVALID_PAYLOAD,
436448
'Exactly one of topic, token or condition is required');
437449
}
438-
if (anyMessage.topic && anyMessage.topic.startsWith('/topics/')) {
439-
throw new FirebaseMessagingError(
440-
MessagingClientErrorCode.INVALID_PAYLOAD,
441-
'Topic name must be specified without the "/topics/" prefix');
442-
}
443450

444451
validateStringMap(message.data, 'data');
445452
validateAndroidConfig(message.android);

test/unit/messaging/messaging.spec.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -365,12 +365,6 @@ describe('Messaging', () => {
365365
});
366366
});
367367

368-
it('should throw given message with invalid topic name', () => {
369-
expect(() => {
370-
messaging.send({topic: '/topics/foo'});
371-
}).to.throw('Topic name must be specified without the "/topics/" prefix');
372-
});
373-
374368
const invalidDryRun = [null, NaN, 0, 1, '', 'a', [], [1, 'a'], {}, { a: 1 }, _.noop];
375369
invalidDryRun.forEach((dryRun) => {
376370
it(`should throw given invalid dryRun parameter: ${JSON.stringify(dryRun)}`, () => {
@@ -380,7 +374,19 @@ describe('Messaging', () => {
380374
});
381375
});
382376

383-
const targetMessages = [{token: 'mock-token'}, {topic: 'mock-topic'}, {condition: '"foo" in topics'}];
377+
const invalidTopics = ['/topics/', '/foo/bar', 'foo bar'];
378+
invalidTopics.forEach((topic) => {
379+
it(`should throw given invalid topic name: ${JSON.stringify(topic)}`, () => {
380+
expect(() => {
381+
messaging.send({topic});
382+
}).to.throw('Malformed topic name');
383+
});
384+
});
385+
386+
const targetMessages = [
387+
{token: 'mock-token'}, {topic: 'mock-topic'},
388+
{topic: '/topics/mock-topic'}, {condition: '"foo" in topics'},
389+
];
384390
targetMessages.forEach((message) => {
385391
it(`should be fulfilled with a message ID given a valid message: ${JSON.stringify(message)}`, () => {
386392
mockedRequests.push(mockSendRequest());
@@ -2308,6 +2314,21 @@ describe('Messaging', () => {
23082314
});
23092315
});
23102316

2317+
it('should not throw when the message is addressed to the prefixed topic name', () => {
2318+
return mockApp.INTERNAL.getToken()
2319+
.then(() => {
2320+
httpsRequestStub = sinon.stub(https, 'request');
2321+
httpsRequestStub.callsArgWith(1, mockResponse).returns(mockRequestStream);
2322+
return messaging.send({topic: '/topics/mock-topic'});
2323+
})
2324+
.then(() => {
2325+
expect(requestWriteSpy).to.have.been.calledOnce;
2326+
const requestData = JSON.parse(requestWriteSpy.args[0][0]);
2327+
const expectedReq = {topic: 'mock-topic'};
2328+
expect(requestData.message).to.deep.equal(expectedReq);
2329+
});
2330+
});
2331+
23112332
it('should convert whitelisted camelCased properties to underscore_cased properties', () => {
23122333
// Wait for the initial getToken() call to complete before stubbing https.request.
23132334
return mockApp.INTERNAL.getToken()

0 commit comments

Comments
 (0)