Skip to content

Commit e2dd161

Browse files
committed
🎹 Add new button types for Generic template
1 parent 75cbffc commit e2dd161

File tree

3 files changed

+139
-25
lines changed

3 files changed

+139
-25
lines changed

docs/FB_TEMPLATE_MESSAGE_BUILDER.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,12 @@ _Arguments_:
114114
| addBubble | Yes | title (string, required), subtitle (string) | `this` for chaining | Each Generic template can have 1 to 10 elements/bubbles, before you add anything to it. It requires element's title, but it can also accept element's subtitle |
115115
| addUrl | No | A valid URL | `this` for chaining | Adds an url to a current element, requires a valid URL, also requires `addBubble` to be added first |
116116
| addImage | No | A valid absolute URL | `this` for chaining | Adds an image to a current element, requires a valid URL, also requires `addBubble` to be added first |
117-
| addButton | Yes | title (string, required), value (required, string or a valid URL) | `this` for chaining | Adds a button to a current element, each button requires a title and a value, where value can be any string if you want `postback` type or a valid URL if you want it's type to be `web_url`, at least one button is required, and maximum 3 of them is allowed. It also requires `addBubble` to be added first |
117+
| addButton | Yes, at least one of the button types | title (string, required), value (required, string or a valid URL) | `this` for chaining | Adds a button to a current element, each button requires a title and a value, where value can be any string if you want `postback` type or a valid URL if you want it's type to be `web_url`, at least one button is required, and maximum 3 of them is allowed. It also requires `addBubble` to be added first |
118+
| addCallButton | Yes, at least one of the button types | title (string, required), phoneNumber (required, string in '+1234...' format) | `this` for chaining | Adds a call button, check [official docs](https://developers.facebook.com/docs/messenger-platform/send-api-reference/call-button) for more info |
119+
| addShareButton | Yes, at least one of the button types | No args. | `this` for chaining | Adds a share button, check [official docs](https://developers.facebook.com/docs/messenger-platform/send-api-reference/share-button) for more info |
120+
| addBuyButton | Yes, at least one of the button types | title (string, required), value (required, string), paymentSummary (required, object, check [official docs](https://developers.facebook.com/docs/messenger-platform/send-api-reference/buy-button) for more info) | `this` for chaining | Adds a share button, check [official docs](https://developers.facebook.com/docs/messenger-platform/send-api-reference/buy-button) for more info |
121+
| addLoginButton | Yes, at least one of the button types | url (required, a valid URL) | `this` for chaining | Adds a login button, check [official docs](https://developers.facebook.com/docs/messenger-platform/account-linking/link-account) for more info |
122+
| addLogoutButton | Yes, at least one of the button types | No args. | `this` for chaining | Adds a logout button, check [official docs](https://developers.facebook.com/docs/messenger-platform/account-linking/unlink-account) for more info |
118123
| addQuickReply | No | title (string, required, up to 20 characters), payload (string, required), up to 1000 characters | `this` for chaining | Facebook allows us to send up to 10 quick replies that will appear above the keyboard |
119124
| setNotificationType | No | type (string, one of `REGULAR`, `SILENT_PUSH` or `NO_PUSH`) | `this` for chaining | REGULAR will emit a sound/vibration and a phone notification; SILENT_PUSH will just emit a phone notification, NO_PUSH will not emit either
120125
| get | Yes | No args. | Formatted JSON | Get method is required and it returns a formatted JSON that is ready to be passed as a response to Facebook Messenger |

lib/facebook/format-message.js

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,10 @@ class Generic extends FacebookTemplate {
253253
return this;
254254
}
255255

256-
addButton(title, value, type) {
256+
addButtonByType(title, value, type, options) {
257+
if (!title)
258+
throw new Error('Button title cannot be empty');
259+
257260
const bubble = this.getLastBubble();
258261

259262
bubble.buttons = bubble.buttons || [];
@@ -264,44 +267,80 @@ class Generic extends FacebookTemplate {
264267
if (!title)
265268
throw new Error('Button title cannot be empty');
266269

267-
if (!value)
268-
throw new Error('Button value is required');
269-
270270
const button = {
271-
title: title
271+
title: title,
272+
type: type || 'postback'
272273
};
273274

274-
if (isUrl(value)) {
275-
button.type = 'web_url';
275+
if (type === 'web_url') {
276+
button.url = value;
277+
} else if (type === 'account_link') {
278+
delete button.title;
276279
button.url = value;
280+
} else if (type === 'phone_number') {
281+
button.payload = value;
282+
} else if (type === 'payment') {
283+
button.payload = value;
284+
button.payment_summary = options.paymentSummary;
285+
} else if (type === 'element_share' || type === 'account_unlink') {
286+
delete button.title;
277287
} else {
278288
button.type = 'postback';
279289
button.payload = value;
280290
}
281291

282-
if (type) {
283-
button.type = type;
284-
}
285-
286292
bubble.buttons.push(button);
287293

288294
return this;
289295
}
290296

297+
addButton(title, value) {
298+
// Keeping this to prevent breaking change
299+
if (!title)
300+
throw new Error('Button title cannot be empty');
301+
302+
if (!value)
303+
throw new Error('Button value is required');
304+
305+
if (isUrl(value)) {
306+
return this.addButtonByType(title, value, 'web_url');
307+
} else {
308+
return this.addButtonByType(title, value, 'postback');
309+
}
310+
}
311+
312+
addCallButton(title, phoneNumber) {
313+
if (!/^\+[0-9]{4,20}$/.test(phoneNumber))
314+
throw new Error('Call button value needs to be a valid phone number in following format: +1234567...');
315+
316+
return this.addButtonByType(title, phoneNumber, 'phone_number');
317+
}
318+
291319
addShareButton() {
292-
const bubble = this.getLastBubble();
320+
return this.addButtonByType('Share', null, 'element_share');
321+
}
293322

294-
bubble.buttons = bubble.buttons || [];
323+
addBuyButton(title, value, paymentSummary) {
324+
if (!value)
325+
throw new Error('Button value is required');
295326

296-
if (bubble.buttons.length === 3)
297-
throw new Error('3 buttons are already added and that\'s the maximum');
298-
const button = {
299-
type: 'element_share'
300-
};
327+
if (typeof paymentSummary !== 'object')
328+
throw new Error('Payment summary is required for buy button');
301329

302-
bubble.buttons.push(button);
330+
return this.addButtonByType(title, value, 'payment', {
331+
paymentSummary: paymentSummary
332+
});
333+
}
303334

304-
return this;
335+
addLoginButton(url) {
336+
if (!isUrl(url))
337+
throw new Error('Valid URL is required for Login button');
338+
339+
return this.addButtonByType('Login', url, 'account_link');
340+
}
341+
342+
addLogoutButton() {
343+
return this.addButtonByType('Logout', null, 'account_unlink');
305344
}
306345

307346
get() {

spec/facebook/facebook-format-message-spec.js

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,14 +311,84 @@ describe('Facebook format message', () => {
311311
expect(generic.bubbles[0].buttons[2].payload).toBe('v3');
312312
});
313313

314-
it('should override type when a type parameter is passed', () => {
315-
generic
316-
.addBubble('Test')
317-
.addButton('b1', '+123456789', 'phone_number');
314+
it('should throw an error if call button is added with wrong phone format', () => {
315+
generic.addBubble('Test');
316+
expect(() => generic.addCallButton('Title')).toThrowError('Call button value needs to be a valid phone number in following format: +1234567...');
317+
expect(() => generic.addCallButton('Title', 123)).toThrowError('Call button value needs to be a valid phone number in following format: +1234567...');
318+
expect(() => generic.addCallButton('Title', 'abc')).toThrowError('Call button value needs to be a valid phone number in following format: +1234567...');
319+
expect(() => generic.addCallButton('Title', '+123')).toThrowError('Call button value needs to be a valid phone number in following format: +1234567...');
320+
});
318321

322+
it('should add a call button', () => {
323+
generic.addBubble('Test')
324+
.addCallButton('Button 1', '+123456789');
325+
326+
expect(generic.bubbles[0].buttons.length).toBe(1);
327+
expect(generic.bubbles[0].buttons[0].title).toBe('Button 1');
328+
expect(generic.bubbles[0].buttons[0].payload).toBe('+123456789');
319329
expect(generic.bubbles[0].buttons[0].type).toBe('phone_number');
320330
});
321331

332+
it('should add a share button', () => {
333+
generic.addBubble('Test')
334+
.addShareButton();
335+
336+
expect(generic.bubbles[0].buttons.length).toBe(1);
337+
expect(generic.bubbles[0].buttons[0].title).toBeUndefined();
338+
expect(generic.bubbles[0].buttons[0].type).toBe('element_share');
339+
});
340+
341+
it('should throw an error if all arguments are not provided for buy button', () => {
342+
generic.addBubble('Test');
343+
344+
expect(() => generic.addBuyButton()).toThrowError('Button value is required');
345+
expect(() => generic.addBuyButton('Title')).toThrowError('Button value is required');
346+
expect(() => generic.addBuyButton('Title', 'PAYLOAD')).toThrowError('Payment summary is required for buy button');
347+
expect(() => generic.addBuyButton('Title', 'PAYLOAD', 123)).toThrowError('Payment summary is required for buy button');
348+
expect(() => generic.addBuyButton('Title', 'PAYLOAD', 'abc')).toThrowError('Payment summary is required for buy button');
349+
});
350+
351+
it('should add a buy button', () => {
352+
generic.addBubble('Test')
353+
.addBuyButton('Buy', 'BUY_PAYLOAD', {
354+
additionalOptions: true
355+
});
356+
357+
expect(generic.bubbles[0].buttons.length).toBe(1);
358+
expect(generic.bubbles[0].buttons[0].title).toBe('Buy');
359+
expect(generic.bubbles[0].buttons[0].type).toBe('payment');
360+
expect(generic.bubbles[0].buttons[0].payload).toBe('BUY_PAYLOAD');
361+
expect(generic.bubbles[0].buttons[0].payment_summary).toEqual({
362+
additionalOptions: true
363+
});
364+
});
365+
366+
it('should throw an error if url provided for login button is not valid', () => {
367+
generic.addBubble('Test');
368+
369+
expect(() => generic.addLoginButton()).toThrowError('Valid URL is required for Login button');
370+
expect(() => generic.addLoginButton('123')).toThrowError('Valid URL is required for Login button');
371+
});
372+
373+
it('should add a login button', () => {
374+
generic.addBubble('Test')
375+
.addLoginButton('https://example.com');
376+
377+
expect(generic.bubbles[0].buttons.length).toBe(1);
378+
expect(generic.bubbles[0].buttons[0].title).toBeUndefined();
379+
expect(generic.bubbles[0].buttons[0].type).toBe('account_link');
380+
expect(generic.bubbles[0].buttons[0].url).toBe('https://example.com');
381+
});
382+
383+
it('should add a logout button', () => {
384+
generic.addBubble('Test')
385+
.addLogoutButton();
386+
387+
expect(generic.bubbles[0].buttons.length).toBe(1);
388+
expect(generic.bubbles[0].buttons[0].title).toBeUndefined();
389+
expect(generic.bubbles[0].buttons[0].type).toBe('account_unlink');
390+
});
391+
322392
it('should throw an error if you add more than 3 buttons', () => {
323393
generic
324394
.addBubble('Test');

0 commit comments

Comments
 (0)