신지원

START

Showing 77 changed files with 3095 additions and 0 deletions
1 +root = true
2 +
3 +[*]
4 +indent_style = space
5 +indent_size = 2
6 +end_of_line = lf
7 +charset = utf-8
8 +trim_trailing_whitespace = true
9 +insert_final_newline = true
10 +
11 +[*.md]
12 +trim_trailing_whitespace = false
1 +# Logs
2 +logs
3 +*.log
4 +npm-debug.log*
5 +yarn-debug.log*
6 +yarn-error.log*
7 +
8 +# Runtime data
9 +pids
10 +*.pid
11 +*.seed
12 +*.pid.lock
13 +
14 +# Directory for instrumented libs generated by jscoverage/JSCover
15 +lib-cov
16 +
17 +# Coverage directory used by tools like istanbul
18 +coverage
19 +
20 +# nyc test coverage
21 +.nyc_output
22 +
23 +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 +.grunt
25 +
26 +# Bower dependency directory (https://bower.io/)
27 +bower_components
28 +
29 +# node-waf configuration
30 +.lock-wscript
31 +
32 +# Compiled binary addons (http://nodejs.org/api/addons.html)
33 +build/Release
34 +
35 +# Dependency directories
36 +node_modules/
37 +jspm_packages/
38 +
39 +# Typescript v1 declaration files
40 +typings/
41 +
42 +# Optional npm cache directory
43 +.npm
44 +
45 +# Optional eslint cache
46 +.eslintcache
47 +
48 +# Optional REPL history
49 +.node_repl_history
50 +
51 +# Output of 'npm pack'
52 +*.tgz
53 +
54 +# Yarn Integrity file
55 +.yarn-integrity
56 +
57 +# dotenv environment variables file
58 +.env
59 +
60 +# Built files
61 +dist
62 +
63 +# Test recent request
64 +test/helpers/request.json
65 +
66 +# IDE
67 +.idea/
1 +language: node_js
2 +node_js:
3 + - 10
4 + - 12
5 + - 14
6 +before_install:
7 + - npm i -g npm@latest
8 +install:
9 + - npm ci
1 +## 7.3.0 (26 Apr 2021)
2 +
3 +### Feature
4 +* Support Flex Message Update 2 (#271)
5 +* Messaging API - January 2021 update (#277)
6 +
7 +### Misc
8 +* Add TypeScript Example (#270)
9 +* Update dependencies (#270)(#272)(#283)
10 +
11 +## 7.2.0 (18 Sep 2020)
12 +
13 +### Feature
14 +* Messaging API - December 2020 update (#268)
15 +* Messaging API - October 2020 update (#261)(#264)
16 +* update some Flex Message Update 2 (#265)
17 +
18 +### Misc
19 +* Update dependencies (#267)
20 +
21 +## 7.1.0 (18 Sep 2020)
22 +
23 +### Feature
24 +* Messaging API - August 2020 update (#240)(#251)(#258)
25 +* Messaging API - September 2020 update (#248)
26 +* Add Video viewing complete event (#241)
27 +* Channel access token v2.1 support key id (#231)
28 +* OAuth API v2.1 endpoint change (#233)
29 +
30 +### Bug fix
31 +* Accept label in richmenu area actions (#246)
32 +* Update dependencies & fix format (#234)(#236)(#238)(#243)(#250)
33 +* fix: fix createUploadAudienceGroup & updateUploadAudienceGroup API doc (#249)
34 +
35 +### Misc
36 +* Add Release CI & change release flow (#256)
37 +* Add: build doc github workflow
38 +
39 +## 7.0.0 (15 June 2020)
40 +
41 +### Breaking Changes
42 +* Node.js: drop 8 & adopt 14 (#222)
43 +
44 +### Feature
45 +* Support Channel access token v2.1 (#223)
46 +* Support Messaging API update for June 2020 (#228)
47 +* add X-Line-Retry-Key support (#224)
48 +* Support emojis in text message webhook (#218)
49 +* narrowcast api & audience apis (#193)
50 +* Add support for sticon in text messages (#214)
51 +* Add language support for profile API (#215)
52 +* Support icon-nickname-switch (#207)
53 +* Define LINE_SIGNATURE_HTTP_HEADER_NAME (#200)
54 +* add docs for getUserInteractionStatistics (#195)
55 +
56 +### Bug fixs & Feature Changes
57 +* fix getUserInteractionStatistics (#194)
58 +* type fix: accept string in the aspectRatio property of flex image and flex icon (#212)
59 +
60 +### Others
61 +* update dependencies & rewrite to promise (#225 & #229)
62 +* fix vulnerabilities (#217)
63 +* add emoji test (#198)
64 +* update vuepress to 1.x (#188)
65 +
66 +## 6.8.4 (19 Dec 2019)
67 +
68 +### Bug fix
69 +
70 +* Fix typo in type of FriendDemographics (#177)
71 +* Add label property to ImageMapAction type (#187)
72 +
73 +### Feature
74 +
75 +* Change data api's domain to api-data.line.me (#178)
76 +* Add getUserInteractionStatistics API (#183)
77 +* Add new properties in webhook types (#182)
78 +
79 +### Misc
80 +
81 +* Rewrite test in nock (#179)
82 +* Update dependencies (#180)
83 +
84 +## 6.8.3 (05 Nov 2019)
85 +
86 +### Bug fix
87 +
88 +* Add exception handler in middleware (#153)
89 +
90 +### Feature
91 +
92 +* Flex Message Update 1 (#173)
93 +* Support friend statistics API (#161)
94 +
95 +### Misc
96 +
97 +* Update dependencies (#174)
98 +
99 +## 6.8.2 (08 Aug 2019)
100 +
101 +### Bug fix
102 +
103 +* Fix LINEThings Scenario Execution Event Types (#158)
104 +
105 +## 6.8.1 (29 Jul 2019)
106 +
107 +### Bug fix
108 +
109 +* Fix a type wrong in Template Message (#163)
110 +
111 +### Feature
112 +* Get `X-LINE-Request-Id` by using `responseData['x-line-request-id']` (#151 #157)
113 +
114 +## 6.8.0 (25 Jun 2019)
115 +
116 +### Feature
117 +
118 +* Add new parameter in push/reply/multicast/broadcast API to catch up the newest bot API (#147)
119 +* Add new APIs in bot API (#147)
120 + - Get the target limit for additional messages
121 + - Get number of messages sent this month
122 + - Get number of sent broadcast messages
123 + - Send broadcast message
124 +
125 +### Breaking changes
126 +* Deprecate Node 6 and start to support Node 12 (#139)
127 +* Remove polyfills for Node 6 (#149)
128 +
129 +### Type
130 +
131 +* Add LINE Things Event (#150)
132 +
133 +### Misc
134 +* Update axios and other dependencies by running `npm audit fix` to fix vulnerabilities. (#148 #154)
135 +
136 +## 6.7.0 (18 Apr 2019)
137 +
138 +### Feature
139 +
140 +* Add alt URL field to URI action (#135)
141 +* Implement (un)linkRichMenuToMultipleUsers (#135)
142 +
143 +### Type
144 +
145 +* Fix typo in a type (#124)
146 +
147 +
148 +## 6.6.0 (4 Mar 2019)
149 +
150 +### Feature
151 +
152 +* Add DeviceLinkEvent / DeviceUnlinkEvent (#123)
153 +
154 +### Type
155 +
156 +* Fix FlexSpacer to have optional 'size' property (#122)
157 +
158 +### Misc
159 +
160 +* Run `npm audit fix` to fix minor dependency vulnerability.
161 +
162 +
163 +## 6.5.0 (16 Feb 2019)
164 +
165 +### Feature
166 +
167 +* Add APIs to get number of sent messages (#116)
168 +* Add account link event (#117)
169 +
170 +### Misc
171 +
172 +* Fix a typo in doc (#119)
173 +
174 +
175 +## 6.4.0 (19 Nov 2018)
176 +
177 +### Feature
178 +
179 +* Add `getLinkToken` API (#96)
180 +* Handle `req.rawBody` in Google Cloud Functions (#101)
181 +* [Kitchensink] Add ngrok functionality (#99)
182 +
183 +### Type
184 +
185 +* Add types for video in imagemap message (#100)
186 +* Add `contentProvider` fields to content messages (#103)
187 +* Add `destination` field to webhook request body (#102)
188 +* Add `MemberJoinEvent` and `MemberLeaveEvent` types (#107)
189 +
190 +### Misc
191 +
192 +* Don't include doc in released source
193 +* Upgrade TypeScript to 3.1.6 (#94)
194 +* Refactoring (#94, #98, #99)
195 +* Remove webhook-tester tool
196 +
197 +
198 +## 6.3.0 (21 Sep 2018)
199 +
200 +### Feature
201 +
202 +* Add default rich menu APIs (#87)
203 +
204 +### Type
205 +
206 +* Add missing `defaultAction` field to `TemplateColumn`
207 +
208 +### Misc
209 +
210 +* Use VuePress as documentation engine (#85)
211 +* Upgrade minimum supported Node.js version to 6
212 +
213 +
214 +## 6.2.1 (16 Aug 2018)
215 +
216 +### Misc
217 +
218 +* Remove gitbook-cli from dev dependencies
219 +
220 +
221 +## 6.2.0 (15 Aug 2018)
222 +
223 +#### Type
224 +
225 +* Add QuickReply types (#83)
226 +* Format type comments
227 +
228 +#### Misc
229 +
230 +* Upgrade TypeScript to 3
231 +
232 +
233 +## 6.1.1 (14 Aug 2018)
234 +
235 +#### Type
236 +
237 +* Update FlexMessage types (#81)
238 +
239 +#### Misc
240 +
241 +* Add test coverage (#78)
242 +* Add JSDoc comments (#80)
243 +
244 +
245 +## 6.1.0 (19 June 2018)
246 +
247 +#### Type
248 +
249 +* Add types for flex message (#74)
250 +* Simplify type definition for `Action`
251 +
252 +
253 +## 6.0.3 (18 June 2018)
254 +
255 +#### Misc
256 +
257 +* Move get-audio-duration dep to proper package.json (#73)
258 +* Vulnerability fix with `npm audit fix`
259 +
260 +
261 +## 6.0.2 (21 May 2018)
262 +
263 +#### Type
264 +
265 +* Add missing `displayText` field to postback action (#63)
266 +* Add missing `FileEventMessage` to `EventMessage` (#71)
267 +
268 +#### Misc
269 +
270 +* Add audio duration lib to kitchensink example (#68)
271 +
272 +
273 +## 6.0.1 (13 Mar 2018)
274 +
275 +#### Type
276 +
277 +* Fix misimplemented 'AudioMessage' type (#61)
278 +
279 +
280 +## 6.0.0 (27 Feb 2018)
281 +
282 +#### Major
283 +
284 +* Fix misimplemented 'unlinkRichMenuFromUser' API
285 +
286 +#### Type
287 +
288 +* Fix TemplateColumn type definition (#48)
289 +
290 +#### Misc
291 +
292 +* Update GitHub issue template (#43)
293 +* Add Code of Conduct (#50)
294 +* Catch errors properly in examples (#52)
295 +
296 +
297 +## 5.2.0 (11 Dec 2017)
298 +
299 +#### Minor
300 +
301 +* Set Content-Length manually for postBinary (#42)
302 +
303 +
304 +## 5.1.0 (7 Dec 2017)
305 +
306 +#### Minor
307 +
308 +* Add new fields (#39)
309 +
310 +#### Misc
311 +
312 +* Fix Windows build (#38)
313 +* Add start scripts and webhook info to examples
314 +
315 +
316 +## 5.0.1 (14 Nov 2017)
317 +
318 +#### Minor
319 +
320 +* Fix typo in `ImageMapMessage` type
321 +* Add kitchensink example (#36)
322 +
323 +
324 +## 5.0.0 (2 Nov 2017)
325 +
326 +#### Major
327 +
328 +* Implement rich menu API (#34)
329 +
330 +#### Type
331 +
332 +* Rename `ImageMapArea` and `TemplateAction`s into general ones
333 +
334 +#### Misc
335 +
336 +* Do not enforce `checkJSON` for some APIs where it means nothing
337 +* Change how to check request object in test cases
338 +
339 +
340 +## 4.0.0 (25 Oct 2017)
341 +
342 +#### Major
343 +
344 +* Make index script export exceptions and types (#31)
345 +
346 +#### Type
347 +
348 +* Simplify config types for client and middleware (#31)
349 +
350 +#### Misc
351 +
352 +* Fix information and links in doc
353 +* Use Prettier instead of TSLint (#30)
354 +* Install git hooks for precommit and prepush (#30)
355 +
356 +
357 +## 3.1.1 (19 Sep 2017)
358 +
359 +#### Type
360 +
361 +* Fix type of postback.params
362 +
363 +
364 +## 3.1.0 (19 Sep 2017)
365 +
366 +#### Major
367 +
368 +* Make middleware return `SignatureValidationFailed` for no signature (#26)
369 +
370 +#### Type
371 +
372 +* Add `FileEventMessage` type
373 +
374 +
375 +## 3.0.0 (8 Sep 2017)
376 +
377 +#### Major
378 +
379 +* Implement "Get group/room member profile" API (#15)
380 +* Implement "Get group/room member IDs" API (#23)
381 +* `getMessageContent` now returns `Promise<ReadableStream>` (#20)
382 +
383 +#### Type
384 +
385 +* Add "datetimepicker" support (#21)
386 +* Fix typo in `TemplateURIAction` type (#21)
387 +
388 +#### Misc
389 +
390 +* Package updates and corresponding fixes
391 +* Use npm 5 instead of Yarn in dev
392 +* Fix `clean` script to work in Windows
393 +* Use "axios" for internal HTTP client instead of "got" (#20)
394 +
395 +
396 +## 2.0.0 (12 June 2017)
397 +
398 +#### Type
399 +
400 +* Use literal types for 'type' fields
401 +
402 +#### Misc
403 +
404 +* Update yarn.lock with the latest Yarn
405 +
406 +
407 +## 1.1.0 (31 May 2017)
408 +
409 +* Handle pre-parsed body (string and buffer only)
410 +
411 +#### Type
412 +
413 +* Separate config type into client and middleware types
414 +* Add `userId` to group and room event sources
415 +
416 +#### Misc
417 +
418 +* Create issue template (#4)
419 +
420 +
421 +## 1.0.0 (11 May 2017)
422 +
423 +* Initial release
1 +# Contributor Covenant Code of Conduct
2 +
3 +## Our Pledge
4 +
5 +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 +
7 +## Our Standards
8 +
9 +Examples of behavior that contributes to creating a positive environment include:
10 +
11 +* Using welcoming and inclusive language
12 +* Being respectful of differing viewpoints and experiences
13 +* Gracefully accepting constructive criticism
14 +* Focusing on what is best for the community
15 +* Showing empathy towards other community members
16 +
17 +Examples of unacceptable behavior by participants include:
18 +
19 +* The use of sexualized language or imagery and unwelcome sexual attention or advances
20 +* Trolling, insulting/derogatory comments, and personal or political attacks
21 +* Public or private harassment
22 +* Publishing others' private information, such as a physical or electronic address, without explicit permission
23 +* Other conduct which could reasonably be considered inappropriate in a professional setting
24 +
25 +## Our Responsibilities
26 +
27 +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 +
29 +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 +
31 +## Scope
32 +
33 +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 +
35 +## Enforcement
36 +
37 +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at dl_oss_dev@linecorp.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 +
39 +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 +
41 +## Attribution
42 +
43 +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 +
45 +[homepage]: http://contributor-covenant.org
46 +[version]: http://contributor-covenant.org/version/1/4/
1 +# How to contribute to LINE Bot SDK for Node.js
2 +
3 +First of all, thank you so much for taking your time to contribute! LINE Bot SDK
4 +for Node.js is not very different from any other open source projects. It will
5 +be fantastic if you help us by doing any of the following:
6 +
7 +- File an issue in [the issue tracker](https://github.com/line/line-bot-sdk-nodejs/issues)
8 + to report bugs and propose new features and improvements.
9 +- Ask a question using [the issue tracker](https://github.com/line/line-bot-sdk-nodejs/issues).
10 +- Contribute your work by sending [a pull request](https://github.com/line/line-bot-sdk-nodejs/pulls).
11 +
12 +## Development
13 +
14 +You can freely fork the project, clone the forked repository, and start editing.
15 +
16 +Here are each top-level directory explained:
17 +
18 +* `lib`: TypeScript source code. You may modify files under this directory.
19 +* `test`: Mocha test suites. Please add tests for modification if possible.
20 +* `examples`: Example projects using this SDK
21 +* `docs`: [VuePress](https://vuepress.vuejs.org) markdowns for project documentation
22 +* `tools`: Useful tools
23 +
24 +Also, you may use the following npm scripts for development:
25 +
26 +* `npm run test`: Run test suites in `test`.
27 +* `npm run format`: Format source code with [Prettier](https://github.com/prettier/prettier)
28 +* `npm run format:check`: Silently run `format` and report formatting errors
29 +* `npm run build`: Build TypeScript code into JavaScript. The built files will
30 + be placed in `dist/`.
31 +* `npm run docs`: Build and serve documentation
32 +
33 +We test, lint and build on CI, but it is always nice to check them before
34 +uploading a pull request.
35 +
36 +## Contributor license agreement
37 +
38 +When you are sending a pull request and it's a non-trivial change beyond fixing typos, please make sure to sign
39 +[the ICLA (individual contributor license agreement)](https://cla-assistant.io/line/line-bot-sdk-nodejs). Please
40 +[contact us](mailto:dl_oss_dev@linecorp.com) if you need the CCLA (corporate contributor license agreement).
...\ No newline at end of file ...\ No newline at end of file
This diff is collapsed. Click to expand it.
1 +# LINE Messaging API SDK for nodejs
2 +
3 +[![Travis CI](https://travis-ci.org/line/line-bot-sdk-nodejs.svg?branch=master)](https://travis-ci.org/line/line-bot-sdk-nodejs)
4 +[![npmjs](https://badge.fury.io/js/%40line%2Fbot-sdk.svg)](https://www.npmjs.com/package/@line/bot-sdk)
5 +
6 +
7 +## Introduction
8 +The LINE Messaging API SDK for nodejs makes it easy to develop bots using LINE Messaging API, and you can create a sample bot within minutes.
9 +
10 +## Documentation
11 +
12 +See the official API documentation for more information
13 +
14 +- English: https://developers.line.biz/en/docs/messaging-api/overview/
15 +- Japanese: https://developers.line.biz/ja/docs/messaging-api/overview/
16 +
17 +line-bot-sdk-nodejs documentation: https://line.github.io/line-bot-sdk-nodejs/#getting-started
18 +
19 +## Requirements
20 +
21 +* **Node.js** 10 or higher
22 +
23 +## Installation
24 +
25 +Using [npm](https://www.npmjs.com/):
26 +
27 +``` bash
28 +$ npm install @line/bot-sdk --save
29 +```
30 +
31 +## Help and media
32 +FAQ: https://developers.line.biz/en/faq/
33 +
34 +Community Q&A: https://www.line-community.me/questions
35 +
36 +News: https://developers.line.biz/en/news/
37 +
38 +Twitter: @LINE_DEV
39 +
40 +## Versioning
41 +This project respects semantic versioning
42 +
43 +See http://semver.org/
44 +
45 +## Contributing
46 +
47 +Please check [CONTRIBUTING](CONTRIBUTING.md) before making a contribution.
48 +
49 +## License
50 +```
51 +Copyright (C) 2016 LINE Corp.
52 +
53 +Licensed under the Apache License, Version 2.0 (the "License");
54 +you may not use this file except in compliance with the License.
55 +You may obtain a copy of the License at
56 +
57 + http://www.apache.org/licenses/LICENSE-2.0
58 +
59 +Unless required by applicable law or agreed to in writing, software
60 +distributed under the License is distributed on an "AS IS" BASIS,
61 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
62 +See the License for the specific language governing permissions and
63 +limitations under the License.
64 +```
1 +module.exports = {
2 + base: "/line-bot-sdk-nodejs/",
3 + head: [
4 + ["link", { rel: "icon", href: "/favicon.ico" }]
5 + ],
6 + title: "line-bot-sdk-nodejs",
7 + description: "Node.js SDK for LINE Messaging API",
8 + themeConfig: {
9 + nav: [
10 + {
11 + text: "Introduction",
12 + link: "/"
13 + },
14 + {
15 + text: "Getting Started",
16 + link: "/getting-started"
17 + },
18 + {
19 + text: "Guide",
20 + link: "/guide"
21 + },
22 + {
23 + text: "API Reference",
24 + link: "/api-reference"
25 +
26 + },
27 + {
28 + text: "Contributing",
29 + link: "/CONTRIBUTING"
30 + },
31 + {
32 + text: "LINE Developers",
33 + link: "https://developers.line.biz/en/"
34 + },
35 + {
36 + text: "GitHub",
37 + link: "https://github.com/line/line-bot-sdk-nodejs/"
38 + },
39 + ],
40 + sidebar: [
41 + {
42 + title: "Introduction",
43 + collapsable: false,
44 + children: [
45 + "",
46 + ]
47 + },
48 + {
49 + title: "Getting Started",
50 + collapsable: false,
51 + children: [
52 + "/getting-started/requirements",
53 + "/getting-started/install",
54 + "/getting-started/basic-usage",
55 + ]
56 + },
57 + {
58 + title: "Guide",
59 + collapsable: false,
60 + children: [
61 + "/guide/webhook",
62 + "/guide/client",
63 + "/guide/typescript",
64 + ]
65 + },
66 + {
67 + title: "API Reference",
68 + collapsable: false,
69 + children: [
70 + "/api-reference/client",
71 + "/api-reference/validate-signature",
72 + "/api-reference/middleware",
73 + "/api-reference/exceptions",
74 + "/api-reference/message-and-event-objects",
75 + ]
76 + },
77 + {
78 + title: "Contributing",
79 + collapsable: false,
80 + children: [
81 + "/CONTRIBUTING",
82 + ]
83 + },
84 + ]
85 + }
86 +}
No preview for this file type
1 +../CONTRIBUTING.md
...\ No newline at end of file ...\ No newline at end of file
1 +../README.md
...\ No newline at end of file ...\ No newline at end of file
1 +# API Reference
2 +
3 +Please import the library via `require` or `import`.
4 +
5 +``` js
6 +// CommonJS
7 +const line = require('@line/bot-sdk');
8 +
9 +// ES2015 modules or TypeScript
10 +import * as line from '@line/bot-sdk';
11 +```
12 +
13 +For the detailed API reference of each, please refer to their own pages.
14 +
15 +- [Client](api-reference/client.md)
16 +- [OAuth](api-reference/oauth.md)
17 +- [validateSignature](api-reference/validate-signature.md)
18 +- [middleware](api-reference/middleware.md)
19 +- [Exceptions](api-reference/exceptions.md)
20 +- [Message and event objects](api-reference/message-and-event-objects.md)
This diff is collapsed. Click to expand it.
1 +# Exceptions
2 +
3 +Exception classes can also be imported from `@line/bot-sdk`.
4 +
5 +``` js
6 +// CommonJS (destructuring can be used for Node.js >= 6)
7 +const HTTPError = require('@line/bot-sdk').HTTPError;
8 +const JSONParseError = require('@line/bot-sdk').JSONParseError;
9 +const ReadError = require('@line/bot-sdk').ReadError;
10 +const RequestError = require('@line/bot-sdk').RequestError;
11 +const SignatureValidationFailed = require('@line/bot-sdk').SignatureValidationFailed;
12 +
13 +// ES2015 modules or TypeScript
14 +import {
15 + HTTPError,
16 + JSONParseError,
17 + ReadError,
18 + RequestError,
19 + SignatureValidationFailed,
20 +} from '@line/bot-sdk/exceptions';
21 +```
22 +
23 +#### Type signature
24 +
25 +``` typescript
26 +class SignatureValidationFailed extends Error {
27 + public signature?: string;
28 +}
29 +
30 +class JSONParseError extends Error {
31 + public raw: any;
32 +}
33 +
34 +class RequestError extends Error {
35 + public code: string; // e.g. ECONNREFUSED
36 +}
37 +
38 +class ReadError extends Error {
39 +}
40 +
41 +class HTTPError extends Error {
42 + public statusCode: number; // e.g. 404
43 + public statusMessage: string; // e.g. Not Found
44 +}
45 +```
46 +
47 +About what causes the errors and how to handle them, please refer to each guide
48 +of [webhook](../guide/webhook.md) and [client](../guide/client.md).
1 +# Message and event objects
2 +
3 +The message objects and event objects are plain JS objects with no
4 +abstraction. This SDK provides TypeScript types for them, which can be imported
5 +from `@line/bot-sdk`.
6 +
7 +Please beware that the types only work in TypeScript, and will be removed when
8 +built into JavaScript.
9 +
10 +``` typescript
11 +import {
12 + // webhook event objects
13 + WebhookEvent,
14 + MessageEvent,
15 + EventSource,
16 + VideoEventMessage,
17 +
18 + // message event objects
19 + Message,
20 + TemplateMessage,
21 + TemplateContent,
22 +} from "@line/bot-sdk";
23 +```
24 +
25 +For the actual type definitions, please refer to [types.ts](https://github.com/line/line-bot-sdk-nodejs/blob/master/lib/types.ts)
26 +directly.
27 +
28 +You can also refer to the official specification:
29 +
30 +- [Message objects](https://developers.line.biz/en/reference/messaging-api/#message-objects)
31 +- [Webhook event objects](https://developers.line.biz/en/reference/messaging-api/#webhook-event-objects)
1 +# `middleware(config)`
2 +
3 +It returns a [connect](https://github.com/senchalabs/connect) middleware used
4 +by several Node.js web frameworks such as [Express](https://expressjs.com/).
5 +
6 +#### Type signature
7 +
8 +``` typescript
9 +function middleware(config: MiddlewareConfig): Middleware
10 +```
11 +
12 +The types of `MiddlewareConfig` and `Middleware` are like below.
13 +
14 +``` typescript
15 +interface MiddlewareConfig {
16 + channelAccessToken?: string;
17 + channelSecret: string;
18 +}
19 +
20 +type Middleware =
21 + ( req: http.IncomingMessage
22 + , res: http.ServerResponse
23 + , next: (err?: Error) => void
24 + ) => void
25 +```
26 +
27 +The `Middleware` type is defined according to the connect middleware itself. For
28 +the detail of the connect middleware, please refer to the [connect](https://github.com/senchalabs/connect) documentation.
29 +
30 +## Usage
31 +
32 +A very simple example of the middleware usage with an Express app is like below:
33 +
34 +``` js
35 +// globally
36 +app.use(middleware(config))
37 +
38 +// or directly with handler
39 +app.post('/webhook', middleware(config), (req, res) => {
40 + req.body.events // webhook event objects
41 + req.body.destination // user ID of the bot (optional)
42 + ...
43 +})
44 +```
45 +
46 +The middleware returned by `middleware()` parses body and checks signature
47 +validation, so you do not need to use [`validateSignature()`](./validate-signature.md)
48 +directly.
49 +
50 +You do not need to use [body-parser](https://github.com/expressjs/body-parser)
51 +to parse webhook events, as `middleware()` embeds body-parser and parses them to
52 +objects. Please keep in mind that it will not process requests without
53 +`X-Line-Signature` header. If you have a reason to use body-parser for other
54 +routes, *please do not use it before the LINE middleware*. body-parser parses
55 +the request body up and the LINE middleware cannot parse it afterwards.
56 +
57 +``` js
58 +// don't
59 +app.use(bodyParser.json())
60 +app.use(middleware(config))
61 +
62 +// do
63 +app.use(middleware(config))
64 +app.use(bodyParser.json())
65 +```
66 +
67 +There are environments where `req.body` is pre-parsed, such as [Firebase Cloud Functions](https://firebase.google.com/docs/functions/http-events).
68 +If it parses the body into string or buffer, do not worry as the middleware will
69 +work just fine. If the pre-parsed body is an object, please use [`validateSignature()`](../api-reference/validate-signature.md)
70 +manually with the raw body.
71 +
72 +About building webhook server, please refer to [Webhook](../guide/webhook.md).
1 +# `new OAuth()`
2 +
3 +`OAuth` is a class representing OAuth APIs. It provides methods
4 +corresponding to [messaging APIs](https://developers.line.biz/en/reference/messaging-api/#issue-channel-access-token).
5 +
6 +#### Type signature
7 +
8 +``` typescript
9 +class OAuth {
10 + constructor() {}
11 +
12 + issueAccessToken(client_id: string, client_secret: string): Promise<Types.ChannelAccessToken>
13 + revokeAccessToken(access_token: string): Promise<{}>
14 + issueChannelAccessTokenV2_1(
15 + client_assertion: string,
16 + ): Promise<Types.ChannelAccessToken>
17 + getChannelAccessTokenKeyIdsV2_1(
18 + client_assertion: string,
19 + ): Promise<{ key_ids: string[] }>
20 + revokeChannelAccessTokenV2_1(
21 + client_id: string,
22 + client_secret: string,
23 + access_token: string,
24 + ): Promise<{}>
25 +}
26 +```
27 +
28 +## Create a OAuth
29 +
30 +The `OAuth` class is provided by the main module.
31 +
32 +``` js
33 +// CommonJS
34 +const { OAuth } = require('@line/bot-sdk');
35 +
36 +// ES6 modules or TypeScript
37 +import { OAuth } from '@line/bot-sdk';
38 +```
39 +
40 +To create a client instance:
41 +
42 +```js
43 +const oauth = new OAuth();
44 +```
45 +
46 +And now you can call client functions as usual:
47 +
48 +``` js
49 +const { access_token } = await oauth.issueAccessToken("client_id", "client_secret");
50 +```
51 +
52 +## Methods
53 +
54 +For functions returning `Promise`, there will be errors thrown if something
55 +goes wrong, such as HTTP errors or parsing errors. You can catch them with the
56 +`.catch()` method of the promises. The detailed error handling is explained
57 +in [the Client guide](../guide/client.md).
58 +
59 +### OAuth
60 +
61 +#### `issueAccessToken(client_id: string, client_secret: string): Promise<Types.ChannelAccessToken>`
62 +
63 +It corresponds to the [Issue channel access token](https://developers.line.biz/en/reference/messaging-api/#issue-channel-access-token) API.
64 +
65 +``` js
66 +const { access_token, expires_in, token_type } = await oauth.issueAccessToken("client_id", "client_secret");
67 +```
68 +
69 +#### `revokeAccessToken(access_token: string): Promise<{}>`
70 +
71 +It corresponds to the [Revoke channel access token](https://developers.line.biz/en/reference/messaging-api/#revoke-channel-access-token) API.
72 +
73 +
74 +``` js
75 +await oauth.revokeAccessToken("access_token");
76 +```
77 +
78 +#### issueChannelAccessTokenV2_1(client_assertion: string): Promise<Types.ChannelAccessToken>
79 +
80 +It corresponds to the [Issue channel access token v2.1](https://developers.line.biz/en/reference/messaging-api/#issue-channel-access-token-v2-1) API.
81 +
82 +#### getChannelAccessTokenKeyIdsV2_1(client_assertion: string): Promise<{ key_ids: string[] }>
83 +
84 +It corresponds to the [Get all valid channel access token key IDs v2.1](https://developers.line.biz/en/reference/messaging-api/#get-all-issued-channel-access-token-key-ids-v2-1) API.
85 +
86 +#### revokeChannelAccessTokenV2_1(client_id: string, client_secret: string, access_token: string): Promise<{}>
87 +
88 +It corresponds to the [Revoke channel access token v2.1](https://developers.line.biz/en/reference/messaging-api/#revoke-channel-access-token-v2-1) API.
1 +# `validateSignature(body, channelSecret, signature)`
2 +
3 +It is a function to check if a provided channel secret is valid, compared with a
4 +provided body.
5 +
6 +#### Type signature
7 +
8 +``` typescript
9 +function validateSignature(
10 + body: string | Buffer,
11 + channelSecret: string,
12 + signature: string,
13 +): boolean
14 +```
15 +
16 +`body` can be a string or buffer. When it's a string, it will be handled as if
17 +it's encoded in UTF-8.
18 +
19 +For more details about signature validation of LINE webhook, please refer
20 +to [the official documentation](https://developers.line.biz/en/reference/messaging-api/#webhooks).
1 +# Getting Started
2 +
3 +* [Requirements](getting-started/requirements.md)
4 +* [Install](getting-started/install.md)
5 +* [Basic Usage](getting-started/basic-usage.md)
1 +# Basic Usage
2 +
3 +It can be imported with [CommonJS](https://nodejs.org/docs/latest/api/modules.html),
4 +[ES2015 modules](https://babeljs.io/learn-es2015/#ecmascript-2015-features-modules),
5 +and preferably [TypeScript](https://www.typescriptlang.org/).
6 +
7 +The library is written in TypeScript and includes TypeScript definitions by
8 +default. Nevertheless, it can surely be used with plain JavaScript too.
9 +
10 +``` js
11 +// CommonJS
12 +const line = require('@line/bot-sdk');
13 +
14 +// ES2015 modules or TypeScript
15 +import * as line from '@line/bot-sdk';
16 +```
17 +
18 +## Configuration
19 +
20 +For the usage of webhook and client, LINE channel access token and secret are
21 +needed. About issuing the token and secret, please refer to [Getting started with the Messaging API](https://developers.line.biz/en/docs/messaging-api/getting-started/).
22 +
23 +``` js
24 +const config = {
25 + channelAccessToken: 'YOUR_CHANNEL_ACCESS_TOKEN',
26 + channelSecret: 'YOUR_CHANNEL_SECRET'
27 +};
28 +
29 +new line.Client(config);
30 +line.middleware(config);
31 +```
32 +
33 +## Synopsis
34 +
35 +Here is a synopsis of echoing webhook server with [Express](https://expressjs.com/):
36 +
37 +``` js
38 +const express = require('express');
39 +const line = require('@line/bot-sdk');
40 +
41 +const config = {
42 + channelAccessToken: 'YOUR_CHANNEL_ACCESS_TOKEN',
43 + channelSecret: 'YOUR_CHANNEL_SECRET'
44 +};
45 +
46 +const app = express();
47 +app.post('/webhook', line.middleware(config), (req, res) => {
48 + Promise
49 + .all(req.body.events.map(handleEvent))
50 + .then((result) => res.json(result));
51 +});
52 +
53 +const client = new line.Client(config);
54 +function handleEvent(event) {
55 + if (event.type !== 'message' || event.message.type !== 'text') {
56 + return Promise.resolve(null);
57 + }
58 +
59 + return client.replyMessage(event.replyToken, {
60 + type: 'text',
61 + text: event.message.text
62 + });
63 +}
64 +
65 +app.listen(3000);
66 +```
67 +
68 +The full examples with comments can be found in [examples](https://github.com/line/line-bot-sdk-nodejs/tree/master/examples/).
69 +
70 +For the specifications of API, please refer to [API Reference](../api-reference.md).
1 +# Install
2 +
3 +Please install via [npm](https://www.npmjs.com/).
4 +
5 +```bash
6 +$ npm install @line/bot-sdk
7 +```
8 +
9 +You can build from source. Please clone the repository and run the following
10 +scripts to build.
11 +
12 +``` bash
13 +$ git clone https://github.com/line/line-bot-sdk-nodejs
14 +$ cd line-bot-sdk-nodejs
15 +$ npm install
16 +$ npm run build
17 +```
18 +
19 +The built result will be placed in `dist/`.
20 +
21 +For the details of development, please refer to [Contributing](../../CONTRIBUTING.md).
1 +# Requirements
2 +
3 +* **Node.js** >= 4, preferably >=6
4 + * It uses ES2015.
5 +* [**npm**](https://www.npmjs.com/), preferably >=5
6 +
7 +Other dependencies are installed via npm, and do not need to be pre-installed.
1 +# Guide
2 +
3 +* [Webhook](guide/webhook.md)
4 +* [Client](guide/client.md)
5 +* [TypeScript](guide/typescript.md)
1 +# Client
2 +
3 +Client is to send messages, get user or content information, or leave chats.
4 +A client instance provides functions for [messaging APIs](https://developers.line.biz/en/reference/messaging-api/),
5 +so that you do not need to worry about HTTP requests and can focus on data.
6 +For type signatures of the methods, please refer to [its API reference](../api-reference/client.md).
7 +
8 +## Create a client
9 +
10 +The `Client` class is provided by the main module.
11 +
12 +``` js
13 +// CommonJS
14 +const Client = require('@line/bot-sdk').Client;
15 +
16 +// ES6 modules or TypeScript
17 +import { Client } from '@line/bot-sdk';
18 +```
19 +
20 +To create a client instance:
21 +
22 +```js
23 +const client = new Client({
24 + channelAccessToken: 'YOUR_CHANNEL_ACCESS_TOKEN',
25 + channelSecret: 'YOUR_CHANNEL_SECRET'
26 +});
27 +```
28 +
29 +And now you can call client functions as usual:
30 +
31 +``` js
32 +client.pushMessage(userId, { type: 'text', text: 'hello, world' });
33 +```
34 +
35 +## Retrieving parameters from webhook
36 +
37 +Many of data used in the client functions, such as user IDs or reply tokens, can
38 +be obtained from nowhere but webhook.
39 +
40 +Webhook event objects are just plain JSON objects, sent as request body, so you
41 +can easily access and use it.
42 +
43 +``` js
44 +const event = req.body.events[0];
45 +
46 +if (event.type === 'message') {
47 + const message = event.message;
48 +
49 + if (message.type === 'text' && message.text === 'bye') {
50 + if (event.source.type === 'room') {
51 + client.leaveRoom(event.source.roomId);
52 + } else if (event.source.type === 'group') {
53 + client.leaveGroup(event.source.groupId);
54 + } else {
55 + client.replyMessage(event.replyToken, {
56 + type: 'text',
57 + text: 'I cannot leave a 1-on-1 chat!',
58 + });
59 + }
60 + }
61 +}
62 +```
63 +
64 +For more detail of building webhook and retrieve event objects, please refer to
65 +its [guide](./webhook.html).
66 +
67 +## Error handling
68 +
69 +There are 4 types of errors caused by client usage.
70 +
71 +- `RequestError`: A request fails by, for example, wrong domain or server
72 + refusal.
73 +- `ReadError`: Reading from a response pipe fails.
74 +- `HTTPError`: Server returns a non-2xx response.
75 +- `JSONParseError`: JSON parsing fails for response body.
76 +
77 +For methods returning `Promise`, you can handle the errors with [`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
78 +method. For others returning `ReadableStream`, you can observe the `'error'`
79 +event for the stream.
80 +
81 +``` js
82 +client
83 + .replyMessage(replyToken, message)
84 + .catch((err) => {
85 + if (err instanceof HTTPError) {
86 + console.error(err.statusCode);
87 + }
88 + });
89 +
90 +const stream = client.getMessageContent(messageId);
91 +stream.on('error', (err) => {
92 + console.log(err.message);
93 +});
94 +```
95 +
96 +You can check which method returns `Promise` or `ReadableStream` in the API
97 +reference of [`Client`](../api-reference/client.md). For type signatures of the
98 +errors above, please refer to [Exceptions](../api-reference/exceptions.md).
1 +# TypeScript
2 +
3 +[TypeScript](https://www.typescriptlang.org/) is a statically typed language
4 +that compiled to plain JavaScript. As you may already have found, This library
5 +is written in TypeScript.
6 +
7 +When installed via npm, the built JavaScript files are already included and
8 +you do not need to worry about TypeScript, but it may be nice to consider
9 +using TypeScript for implement what you need.
10 +
11 +## What's good about using TypeScript
12 +
13 +It provides a default type set for mostly used objects in webhook and client
14 +and prevent possible typo and mistakes.
15 +
16 +``` typescript
17 +const config = {
18 + channelAccessToken: "", // typo Token
19 + channelSecret: "",
20 +}
21 +
22 +const c = new Client(config) // will throw a compile error
23 +```
24 +
25 +Also, when building a complex message object, you can make use of types for
26 +its fields.
27 +
28 +``` typescript
29 +const message: TemplateMessage = {
30 + type: "template",
31 + altText: "cannot display template message",
32 + template: {
33 + type: "carousel",
34 + columns: [ {
35 + text: "col1",
36 + title: "Column 1",
37 + actions: [ {
38 + type: "message",
39 + label: "send message",
40 + text: "hi, there",
41 + } ],
42 + } ],
43 + },
44 +}
45 +```
46 +
47 +The object above will be type-checked to have the type of
48 +`TemplateMessage`, and thus ensured not to miss any required field.
49 +
50 +Also, [literal type](https://www.typescriptlang.org/docs/handbook/advanced-types.html)
51 +is used for `type` fields, which means the compiler will complain if a wrong
52 +type string is used, and also inference the type of objects by its `type` field.
53 +
54 +## How to use
55 +
56 +The library is built to just-work with TypeScript too, so import the library and
57 +there you go.
58 +
59 +``` typescript
60 +import {
61 + // main APIs
62 + Client,
63 + middleware,
64 +
65 + // exceptions
66 + JSONParseError,
67 + SignatureValidationFailed,
68 +
69 + // types
70 + TemplateMessage,
71 + WebhookEvent,
72 +} from "@line/bot-sdk";
73 +```
74 +
75 +Message object and webhook event types can be also imported from `@line/bot-sdk`,
76 +e.g. `TemplateMessage` or `WebhookEvent`. For declarations of the types, please
77 +refer to [types.ts](https://github.com/line/line-bot-sdk-nodejs/blob/master/lib/types.ts).
1 +# Webhook
2 +
3 +A webhook server for LINE messaging API is just a plain HTTP(S) server. When
4 +there is a observable user event, an HTTP request will be sent to a
5 +pre-configured webhook server.
6 +
7 +About configuration of webhook itself, please refer to [Webhook](https://developers.line.biz/en/reference/messaging-api/#webhooks)
8 +of the official document.
9 +
10 +## What a webhook server should do
11 +
12 +- [Signature validation](https://developers.line.biz/en/reference/messaging-api/#signature-validation)
13 +- [Webhook event object parsing](https://developers.line.biz/en/reference/messaging-api/#webhook-event-objects)
14 +
15 +**Signature validation** is checking if a request is actually sent from real
16 +LINE servers, not a fraud. The validation is conducted by checking
17 +the [X-Line-Signature](https://developers.line.biz/en/reference/messaging-api/#signature-validation) header
18 +and request body. There is a [`validateSignature()`](../api-reference/validate-signature.md)
19 +function to do this.
20 +
21 +**Webhook event object parsing** is literally parsing webhook event objects,
22 +which contains information of each webhook event. The objects are provided as
23 +request body in JSON format, so any body parser will work here. For interal
24 +object types in this SDK, please refer to [Message and event objects](../api-reference/message-and-event-objects.md).
25 +
26 +There is a function to generate a [connect](https://github.com/senchalabs/connect) middleware,
27 +[`middleware()`](../api-reference/middleware.md), to conduct both of them. If
28 +your server can make use of connect middlewares, such as [Express](https://expressjs.com/),
29 +using the middleware is a recommended way to build a webhook server.
30 +
31 +## Build a webhook server with Express
32 +
33 +[Express](https://expressjs.com/) is a minimal web framework for Node.js, which
34 +is widely used in Node.js communities. You can surely build a webhook server
35 +with any web framework, but we use Express as an example here for its
36 +popularity.
37 +
38 +We skip the detailed guide for Express. If more information is needed about
39 +Express, please refer to its documentation.
40 +
41 +Here is an example of an HTTP server built with Express.
42 +
43 +``` js
44 +const express = require('express')
45 +
46 +const app = express()
47 +
48 +app.post('/webhook', (req, res) => {
49 + res.json({})
50 +})
51 +
52 +app.listen(8080)
53 +```
54 +
55 +The server above listens to 8080 and will response with an empty object for
56 +`POST /webhook`. We will add webhook functionality to this server.
57 +
58 +``` js
59 +const express = require('express')
60 +const middleware = require('@line/bot-sdk').middleware
61 +
62 +const app = express()
63 +
64 +const config = {
65 + channelAccessToken: 'YOUR_CHANNEL_ACCESS_TOKEN',
66 + channelSecret: 'YOUR_CHANNEL_SECRET'
67 +}
68 +
69 +app.post('/webhook', middleware(config), (req, res) => {
70 + req.body.events // webhook event objects
71 + req.body.destination // user ID of the bot (optional)
72 + ...
73 +})
74 +
75 +app.listen(8080)
76 +```
77 +
78 +We have imported `middleware` from the package and make the Express app to use
79 +the middleware. The middlware validates the request and parses webhook event
80 +object. It embeds body-parser and parses them to objects. If you have a reason
81 +to use another body-parser separately for other routes, please keep in mind the
82 +followings.
83 +
84 +### Do not use the webhook `middleware()` for other usual routes
85 +
86 +``` js
87 +// don't
88 +app.use(middleware(config))
89 +
90 +// do
91 +app.use('/webhook', middleware(config))
92 +```
93 +
94 +The middleware will throw an exception when the [X-Line-Signature](https://developers.line.biz/en/reference/messaging-api/#signature-validation)
95 +header is not set. If you want to handle usual user requests, the middleware
96 +shouldn't be used for them.
97 +
98 +### Do not use another body-parser before the webhook `middleware()`
99 +
100 +``` js
101 +// don't
102 +app.use(bodyParser.json())
103 +app.use('/webhook', middleware(config))
104 +
105 +// do
106 +app.use('/webhook', middleware(config))
107 +app.use(bodyParser.json())
108 +```
109 +
110 +If another body parser already parsed a request's body, the webhook middleware
111 +cannot access to the raw body of the request. The raw body should be retrieved
112 +for signature validation.
113 +
114 +However, there are environments where `req.body` is pre-parsed, such as
115 +[Firebase Cloud Functions](https://firebase.google.com/docs/functions/http-events).
116 +If it parses the body into string or buffer, the middleware will use the body
117 +as it is and work just fine. If the pre-parsed body is an object, the webhook
118 +middleware will fail to work. In the case, please use [`validateSignature()`](../api-reference/validate-signature.md)
119 +manually with raw body.
120 +
121 +## Error handling
122 +
123 +There are two types of errors thrown by the middleware, one is `SignatureValidationFailed`
124 +and the other is `JSONParseError`.
125 +
126 +- `SignatureValidationFailed` is thrown when a request doesn't have a signature.
127 +- `SignatureValidationFailed` is thrown when a request has a wrong signature.
128 +- `JSONParseError` occurs when a request body cannot be parsed as JSON.
129 +
130 +For type references of the errors, please refer to [the API reference](../api-reference/exceptions.md).
131 +
132 +The errors can be handled with [error middleware](https://github.com/senchalabs/connect#error-middleware).
133 +
134 +``` js
135 +const express = require('express')
136 +const middleware = require('@line/bot-sdk').middleware
137 +const JSONParseError = require('@line/bot-sdk').JSONParseError
138 +const SignatureValidationFailed = require('@line/bot-sdk').SignatureValidationFailed
139 +
140 +const app = express()
141 +
142 +const config = {
143 + channelAccessToken: 'YOUR_CHANNEL_ACCESS_TOKEN',
144 + channelSecret: 'YOUR_CHANNEL_SECRET'
145 +}
146 +
147 +app.use(middleware(config))
148 +
149 +app.post('/webhook', (req, res) => {
150 + res.json(req.body.events) // req.body will be webhook event object
151 +})
152 +
153 +app.use((err, req, res, next) => {
154 + if (err instanceof SignatureValidationFailed) {
155 + res.status(401).send(err.signature)
156 + return
157 + } else if (err instanceof JSONParseError) {
158 + res.status(400).send(err.raw)
159 + return
160 + }
161 + next(err) // will throw default 500
162 +})
163 +
164 +app.listen(8080)
165 +```
166 +
167 +## HTTPS
168 +
169 +The webhook URL should have HTTPS protocol. There are several ways to build an
170 +HTTPS server. For example, here is a [documentation](https://expressjs.com/en/api.html#app.listen)
171 +of making Express work with HTTPS. You can also set HTTPS in web servers like
172 +[NGINX](https://www.nginx.com/). This guide will not cover HTTPS configuration,
173 +but do not forget to set HTTPS beforehand.
174 +
175 +For development and test usages, [ngrok](https://ngrok.com/) works perfectly.
1 +# Dependencies
2 +node_modules/
3 +
4 +# Built files.
5 +dist/
1 +# LINE Echo Bot with TypeScript
2 +
3 +An example LINE bot to echo message with TypeScript. The bot is coded according to TypeScript's best practices.
4 +
5 +## Prerequisite
6 +
7 +- Git
8 +- Node.js version 10 and up
9 +- Heroku CLI (optional)
10 +- LINE Developers Account for the bot
11 +
12 +## Installation
13 +
14 +- Clone the repository.
15 +
16 +```bash
17 +git clone https://github.com/line/line-bot-sdk-nodejs.git
18 +```
19 +
20 +- Change directory to the example.
21 +
22 +```bash
23 +cd line-bot-sdk-nodejs/examples/echo-bot-ts
24 +```
25 +
26 +- Install all dependencies.
27 +
28 +```bash
29 +npm install
30 +```
31 +
32 +- Configure all of the environment variables.
33 +
34 +```bash
35 +export CHANNEL_ACCESS_TOKEN=<YOUR_CHANNEL_ACCESS_TOKEN>
36 +export CHANNEL_SECRET=<YOUR_CHANNEL_SECRET>
37 +export PORT=<YOUR_PORT>
38 +```
39 +
40 +- Setup your webhook URL in your LINE Official Account to be in the following format. Don't forget to disable the greeting messages and auto-response messages for convenience.
41 +
42 +```bash
43 +https://example-url.com/webhook
44 +```
45 +
46 +- Compile the TypeScript files.
47 +
48 +```bash
49 +npm run build
50 +```
51 +
52 +- Run the application.
53 +
54 +```bash
55 +npm start
56 +```
57 +
58 +## Alternative Installation
59 +
60 +If you want to deploy it via Heroku, it is also possible and is even easier for testing purposes.
61 +
62 +- Clone the repository.
63 +
64 +```bash
65 +git clone https://github.com/line/line-bot-sdk-nodejs.git
66 +```
67 +
68 +- Change directory to the example.
69 +
70 +```bash
71 +cd line-bot-sdk-nodejs/examples/echo-bot-ts
72 +```
73 +
74 +- Create a Heroku application.
75 +
76 +```bash
77 +git init
78 +heroku create <YOUR_APP_NAME> # Do not specify for a random name
79 +```
80 +
81 +- Setup the environment variables, and don't forget to setup your webhook URL (from the Heroku application that you have just created) in your LINE Offical Account. The webhook URL will still accept the following format: `https://example-url.com.herokuapp.com/webhook`.
82 +
83 +```bash
84 +heroku config:set CHANNEL_ACCESS_TOKEN=YOUR_CHANNEL_ACCESS_TOKEN
85 +heroku config:set CHANNEL_SECRET=YOUR_CHANNEL_SECRET
86 +```
87 +
88 +- Push the application to the server.
89 +
90 +```bash
91 +git add .
92 +git commit -m "Initial commit for Heroku testing"
93 +git push heroku master
94 +```
95 +
96 +- Open your application.
97 +
98 +```bash
99 +heroku open
100 +```
101 +
102 +- Done!
1 +declare global {
2 + namespace NodeJS {
3 + interface ProcessEnv {
4 + CHANNEL_ACCESS_TOKEN: string;
5 + CHANNEL_SECRET: string;
6 + PORT: string;
7 + }
8 + }
9 +}
10 +
11 +export {};
1 +// Import all dependencies, mostly using destructuring for better view.
2 +import { ClientConfig, Client, middleware, MiddlewareConfig, WebhookEvent, TextMessage, MessageAPIResponseBase } from '@line/bot-sdk';
3 +import express, { Application, Request, Response } from 'express';
4 +
5 +// Setup all LINE client and Express configurations.
6 +const clientConfig: ClientConfig = {
7 + channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
8 + channelSecret: process.env.CHANNEL_SECRET,
9 +};
10 +
11 +const middlewareConfig: MiddlewareConfig = {
12 + channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
13 + channelSecret: process.env.CHANNEL_SECRET || '',
14 +};
15 +
16 +const PORT = process.env.PORT || 3000;
17 +
18 +// Create a new LINE SDK client.
19 +const client = new Client(clientConfig);
20 +
21 +// Create a new Express application.
22 +const app: Application = express();
23 +
24 +// Function handler to receive the text.
25 +const textEventHandler = async (event: WebhookEvent): Promise<MessageAPIResponseBase | undefined> => {
26 + // Process all variables here.
27 + if (event.type !== 'message' || event.message.type !== 'text') {
28 + return;
29 + }
30 +
31 + // Process all message related variables here.
32 + const { replyToken } = event;
33 + const { text } = event.message;
34 +
35 + // Create a new message.
36 + const response: TextMessage = {
37 + type: 'text',
38 + text,
39 + };
40 +
41 + // Reply to the user.
42 + await client.replyMessage(replyToken, response);
43 +};
44 +
45 +// Register the LINE middleware.
46 +// As an alternative, you could also pass the middleware in the route handler, which is what is used here.
47 +// app.use(middleware(middlewareConfig));
48 +
49 +// Route handler to receive webhook events.
50 +// This route is used to receive connection tests.
51 +app.get(
52 + '/',
53 + async (_: Request, res: Response): Promise<Response> => {
54 + return res.status(200).json({
55 + status: 'success',
56 + message: 'Connected successfully!',
57 + });
58 + }
59 +);
60 +
61 +// This route is used for the Webhook.
62 +app.post(
63 + '/webhook',
64 + middleware(middlewareConfig),
65 + async (req: Request, res: Response): Promise<Response> => {
66 + const events: WebhookEvent[] = req.body.events;
67 +
68 + // Process all of the received events asynchronously.
69 + const results = await Promise.all(
70 + events.map(async (event: WebhookEvent) => {
71 + try {
72 + await textEventHandler(event);
73 + } catch (err: unknown) {
74 + if (err instanceof Error) {
75 + console.error(err);
76 + }
77 +
78 + // Return an error message.
79 + return res.status(500).json({
80 + status: 'error',
81 + });
82 + }
83 + })
84 + );
85 +
86 + // Return a successfull message.
87 + return res.status(200).json({
88 + status: 'success',
89 + results,
90 + });
91 + }
92 +);
93 +
94 +// Create a server and listen to it.
95 +app.listen(PORT, () => {
96 + console.log(`Application is live and listening on port ${PORT}`);
97 +});
This diff is collapsed. Click to expand it.
1 +{
2 + "name": "echo-bot-ts",
3 + "version": "0.0.0",
4 + "description": "An example LINE bot with TypeScript made to echo messages",
5 + "main": "./dist/index.js",
6 + "scripts": {
7 + "clean": "rimraf ./dist",
8 + "build": "npm run clean && tsc",
9 + "start": "node dist/index.js"
10 + },
11 + "author": "Nicholas Dwiarto <nicholasdwiarto@yahoo.com> (https://nicholasdw.com)",
12 + "dependencies": {
13 + "@line/bot-sdk": "^7.2.0",
14 + "express": "^4.17.1"
15 + },
16 + "devDependencies": {
17 + "@types/express": "^4.17.9",
18 + "@types/node": "^14.14.14",
19 + "rimraf": "^3.0.2",
20 + "typescript": "^4.1.3"
21 + }
22 +}
1 +{
2 + "compilerOptions": {
3 + /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 +
5 + /* Basic Options */
6 + // "incremental": true, /* Enable incremental compilation */
7 + "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8 + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9 + // "lib": [], /* Specify library files to be included in the compilation. */
10 + // "allowJs": true, /* Allow javascript files to be compiled. */
11 + // "checkJs": true, /* Report errors in .js files. */
12 + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
13 + // "declaration": true, /* Generates corresponding '.d.ts' file. */
14 + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 + // "sourceMap": true, /* Generates corresponding '.map' file. */
16 + // "outFile": "./", /* Concatenate and emit output to single file. */
17 + "outDir": "./dist", /* Redirect output structure to the directory. */
18 + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 + // "composite": true, /* Enable project compilation */
20 + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 + // "removeComments": true, /* Do not emit comments to output. */
22 + // "noEmit": true, /* Do not emit outputs. */
23 + // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 +
27 + /* Strict Type-Checking Options */
28 + "strict": true, /* Enable all strict type-checking options. */
29 + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
30 + // "strictNullChecks": true, /* Enable strict null checks. */
31 + // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 +
37 + /* Additional Checks */
38 + // "noUnusedLocals": true, /* Report errors on unused locals. */
39 + // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
43 +
44 + /* Module Resolution Options */
45 + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
46 + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
47 + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
48 + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
49 + // "typeRoots": [], /* List of folders to include type definitions from. */
50 + // "types": [], /* Type declaration files to be included in compilation. */
51 + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
52 + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
53 + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
54 + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
55 +
56 + /* Source Map Options */
57 + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
58 + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
59 + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
60 + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
61 +
62 + /* Experimental Options */
63 + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
64 + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
65 +
66 + /* Advanced Options */
67 + "skipLibCheck": true, /* Skip type checking of declaration files. */
68 + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
69 + },
70 + "exclude": ["node_modules"]
71 +}
1 +# Echo Bot
2 +
3 +An example LINE bot just to echo messages
4 +
5 +## How to use
6 +
7 +### Install deps
8 +
9 +``` shell
10 +$ npm install
11 +```
12 +
13 +### Configuration
14 +
15 +``` shell
16 +$ export CHANNEL_SECRET=YOUR_CHANNEL_SECRET
17 +$ export CHANNEL_ACCESS_TOKEN=YOUR_CHANNEL_ACCESS_TOKEN
18 +$ export PORT=1234
19 +```
20 +
21 +### Run
22 +
23 +``` shell
24 +$ node .
25 +```
26 +
27 +## Webhook URL
28 +
29 +```
30 +https://your.base.url/callback
31 +```
1 +'use strict';
2 +
3 +const line = require('@line/bot-sdk');
4 +const express = require('express');
5 +
6 +// create LINE SDK config from env variables
7 +const config = {
8 + channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
9 + channelSecret: process.env.CHANNEL_SECRET,
10 +};
11 +
12 +// create LINE SDK client
13 +const client = new line.Client(config);
14 +
15 +// create Express app
16 +// about Express itself: https://expressjs.com/
17 +const app = express();
18 +
19 +// register a webhook handler with middleware
20 +// about the middleware, please refer to doc
21 +app.post('/callback', line.middleware(config), (req, res) => {
22 + Promise
23 + .all(req.body.events.map(handleEvent))
24 + .then((result) => res.json(result))
25 + .catch((err) => {
26 + console.error(err);
27 + res.status(500).end();
28 + });
29 +});
30 +
31 +// event handler
32 +function handleEvent(event) {
33 + if (event.type !== 'message' || event.message.type !== 'text') {
34 + // ignore non-text-message event
35 + return Promise.resolve(null);
36 + }
37 +
38 + // create a echoing text message
39 + const echo = { type: 'text', text: event.message.text };
40 +
41 + // use reply API
42 + return client.replyMessage(event.replyToken, echo);
43 +}
44 +
45 +// listen on port
46 +const port = process.env.PORT || 3000;
47 +app.listen(port, () => {
48 + console.log(`listening on ${port}`);
49 +});
This diff is collapsed. Click to expand it.
1 +{
2 + "name": "echo-bot",
3 + "version": "0.0.0",
4 + "description": "An example LINE bot just to echo messages",
5 + "main": "index.js",
6 + "scripts": {
7 + "start": "node ."
8 + },
9 + "dependencies": {
10 + "@line/bot-sdk": "^6.8.0",
11 + "express": "^4.16.3"
12 + }
13 +}
No preview for this file type
1 +/downloaded/*
2 +!/downloaded/.gitkeep
1 +# Kitchen Sink Bot
2 +
3 +A kitchen-sink LINE bot example
4 +
5 +## Requirements
6 +
7 +Install npm dependencies:
8 +
9 +```bash
10 +npm run build-sdk # build SDK installed from local directory
11 +npm install
12 +```
13 +
14 +Also, FFmpeg and ImageMagick should be installed to test image and video
15 +echoing.
16 +
17 +### About local dependencies
18 +
19 +Currently, [`@line/bot-sdk`](package.json) is installed from local directory.
20 +
21 +```json
22 +{
23 + "@line/bot-sdk": "../../"
24 +}
25 +```
26 +
27 +To install `@line/bot-sdk` from npm, please update the line with the following:
28 +
29 +```json
30 +{
31 + "@line/bot-sdk": "*"
32 +}
33 +```
34 +
35 +In the case, `npm run build-sdk` needn't be run before `npm install`.
36 +
37 +## Configuration
38 +
39 +Configuration can be done via environment variables.
40 +
41 +```bash
42 +export CHANNEL_SECRET=YOUR_CHANNEL_SECRET
43 +export CHANNEL_ACCESS_TOKEN=YOUR_CHANNEL_ACCESS_TOKEN
44 +export BASE_URL=https://your.base.url # for static file serving
45 +export PORT=1234
46 +```
47 +
48 +The code above is an example of Bash. It may differ in other shells.
49 +
50 +## Run webhook server
51 +
52 +```bash
53 +npm start
54 +```
55 +
56 +With the configuration above, the webhook listens on `https://your.base.url:1234/callback`.
57 +
58 +## ngrok usage
59 +
60 +[ngrok](https://ngrok.com/) tunnels extenral requests to localhost, helps
61 +debugging local webhooks.
62 +
63 +This example includes ngrok inside, and it just works if no `BASE_URL` is
64 +set. Make sure that other configurations are set correctly.
65 +
66 +```
67 +❯ npm start
68 +
69 +...
70 +
71 +It seems that BASE_URL is not set. Connecting to ngrok...
72 +listening on https://ffffffff.ngrok.io/callback
73 +```
74 +
75 +The URL can be directly registered as the webhook URL in LINE Developers
76 +console.
This diff is collapsed. Click to expand it.
This diff could not be displayed because it is too large.
1 +{
2 + "name": "kitchensink",
3 + "version": "0.0.0",
4 + "description": "A kitchen-sink LINE bot example",
5 + "main": "index.js",
6 + "scripts": {
7 + "build-sdk": "cd ../../; npm i; npm run build",
8 + "start": "node ."
9 + },
10 + "dependencies": {
11 + "@line/bot-sdk": "../../",
12 + "express": "^4.17.1",
13 + "ngrok": "^3.2.7"
14 + }
15 +}
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
This diff is collapsed. Click to expand it.
1 +export const MESSAGING_API_PREFIX = `https://api.line.me/v2/bot`;
2 +export const DATA_API_PREFIX = `https://api-data.line.me/v2/bot`;
3 +export const OAUTH_BASE_PREFIX = `https://api.line.me/v2/oauth`;
4 +export const OAUTH_BASE_PREFIX_V2_1 = `https://api.line.me/oauth2/v2.1`;
1 +export class SignatureValidationFailed extends Error {
2 + constructor(message: string, public signature?: string) {
3 + super(message);
4 + }
5 +}
6 +
7 +export class JSONParseError extends Error {
8 + constructor(message: string, public raw: any) {
9 + super(message);
10 + }
11 +}
12 +
13 +export class RequestError extends Error {
14 + constructor(
15 + message: string,
16 + public code: string,
17 + private originalError: Error,
18 + ) {
19 + super(message);
20 + }
21 +}
22 +
23 +export class ReadError extends Error {
24 + constructor(private originalError: Error) {
25 + super(originalError.message);
26 + }
27 +}
28 +
29 +export class HTTPError extends Error {
30 + constructor(
31 + message: string,
32 + public statusCode: number,
33 + public statusMessage: string,
34 + public originalError: any,
35 + ) {
36 + super(message);
37 + }
38 +}
1 +import axios, {
2 + AxiosInstance,
3 + AxiosError,
4 + AxiosResponse,
5 + AxiosRequestConfig,
6 +} from "axios";
7 +import { Readable } from "stream";
8 +import { HTTPError, ReadError, RequestError } from "./exceptions";
9 +import * as fileType from "file-type";
10 +import * as qs from "querystring";
11 +
12 +const pkg = require("../package.json");
13 +
14 +interface httpClientConfig extends Partial<AxiosRequestConfig> {
15 + baseURL?: string;
16 + defaultHeaders?: any;
17 + responseParser?: <T>(res: AxiosResponse) => T;
18 +}
19 +
20 +export default class HTTPClient {
21 + private instance: AxiosInstance;
22 + private config: httpClientConfig;
23 +
24 + constructor(config: httpClientConfig = {}) {
25 + this.config = config;
26 + const { baseURL, defaultHeaders } = config;
27 + this.instance = axios.create({
28 + baseURL,
29 + headers: Object.assign({}, defaultHeaders, {
30 + "User-Agent": `${pkg.name}/${pkg.version}`,
31 + }),
32 + });
33 +
34 + this.instance.interceptors.response.use(
35 + res => res,
36 + err => Promise.reject(this.wrapError(err)),
37 + );
38 + }
39 +
40 + public async get<T>(url: string, params?: any): Promise<T> {
41 + const res = await this.instance.get(url, { params });
42 + return res.data;
43 + }
44 +
45 + public async getStream(url: string, params?: any): Promise<Readable> {
46 + const res = await this.instance.get(url, {
47 + params,
48 + responseType: "stream",
49 + });
50 + return res.data as Readable;
51 + }
52 +
53 + public async post<T>(
54 + url: string,
55 + body?: any,
56 + config?: Partial<AxiosRequestConfig>,
57 + ): Promise<T> {
58 + const res = await this.instance.post(url, body, {
59 + headers: {
60 + "Content-Type": "application/json",
61 + ...(config && config.headers),
62 + },
63 + ...config,
64 + });
65 +
66 + return this.responseParse(res);
67 + }
68 +
69 + private responseParse(res: AxiosResponse) {
70 + const { responseParser } = this.config;
71 + if (responseParser) return responseParser(res);
72 + else return res.data;
73 + }
74 +
75 + public async put<T>(
76 + url: string,
77 + body?: any,
78 + config?: Partial<AxiosRequestConfig>,
79 + ): Promise<T> {
80 + const res = await this.instance.put(url, body, {
81 + headers: {
82 + "Content-Type": "application/json",
83 + ...(config && config.headers),
84 + },
85 + ...config,
86 + });
87 +
88 + return this.responseParse(res);
89 + }
90 +
91 + public async postForm<T>(url: string, body?: any): Promise<T> {
92 + const res = await this.instance.post(url, qs.stringify(body), {
93 + headers: { "Content-Type": "application/x-www-form-urlencoded" },
94 + });
95 +
96 + return res.data;
97 + }
98 +
99 + public async toBuffer(data: Buffer | Readable) {
100 + if (Buffer.isBuffer(data)) {
101 + return data;
102 + } else if (data instanceof Readable) {
103 + return await new Promise<Buffer>((resolve, reject) => {
104 + const buffers: Buffer[] = [];
105 + let size = 0;
106 + data.on("data", (chunk: Buffer) => {
107 + buffers.push(chunk);
108 + size += chunk.length;
109 + });
110 + data.on("end", () => resolve(Buffer.concat(buffers, size)));
111 + data.on("error", reject);
112 + });
113 + } else {
114 + throw new Error("invalid data type for binary data");
115 + }
116 + }
117 +
118 + public async postBinary<T>(
119 + url: string,
120 + data: Buffer | Readable,
121 + contentType?: string,
122 + ): Promise<T> {
123 + const buffer = await this.toBuffer(data);
124 +
125 + const res = await this.instance.post(url, buffer, {
126 + headers: {
127 + "Content-Type": contentType || (await fileType.fromBuffer(buffer)).mime,
128 + "Content-Length": buffer.length,
129 + },
130 + });
131 +
132 + return res.data;
133 + }
134 +
135 + public async delete<T>(url: string, params?: any): Promise<T> {
136 + const res = await this.instance.delete(url, { params });
137 + return res.data;
138 + }
139 +
140 + private wrapError(err: AxiosError): Error {
141 + if (err.response) {
142 + return new HTTPError(
143 + err.message,
144 + err.response.status,
145 + err.response.statusText,
146 + err,
147 + );
148 + } else if (err.code) {
149 + return new RequestError(err.message, err.code, err);
150 + } else if (err.config) {
151 + // unknown, but from axios
152 + return new ReadError(err);
153 + }
154 +
155 + // otherwise, just rethrow
156 + return err;
157 + }
158 +}
1 +import Client, { OAuth } from "./client";
2 +import middleware from "./middleware";
3 +import validateSignature from "./validate-signature";
4 +
5 +export { Client, middleware, validateSignature, OAuth };
6 +
7 +// re-export exceptions and types
8 +export * from "./exceptions";
9 +export * from "./types";
1 +import { raw } from "body-parser";
2 +import * as http from "http";
3 +import { JSONParseError, SignatureValidationFailed } from "./exceptions";
4 +import * as Types from "./types";
5 +import validateSignature from "./validate-signature";
6 +
7 +export type Request = http.IncomingMessage & { body: any };
8 +export type Response = http.ServerResponse;
9 +export type NextCallback = (err?: Error) => void;
10 +
11 +export type Middleware = (
12 + req: Request,
13 + res: Response,
14 + next: NextCallback,
15 +) => void | Promise<void>;
16 +
17 +function isValidBody(body?: any): body is string | Buffer {
18 + return (body && typeof body === "string") || Buffer.isBuffer(body);
19 +}
20 +
21 +export default function middleware(config: Types.MiddlewareConfig): Middleware {
22 + if (!config.channelSecret) {
23 + throw new Error("no channel secret");
24 + }
25 +
26 + const secret = config.channelSecret;
27 +
28 + const _middleware: Middleware = async (req, res, next) => {
29 + // header names are lower-cased
30 + // https://nodejs.org/api/http.html#http_message_headers
31 + const signature = req.headers[
32 + Types.LINE_SIGNATURE_HTTP_HEADER_NAME
33 + ] as string;
34 +
35 + if (!signature) {
36 + next(new SignatureValidationFailed("no signature"));
37 + return;
38 + }
39 +
40 + const body = await (async (): Promise<string | Buffer> => {
41 + if (isValidBody((req as any).rawBody)) {
42 + // rawBody is provided in Google Cloud Functions and others
43 + return (req as any).rawBody;
44 + } else if (isValidBody(req.body)) {
45 + return req.body;
46 + } else {
47 + // body may not be parsed yet, parse it to a buffer
48 + return new Promise<Buffer>((resolve, reject) =>
49 + raw({ type: "*/*" })(req as any, res as any, (error: Error) =>
50 + error ? reject(error) : resolve(req.body),
51 + ),
52 + );
53 + }
54 + })();
55 +
56 + if (!validateSignature(body, secret, signature)) {
57 + next(
58 + new SignatureValidationFailed("signature validation failed", signature),
59 + );
60 + return;
61 + }
62 +
63 + const strBody = Buffer.isBuffer(body) ? body.toString() : body;
64 +
65 + try {
66 + req.body = JSON.parse(strBody);
67 + next();
68 + } catch (err) {
69 + next(new JSONParseError(err.message, strBody));
70 + }
71 + };
72 + return (req, res, next): void => {
73 + (<Promise<void>>_middleware(req, res, next)).catch(next);
74 + };
75 +}
This diff is collapsed. Click to expand it.
1 +import { JSONParseError } from "./exceptions";
2 +import * as FormData from "form-data";
3 +
4 +export function toArray<T>(maybeArr: T | T[]): T[] {
5 + return Array.isArray(maybeArr) ? maybeArr : [maybeArr];
6 +}
7 +
8 +export function ensureJSON<T>(raw: T): T {
9 + if (typeof raw === "object") {
10 + return raw;
11 + } else {
12 + throw new JSONParseError("Failed to parse response body as JSON", raw);
13 + }
14 +}
15 +
16 +export function createMultipartFormData(
17 + this: FormData | void,
18 + formBody: Record<string, any>,
19 +): FormData {
20 + const formData = this instanceof FormData ? this : new FormData();
21 + Object.entries(formBody).forEach(([key, value]) => {
22 + if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
23 + formData.append(key, value);
24 + } else {
25 + formData.append(key, String(value));
26 + }
27 + });
28 + return formData;
29 +}
1 +import { createHmac, timingSafeEqual } from "crypto";
2 +
3 +function s2b(str: string, encoding: BufferEncoding): Buffer {
4 + return Buffer.from(str, encoding);
5 +}
6 +
7 +function safeCompare(a: Buffer, b: Buffer): boolean {
8 + if (a.length !== b.length) {
9 + return false;
10 + }
11 + return timingSafeEqual(a, b);
12 +}
13 +
14 +export default function validateSignature(
15 + body: string | Buffer,
16 + channelSecret: string,
17 + signature: string,
18 +): boolean {
19 + return safeCompare(
20 + createHmac("SHA256", channelSecret).update(body).digest(),
21 + s2b(signature, "base64"),
22 + );
23 +}
This diff could not be displayed because it is too large.
1 +{
2 + "name": "@line/bot-sdk",
3 + "version": "7.3.0",
4 + "description": "Node.js SDK for LINE Messaging API",
5 + "engines": {
6 + "node": ">=10"
7 + },
8 + "main": "dist/index.js",
9 + "types": "dist/index.d.ts",
10 + "files": [
11 + "dist",
12 + "lib"
13 + ],
14 + "scripts": {
15 + "pretest": "npm run format && npm run build",
16 + "test": "TEST_PORT=1234 TS_NODE_CACHE=0 nyc mocha",
17 + "prettier": "prettier --parser typescript --trailing-comma all --arrow-parens avoid \"{lib,test}/**/*.ts\"",
18 + "format": "npm run prettier -- --write",
19 + "format:check": "npm run prettier -- -l",
20 + "clean": "rm -rf dist/*",
21 + "prebuild": "npm run format:check && npm run clean",
22 + "build": "tsc",
23 + "docs": "vuepress dev docs",
24 + "docs:build": "vuepress build docs",
25 + "docs:deploy": "./scripts/deploy-docs.sh",
26 + "generate-changelog": "ts-node ./scripts/generate-changelog.ts",
27 + "release": "npm run build && npm publish --access public"
28 + },
29 + "repository": {
30 + "type": "git",
31 + "url": "git@github.com:line/line-bot-sdk-nodejs.git"
32 + },
33 + "keywords": [
34 + "node",
35 + "line",
36 + "sdk"
37 + ],
38 + "dependencies": {
39 + "@types/body-parser": "^1.19.0",
40 + "@types/node": "^14.10.0",
41 + "axios": "^0.21.1",
42 + "body-parser": "^1.19.0",
43 + "file-type": "^15.0.0",
44 + "form-data": "^3.0.0"
45 + },
46 + "devDependencies": {
47 + "@types/express": "^4.17.8",
48 + "@types/finalhandler": "^1.1.0",
49 + "@types/mocha": "^8.0.3",
50 + "express": "^4.17.1",
51 + "finalhandler": "^1.1.2",
52 + "husky": "^4.3.0",
53 + "mocha": "^8.1.3",
54 + "nock": "^13.0.4",
55 + "nyc": "^15.1.0",
56 + "prettier": "^2.1.1",
57 + "ts-node": "^9.0.0",
58 + "typescript": "^3.9.7",
59 + "vuepress": "^1.5.4"
60 + },
61 + "husky": {
62 + "hooks": {
63 + "pre-commit": "npm run format:check",
64 + "pre-push": "npm run format:check && npm run build && npm run test"
65 + }
66 + },
67 + "nyc": {
68 + "require": [
69 + "ts-node/register"
70 + ],
71 + "extension": [
72 + ".ts"
73 + ],
74 + "reporter": [
75 + "lcov",
76 + "text"
77 + ],
78 + "sourceMap": true,
79 + "instrument": true
80 + },
81 + "mocha": {
82 + "require": "ts-node/register",
83 + "spec": "test/*.spec.ts"
84 + },
85 + "license": "Apache-2.0"
86 +}
1 +#!/usr/bin/env sh
2 +
3 +# abort on errors
4 +set -e
5 +
6 +# build
7 +npm run docs:build
8 +
9 +# navigate into the build output directory
10 +cd docs/.vuepress/dist
11 +
12 +git init
13 +git add -A
14 +git commit -m 'Deploy docs'
15 +
16 +git push -f git@github.com:line/line-bot-sdk-nodejs.git master:gh-pages
17 +
18 +cd -
1 +#!/bin/bash
2 +
3 +git checkout ${GITHUB_HEAD_REF}
4 +
5 +git config --global user.email "action@github.com"
6 +git config --global user.name "GitHub Action"
7 +
8 +npm run generate-changelog
9 +
10 +git add -A
11 +git commit -m "(Changelog CI) Added Changelog"
12 +git push -u origin ${GITHUB_HEAD_REF}
1 +import { execSync } from "child_process";
2 +import { readFileSync, writeFileSync } from "fs";
3 +import { resolve } from "path";
4 +const { version: lastVersion } = require("../package.json");
5 +
6 +const changeLogPath = resolve(__dirname, "../CHANGELOG.md");
7 +
8 +let newVersion = lastVersion;
9 +
10 +console.log("Gets Release Version from GITHUB_EVENT_PATH");
11 +if (process.env.GITHUB_EVENT_PATH) {
12 + const {
13 + pull_request: { title },
14 + } = require(process.env.GITHUB_EVENT_PATH);
15 +
16 + if (/^release/i.test(title))
17 + newVersion = (title as string).match(/release ([\d\.]+)/i)[1];
18 + else {
19 + console.log("Not target pull request, exiting");
20 + process.exit(0);
21 + }
22 +}
23 +console.log(`New Version: ${newVersion}`);
24 +
25 +console.log("Bump Version");
26 +execSync(`npm version ${newVersion}`);
27 +
28 +const gitLogOutput = execSync(
29 + `git log v${lastVersion}... --format=%s`
30 +).toString("utf-8");
31 +
32 +const commitsArray = gitLogOutput
33 + .split("\n")
34 + .filter((message) => message && message !== "");
35 +
36 +const category = {
37 + miscs: [] as string[],
38 + features: [] as string[],
39 + bugFixes: [] as string[],
40 +};
41 +
42 +commitsArray.forEach((message) => {
43 + let cat: keyof typeof category;
44 + if (/^([\d\.]+)$/.test(message)) {
45 + return;
46 + } else if (message.includes("test")) {
47 + cat = "miscs";
48 + } else if (/(add)|(support)/i.test(message)) {
49 + cat = "features";
50 + } else if (/fix/i.test(message)) {
51 + cat = "bugFixes";
52 + } else {
53 + cat = "miscs";
54 + }
55 + category[cat].push(`* ${message}`);
56 +});
57 +
58 +const now = new Date();
59 +const MonthText = [
60 + "Jan",
61 + "Feb",
62 + "Mar",
63 + "Apr",
64 + "May",
65 + "Jun",
66 + "Jul",
67 + "Aug",
68 + "Sep",
69 + "Oct",
70 + "Nov",
71 + "Dec",
72 +];
73 +let newChangelog = `## ${newVersion} (${now.getDate()} ${
74 + MonthText[now.getMonth()]
75 +} ${now.getFullYear()})
76 +`;
77 +
78 +if (category.features.length > 0) {
79 + newChangelog += `
80 +### Feature
81 +${category.features.join("\n")}
82 +`;
83 +}
84 +
85 +if (category.bugFixes.length > 0) {
86 + newChangelog += `
87 +### Bug fix
88 +${category.bugFixes.join("\n")}
89 +`;
90 +}
91 +
92 +if (category.miscs.length > 0) {
93 + newChangelog += `
94 +### Misc
95 +${category.miscs.join("\n")}
96 +`;
97 +}
98 +
99 +const currentChangelog = readFileSync(changeLogPath, "utf-8");
100 +
101 +writeFileSync(
102 + changeLogPath,
103 + `${newChangelog}
104 +${currentChangelog}`
105 +);
This diff is collapsed. Click to expand it.
1 +import { Readable } from "stream";
2 +
3 +export function getStreamData(stream: Readable): Promise<string> {
4 + return new Promise(resolve => {
5 + let result: string = "";
6 + stream.on("data", (chunk: Buffer) => {
7 + result += chunk.toString();
8 + });
9 + stream.on("end", () => {
10 + resolve(result);
11 + });
12 + });
13 +}
1 +import * as bodyParser from "body-parser";
2 +import * as express from "express";
3 +import { Server } from "http";
4 +import { join } from "path";
5 +import { writeFileSync } from "fs";
6 +import {
7 + JSONParseError,
8 + SignatureValidationFailed,
9 +} from "../../lib/exceptions";
10 +import * as finalhandler from "finalhandler";
11 +
12 +let server: Server = null;
13 +
14 +function listen(port: number, middleware?: express.RequestHandler) {
15 + const app = express();
16 +
17 + if (middleware) {
18 + app.use((req: express.Request, res, next) => {
19 + if (req.path === "/mid-text") {
20 + bodyParser.text({ type: "*/*" })(req, res, next);
21 + } else if (req.path === "/mid-buffer") {
22 + bodyParser.raw({ type: "*/*" })(req, res, next);
23 + } else if (req.path === "/mid-rawbody") {
24 + bodyParser.raw({ type: "*/*" })(req, res, err => {
25 + if (err) return next(err);
26 + (req as any).rawBody = req.body;
27 + delete req.body;
28 + next();
29 + });
30 + } else if (req.path === "/mid-json") {
31 + bodyParser.json({ type: "*/*" })(req, res, next);
32 + } else {
33 + next();
34 + }
35 + });
36 +
37 + app.use(middleware);
38 + }
39 +
40 + // write request info
41 + app.use((req: express.Request, res, next) => {
42 + const request: any = ["headers", "method", "path", "query"].reduce(
43 + (r, k) => Object.assign(r, { [k]: (req as any)[k] }),
44 + {},
45 + );
46 + if (Buffer.isBuffer(req.body)) {
47 + request.body = req.body.toString("base64");
48 + } else {
49 + request.body = req.body;
50 + }
51 + writeFileSync(
52 + join(__dirname, "request.json"),
53 + JSON.stringify(request, null, 2),
54 + );
55 + next();
56 + });
57 +
58 + // return an empty object for others
59 + app.use((req, res) => res.json({}));
60 +
61 + app.use(
62 + (err: Error, req: express.Request, res: express.Response, next: any) => {
63 + if (err instanceof SignatureValidationFailed) {
64 + res.status(401).send(err.signature);
65 + return;
66 + } else if (err instanceof JSONParseError) {
67 + res.status(400).send(err.raw);
68 + return;
69 + }
70 + // https://github.com/expressjs/express/blob/2df1ad26a58bf51228d7600df0d62ed17a90ff71/lib/application.js#L162
71 + // express will record error in console when
72 + // there is no other handler to handle error & it is in test environment
73 + // use final handler the same as in express application.js
74 + finalhandler(req, res)(err);
75 + },
76 + );
77 +
78 + return new Promise(resolve => {
79 + server = app.listen(port, () => resolve());
80 + });
81 +}
82 +
83 +function close() {
84 + return new Promise(resolve => {
85 + if (!server) {
86 + resolve();
87 + }
88 + server.close(() => resolve());
89 + });
90 +}
91 +
92 +export { listen, close };
1 +import { deepEqual, equal, ok } from "assert";
2 +import { HTTPError, RequestError } from "../lib/exceptions";
3 +import HTTPClient from "../lib/http";
4 +import { getStreamData } from "./helpers/stream";
5 +import * as nock from "nock";
6 +import { readFileSync, createReadStream } from "fs";
7 +import { join } from "path";
8 +
9 +const pkg = require("../package.json");
10 +const baseURL = "https://line.me";
11 +const defaultHeaders = {
12 + "test-header-key": "Test-Header-Value",
13 +};
14 +
15 +describe("http", () => {
16 + const http = new HTTPClient({
17 + baseURL,
18 + defaultHeaders,
19 + });
20 +
21 + before(() => nock.disableNetConnect());
22 + afterEach(() => nock.cleanAll());
23 + after(() => nock.enableNetConnect());
24 +
25 + const interceptionOption = {
26 + reqheaders: {
27 + ...defaultHeaders,
28 + "User-Agent": `${pkg.name}/${pkg.version}`,
29 + },
30 + };
31 +
32 + const mockGet = (
33 + path: string,
34 + expectedQuery?: boolean | string | nock.DataMatcherMap | URLSearchParams,
35 + ) => {
36 + let _it = nock(baseURL, interceptionOption).get(path);
37 + if (expectedQuery) {
38 + _it = _it.query(expectedQuery);
39 + }
40 + return _it.reply(200, {});
41 + };
42 +
43 + const mockPost = (path: string, expectedBody?: nock.RequestBodyMatcher) => {
44 + return nock(baseURL, interceptionOption)
45 + .post(path, expectedBody)
46 + .reply(200, {});
47 + };
48 +
49 + const mockDelete = (
50 + path: string,
51 + expectedQuery?: boolean | string | nock.DataMatcherMap | URLSearchParams,
52 + ) => {
53 + let _it = nock(baseURL, interceptionOption).delete(path);
54 + if (expectedQuery) {
55 + _it = _it.query(expectedQuery);
56 + }
57 + return _it.reply(200, {});
58 + };
59 +
60 + it("get", async () => {
61 + const scope = mockGet("/get");
62 + const res = await http.get<any>(`/get`);
63 + equal(scope.isDone(), true);
64 + deepEqual(res, {});
65 + });
66 +
67 + it("get with query", async () => {
68 + const scope = mockGet("/get", { x: 10 });
69 + const res = await http.get<any>(`/get`, { x: 10 });
70 + equal(scope.isDone(), true);
71 + deepEqual(res, {});
72 + });
73 +
74 + it("post without body", async () => {
75 + const scope = mockPost("/post");
76 + const res = await http.post<any>(`/post`);
77 + equal(scope.isDone(), true);
78 +
79 + deepEqual(res, {});
80 + });
81 +
82 + it("post with body", async () => {
83 + const testBody = {
84 + id: 12345,
85 + message: "hello, body!",
86 + };
87 +
88 + const scope = mockPost("/post/body", testBody);
89 + const res = await http.post<any>(`/post/body`, testBody);
90 + equal(scope.isDone(), true);
91 +
92 + deepEqual(res, {});
93 + });
94 +
95 + it("getStream", async () => {
96 + const scope = nock(baseURL, interceptionOption)
97 + .get("/stream.txt")
98 + .reply(200, () =>
99 + createReadStream(join(__dirname, "./helpers/stream.txt")),
100 + );
101 + const stream = await http.getStream(`/stream.txt`);
102 + const data = await getStreamData(stream);
103 +
104 + equal(scope.isDone(), true);
105 + equal(data, "hello, stream!\n");
106 + });
107 +
108 + it("delete", async () => {
109 + const scope = mockDelete("/delete");
110 + await http.delete(`/delete`);
111 + equal(scope.isDone(), true);
112 + });
113 +
114 + it("delete with query", async () => {
115 + const scope = mockDelete("/delete", { x: 10 });
116 + await http.delete(`/delete`, { x: 10 });
117 + equal(scope.isDone(), true);
118 + });
119 +
120 + const mockPostBinary = (
121 + buffer: Buffer,
122 + reqheaders: Record<string, nock.RequestHeaderMatcher>,
123 + ) => {
124 + return nock(baseURL, {
125 + reqheaders: {
126 + ...interceptionOption.reqheaders,
127 + ...reqheaders,
128 + "content-length": buffer.length + "",
129 + },
130 + })
131 + .post("/post/binary", buffer)
132 + .reply(200, {});
133 + };
134 +
135 + it("postBinary", async () => {
136 + const filepath = join(__dirname, "/helpers/line-icon.png");
137 + const buffer = readFileSync(filepath);
138 + const scope = mockPostBinary(buffer, {
139 + "content-type": "image/png",
140 + });
141 +
142 + await http.postBinary(`/post/binary`, buffer);
143 + equal(scope.isDone(), true);
144 + });
145 +
146 + it("postBinary with specific content type", async () => {
147 + const filepath = join(__dirname, "/helpers/line-icon.png");
148 + const buffer = readFileSync(filepath);
149 + const scope = mockPostBinary(buffer, {
150 + "content-type": "image/jpeg",
151 + });
152 +
153 + await http.postBinary(`/post/binary`, buffer, "image/jpeg");
154 + equal(scope.isDone(), true);
155 + });
156 +
157 + it("postBinary with stream", async () => {
158 + const filepath = join(__dirname, "/helpers/line-icon.png");
159 + const stream = createReadStream(filepath);
160 + const buffer = readFileSync(filepath);
161 + const scope = mockPostBinary(buffer, {
162 + "content-type": "image/png",
163 + });
164 +
165 + await http.postBinary(`/post/binary`, stream);
166 + equal(scope.isDone(), true);
167 + });
168 +
169 + it("fail with 404", async () => {
170 + const scope = nock(baseURL, interceptionOption).get("/404").reply(404, {});
171 + try {
172 + await http.get(`/404`);
173 + ok(false);
174 + } catch (err) {
175 + ok(err instanceof HTTPError);
176 + equal(scope.isDone(), true);
177 + equal(err.statusCode, 404);
178 + }
179 + });
180 +
181 + it("fail with wrong addr", async () => {
182 + nock.enableNetConnect();
183 + try {
184 + await http.get("http://domain.invalid");
185 + ok(false);
186 + } catch (err) {
187 + ok(err instanceof RequestError);
188 + equal(err.code, "ENOTFOUND");
189 + nock.disableNetConnect();
190 + }
191 + });
192 +
193 + it("will generate default params", async () => {
194 + const scope = nock(baseURL, {
195 + reqheaders: {
196 + "User-Agent": `${pkg.name}/${pkg.version}`,
197 + },
198 + })
199 + .get("/get")
200 + .reply(200, {});
201 +
202 + const http = new HTTPClient();
203 + const res = await http.get<any>(`${baseURL}/get`);
204 + equal(scope.isDone(), true);
205 + deepEqual(res, {});
206 + });
207 +});
1 +import { deepEqual, equal, ok } from "assert";
2 +import { readFileSync } from "fs";
3 +import { join } from "path";
4 +import { HTTPError } from "../lib/exceptions";
5 +import HTTPClient from "../lib/http";
6 +import middleware from "../lib/middleware";
7 +import * as Types from "../lib/types";
8 +import { close, listen } from "./helpers/test-server";
9 +
10 +const TEST_PORT = parseInt(process.env.TEST_PORT, 10);
11 +
12 +const m = middleware({ channelSecret: "test_channel_secret" });
13 +
14 +const getRecentReq = (): { body: Types.WebhookRequestBody } =>
15 + JSON.parse(readFileSync(join(__dirname, "helpers/request.json")).toString());
16 +
17 +describe("middleware", () => {
18 + const webhook: Types.MessageEvent = {
19 + message: {
20 + id: "test_event_message_id",
21 + text: "this is test message.😄😅😢😞😄😅😢😞",
22 + type: "text",
23 + },
24 + replyToken: "test_reply_token",
25 + source: {
26 + groupId: "test_group_id",
27 + type: "group",
28 + },
29 + timestamp: 0,
30 + mode: "active",
31 + type: "message",
32 + };
33 + const webhookSignature = {
34 + "X-Line-Signature": "GzU7H3qOXDzDD6cNcS/9otLzlLFxnYYriz62rNu5BDE=",
35 + };
36 +
37 + const http = (headers: any = { ...webhookSignature }) =>
38 + new HTTPClient({
39 + baseURL: `http://localhost:${TEST_PORT}`,
40 + defaultHeaders: headers,
41 + });
42 +
43 + before(() => listen(TEST_PORT, m));
44 + after(() => close());
45 +
46 + it("succeed", async () => {
47 + await http().post(`/webhook`, {
48 + events: [webhook],
49 + destination: "Uaaaabbbbccccddddeeeeffff",
50 + });
51 + const req = getRecentReq();
52 + deepEqual(req.body.destination, "Uaaaabbbbccccddddeeeeffff");
53 + deepEqual(req.body.events, [webhook]);
54 + });
55 +
56 + it("succeed with pre-parsed string", async () => {
57 + await http().post(`/mid-text`, {
58 + events: [webhook],
59 + destination: "Uaaaabbbbccccddddeeeeffff",
60 + });
61 + const req = getRecentReq();
62 + deepEqual(req.body.destination, "Uaaaabbbbccccddddeeeeffff");
63 + deepEqual(req.body.events, [webhook]);
64 + });
65 +
66 + it("succeed with pre-parsed buffer", async () => {
67 + await http().post(`/mid-buffer`, {
68 + events: [webhook],
69 + destination: "Uaaaabbbbccccddddeeeeffff",
70 + });
71 + const req = getRecentReq();
72 + deepEqual(req.body.destination, "Uaaaabbbbccccddddeeeeffff");
73 + deepEqual(req.body.events, [webhook]);
74 + });
75 +
76 + it("succeed with pre-parsed buffer in rawBody", async () => {
77 + await http().post(`/mid-rawbody`, {
78 + events: [webhook],
79 + destination: "Uaaaabbbbccccddddeeeeffff",
80 + });
81 + const req = getRecentReq();
82 + deepEqual(req.body.destination, "Uaaaabbbbccccddddeeeeffff");
83 + deepEqual(req.body.events, [webhook]);
84 + });
85 +
86 + it("fails on parsing raw as it's a not valid request and should be catched", async () => {
87 + try {
88 + await http({
89 + "X-Line-Signature": "wqJD7WAIZhWcXThMCf8jZnwG3Hmn7EF36plkQGkj48w=",
90 + "Content-Encoding": 1,
91 + }).post(`/webhook`, {
92 + events: [webhook],
93 + destination: "Uaaaabbbbccccddddeeeeffff",
94 + });
95 + ok(false);
96 + } catch (err) {
97 + if (err instanceof HTTPError) {
98 + equal(err.statusCode, 415);
99 + } else {
100 + throw err;
101 + }
102 + }
103 + });
104 +
105 + it("fails on pre-parsed json", async () => {
106 + try {
107 + await http().post(`/mid-json`, {
108 + events: [webhook],
109 + destination: "Uaaaabbbbccccddddeeeeffff",
110 + });
111 + ok(false);
112 + } catch (err) {
113 + if (err instanceof HTTPError) {
114 + equal(err.statusCode, 500);
115 + } else {
116 + throw err;
117 + }
118 + }
119 + });
120 + it("fails on construct with no channelSecret", () => {
121 + try {
122 + middleware({ channelSecret: null });
123 + ok(false);
124 + } catch (err) {
125 + equal(err.message, "no channel secret");
126 + }
127 + });
128 +
129 + it("fails on wrong signature", async () => {
130 + try {
131 + await http({
132 + "X-Line-Signature": "WqJD7WAIZhWcXThMCf8jZnwG3Hmn7EF36plkQGkj48w=",
133 + }).post(`/webhook`, {
134 + events: [webhook],
135 + destination: "Uaaaabbbbccccddddeeeeffff",
136 + });
137 + ok(false);
138 + } catch (err) {
139 + if (err instanceof HTTPError) {
140 + equal(err.statusCode, 401);
141 + } else {
142 + throw err;
143 + }
144 + }
145 + });
146 +
147 + it("fails on wrong signature (length)", async () => {
148 + try {
149 + await http({
150 + "X-Line-Signature": "WqJD7WAIZ6plkQGkj48w=",
151 + }).post(`/webhook`, {
152 + events: [webhook],
153 + destination: "Uaaaabbbbccccddddeeeeffff",
154 + });
155 + ok(false);
156 + } catch (err) {
157 + if (err instanceof HTTPError) {
158 + equal(err.statusCode, 401);
159 + } else {
160 + throw err;
161 + }
162 + }
163 + });
164 +
165 + it("fails on invalid JSON", async () => {
166 + try {
167 + await http({
168 + "X-Line-Signature": "Z8YlPpm0lQOqPipiCHVbiuwIDIzRzD7w5hvHgmwEuEs=",
169 + }).post(`/webhook`, "i am not jason");
170 + ok(false);
171 + } catch (err) {
172 + if (err instanceof HTTPError) {
173 + equal(err.statusCode, 400);
174 + } else {
175 + throw err;
176 + }
177 + }
178 + });
179 +
180 + it("fails on empty signature", async () => {
181 + try {
182 + await http({}).post(`/webhook`, {
183 + events: [webhook],
184 + destination: "Uaaaabbbbccccddddeeeeffff",
185 + });
186 + ok(false);
187 + } catch (err) {
188 + if (err instanceof HTTPError) {
189 + equal(err.statusCode, 401);
190 + } else {
191 + throw err;
192 + }
193 + }
194 + });
195 +});
1 +import { ensureJSON } from "../lib/utils";
2 +import { JSONParseError } from "../lib/exceptions";
3 +import { equal, ok } from "assert";
4 +
5 +describe("utils", () => {
6 + describe("ensureJSON", () => {
7 + it("fails when input isn't an object", () => {
8 + let input = "not Object";
9 + try {
10 + ensureJSON(input);
11 + ok(false);
12 + } catch (err) {
13 + equal(
14 + (err as JSONParseError).message,
15 + "Failed to parse response body as JSON",
16 + );
17 + }
18 + });
19 + });
20 +});
1 +import { ok } from "assert";
2 +import validateSignature from "../lib/validate-signature";
3 +
4 +const body = { hello: "world" };
5 +const secret = "test_secret";
6 +
7 +describe("validateSignature", () => {
8 + it("success", () => {
9 + const validSignature = "t7Hn4ZDHqs6e+wdvI5TyQIvzie0DmMUmuXEBqyyE/tM=";
10 + ok(validateSignature(JSON.stringify(body), secret, validSignature));
11 + });
12 +
13 + it("failure", () => {
14 + const invalidSignature = "t7Hn4ZDHqs6e+wdvi5TyQivzie0DmMUmuXEBqyyE/tM=";
15 + ok(!validateSignature(JSON.stringify(body), secret, invalidSignature));
16 + });
17 +});
1 +{
2 + "compilerOptions": {
3 + "module": "commonjs",
4 + "target": "es2017",
5 + "noImplicitAny": true,
6 + "outDir": "dist",
7 + "rootDirs": ["lib", "test"],
8 + "declaration": true
9 + },
10 + "include": [
11 + "lib/**/*.ts"
12 + ]
13 +}