This commit is contained in:
darenhsu
2022-07-17 13:16:16 +08:00
parent 84759556ff
commit befd344ab0
28070 changed files with 4008428 additions and 1 deletions
+385
View File
@@ -0,0 +1,385 @@
## 7.0.0 (15 June 202)
### Breaking Changes
* Node.js: drop 8 & adopt 14 (#222)
### Feature
* Support Channel access token v2.1 (#223)
* Support Messaging API update for June 2020 (#228)
* add X-Line-Retry-Key support (#224)
* Support emojis in text message webhook (#218)
* narrowcast api & audience apis (#193)
* Add support for sticon in text messages (#214)
* Add language support for profile API (#215)
* Support icon-nickname-switch (#207)
* Define LINE_SIGNATURE_HTTP_HEADER_NAME (#200)
* add docs for getUserInteractionStatistics (#195)
### Bug fixs & Feature Changes
* fix getUserInteractionStatistics (#194)
* type fix: accept string in the aspectRatio property of flex image and flex icon (#212)
### Others
* update dependencies & rewrite to promise (#225 & #229)
* fix vulnerabilities (#217)
* add emoji test (#198)
* update vuepress to 1.x (#188)
## 6.8.4 (19 Dec 2019)
### Bug fix
* Fix typo in type of FriendDemographics (#177)
* Add label property to ImageMapAction type (#187)
### Feature
* Change data api's domain to api-data.line.me (#178)
* Add getUserInteractionStatistics API (#183)
* Add new properties in webhook types (#182)
### Misc
* Rewrite test in nock (#179)
* Update dependencies (#180)
## 6.8.3 (05 Nov 2019)
### Bug fix
* Add exception handler in middleware (#153)
### Feature
* Flex Message Update 1 (#173)
* Support friend statistics API (#161)
### Misc
* Update dependencies (#174)
## 6.8.2 (08 Aug 2019)
### Bug fix
* Fix LINEThings Scenario Execution Event Types (#158)
## 6.8.1 (29 Jul 2019)
### Bug fix
* Fix a type wrong in Template Message (#163)
### Feature
* Get `X-LINE-Request-Id` by using `responseData['x-line-request-id']` (#151 #157)
## 6.8.0 (25 Jun 2019)
### Feature
* Add new parameter in push/reply/multicast/broadcast API to catch up the newest bot API (#147)
* Add new APIs in bot API (#147)
- Get the target limit for additional messages
- Get number of messages sent this month
- Get number of sent broadcast messages
- Send broadcast message
### Breaking changes
* Deprecate Node 6 and start to support Node 12 (#139)
* Remove polyfills for Node 6 (#149)
### Type
* Add LINE Things Event (#150)
### Misc
* Update axios and other dependencies by running `npm audit fix` to fix vulnerabilities. (#148 #154)
## 6.7.0 (18 Apr 2019)
### Feature
* Add alt URL field to URI action (#135)
* Implement (un)linkRichMenuToMultipleUsers (#135)
### Type
* Fix typo in a type (#124)
## 6.6.0 (4 Mar 2019)
### Feature
* Add DeviceLinkEvent / DeviceUnlinkEvent (#123)
### Type
* Fix FlexSpacer to have optional 'size' property (#122)
### Misc
* Run `npm audit fix` to fix minor dependency vulnerability.
## 6.5.0 (16 Feb 2019)
### Feature
* Add APIs to get number of sent messages (#116)
* Add account link event (#117)
### Misc
* Fix a typo in doc (#119)
## 6.4.0 (19 Nov 2018)
### Feature
* Add `getLinkToken` API (#96)
* Handle `req.rawBody` in Google Cloud Functions (#101)
* [Kitchensink] Add ngrok functionality (#99)
### Type
* Add types for video in imagemap message (#100)
* Add `contentProvider` fields to content messages (#103)
* Add `destination` field to webhook request body (#102)
* Add `MemberJoinEvent` and `MemberLeaveEvent` types (#107)
### Misc
* Don't include doc in released source
* Upgrade TypeScript to 3.1.6 (#94)
* Refactoring (#94, #98, #99)
* Remove webhook-tester tool
## 6.3.0 (21 Sep 2018)
### Feature
* Add default rich menu APIs (#87)
### Type
* Add missing `defaultAction` field to `TemplateColumn`
### Misc
* Use VuePress as documentation engine (#85)
* Upgrade minimum supported Node.js version to 6
## 6.2.1 (16 Aug 2018)
### Misc
* Remove gitbook-cli from dev dependencies
## 6.2.0 (15 Aug 2018)
#### Type
* Add QuickReply types (#83)
* Format type comments
#### Misc
* Upgrade TypeScript to 3
## 6.1.1 (14 Aug 2018)
#### Type
* Update FlexMessage types (#81)
#### Misc
* Add test coverage (#78)
* Add JSDoc comments (#80)
## 6.1.0 (19 June 2018)
#### Type
* Add types for flex message (#74)
* Simplify type definition for `Action`
## 6.0.3 (18 June 2018)
#### Misc
* Move get-audio-duration dep to proper package.json (#73)
* Vulnerability fix with `npm audit fix`
## 6.0.2 (21 May 2018)
#### Type
* Add missing `displayText` field to postback action (#63)
* Add missing `FileEventMessage` to `EventMessage` (#71)
#### Misc
* Add audio duration lib to kitchensink example (#68)
## 6.0.1 (13 Mar 2018)
#### Type
* Fix misimplemented 'AudioMessage' type (#61)
## 6.0.0 (27 Feb 2018)
#### Major
* Fix misimplemented 'unlinkRichMenuFromUser' API
#### Type
* Fix TemplateColumn type definition (#48)
#### Misc
* Update GitHub issue template (#43)
* Add Code of Conduct (#50)
* Catch errors properly in examples (#52)
## 5.2.0 (11 Dec 2017)
#### Minor
* Set Content-Length manually for postBinary (#42)
## 5.1.0 (7 Dec 2017)
#### Minor
* Add new fields (#39)
#### Misc
* Fix Windows build (#38)
* Add start scripts and webhook info to examples
## 5.0.1 (14 Nov 2017)
#### Minor
* Fix typo in `ImageMapMessage` type
* Add kitchensink example (#36)
## 5.0.0 (2 Nov 2017)
#### Major
* Implement rich menu API (#34)
#### Type
* Rename `ImageMapArea` and `TemplateAction`s into general ones
#### Misc
* Do not enforce `checkJSON` for some APIs where it means nothing
* Change how to check request object in test cases
## 4.0.0 (25 Oct 2017)
#### Major
* Make index script export exceptions and types (#31)
#### Type
* Simplify config types for client and middleware (#31)
#### Misc
* Fix information and links in doc
* Use Prettier instead of TSLint (#30)
* Install git hooks for precommit and prepush (#30)
## 3.1.1 (19 Sep 2017)
#### Type
* Fix type of postback.params
## 3.1.0 (19 Sep 2017)
#### Major
* Make middleware return `SignatureValidationFailed` for no signature (#26)
#### Type
* Add `FileEventMessage` type
## 3.0.0 (8 Sep 2017)
#### Major
* Implement "Get group/room member profile" API (#15)
* Implement "Get group/room member IDs" API (#23)
* `getMessageContent` now returns `Promise<ReadableStream>` (#20)
#### Type
* Add "datetimepicker" support (#21)
* Fix typo in `TemplateURIAction` type (#21)
#### Misc
* Package updates and corresponding fixes
* Use npm 5 instead of Yarn in dev
* Fix `clean` script to work in Windows
* Use "axios" for internal HTTP client instead of "got" (#20)
## 2.0.0 (12 June 2017)
#### Type
* Use literal types for 'type' fields
#### Misc
* Update yarn.lock with the latest Yarn
## 1.1.0 (31 May 2017)
* Handle pre-parsed body (string and buffer only)
#### Type
* Separate config type into client and middleware types
* Add `userId` to group and room event sources
#### Misc
* Create issue template (#4)
## 1.0.0 (11 May 2017)
* Initial release
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (C) 2017-2018 LINE Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+64
View File
@@ -0,0 +1,64 @@
# LINE Messaging API SDK for nodejs
[![Travis CI](https://travis-ci.org/line/line-bot-sdk-nodejs.svg?branch=master)](https://travis-ci.org/line/line-bot-sdk-nodejs)
[![npmjs](https://badge.fury.io/js/%40line%2Fbot-sdk.svg)](https://www.npmjs.com/package/@line/bot-sdk)
## Introduction
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.
## Documentation
See the official API documentation for more information
- English: https://developers.line.biz/en/docs/messaging-api/overview/
- Japanese: https://developers.line.biz/ja/docs/messaging-api/overview/
line-bot-sdk-nodejs documentation: https://line.github.io/line-bot-sdk-nodejs/#getting-started
## Requirements
* **Node.js** 10 or higher
## Installation
Using [npm](https://www.npmjs.com/):
``` bash
$ npm install @line/bot-sdk --save
```
## Help and media
FAQ: https://developers.line.biz/en/faq/
Community Q&A: https://www.line-community.me/questions
News: https://developers.line.biz/en/news/
Twitter: @LINE_DEV
## Versioning
This project respects semantic versioning
See http://semver.org/
## Contributing
Please check [CONTRIBUTING](CONTRIBUTING.md) before making a contribution.
## License
```
Copyright (C) 2016 LINE Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
+137
View File
@@ -0,0 +1,137 @@
/// <reference types="node" />
import { Readable } from "stream";
import * as Types from "./types";
import { AxiosRequestConfig } from "axios";
declare type RequestOption = {
retryKey: string;
};
export default class Client {
config: Types.ClientConfig;
private http;
private requestOption;
constructor(config: Types.ClientConfig);
setRequestOptionOnce(option: Partial<RequestOption>): void;
private generateRequestConfig;
private parseHTTPResponse;
pushMessage(to: string, messages: Types.Message | Types.Message[], notificationDisabled?: boolean): Promise<Types.MessageAPIResponseBase>;
replyMessage(replyToken: string, messages: Types.Message | Types.Message[], notificationDisabled?: boolean): Promise<Types.MessageAPIResponseBase>;
multicast(to: string[], messages: Types.Message | Types.Message[], notificationDisabled?: boolean): Promise<Types.MessageAPIResponseBase>;
narrowcast(messages: Types.Message | Types.Message[], recipient?: Types.ReceieptObject, filter?: {
demographic: Types.DemographicFilterObject;
}, limit?: {
max: number;
}, notificationDisabled?: boolean): Promise<Types.MessageAPIResponseBase>;
broadcast(messages: Types.Message | Types.Message[], notificationDisabled?: boolean): Promise<Types.MessageAPIResponseBase>;
getProfile(userId: string): Promise<Types.Profile>;
private getChatMemberProfile;
getGroupMemberProfile(groupId: string, userId: string): Promise<Types.Profile>;
getRoomMemberProfile(roomId: string, userId: string): Promise<Types.Profile>;
private getChatMemberIds;
getGroupMemberIds(groupId: string): Promise<string[]>;
getRoomMemberIds(roomId: string): Promise<string[]>;
getGroupMembersCount(groupId: string): Promise<Types.MembersCountResponse>;
getRoomMembersCount(roomId: string): Promise<Types.MembersCountResponse>;
getGroupSummary(groupId: string): Promise<Types.GroupSummaryResponse>;
getMessageContent(messageId: string): Promise<Readable>;
private leaveChat;
leaveGroup(groupId: string): Promise<any>;
leaveRoom(roomId: string): Promise<any>;
getRichMenu(richMenuId: string): Promise<Types.RichMenuResponse>;
createRichMenu(richMenu: Types.RichMenu): Promise<string>;
deleteRichMenu(richMenuId: string): Promise<any>;
getRichMenuIdOfUser(userId: string): Promise<string>;
linkRichMenuToUser(userId: string, richMenuId: string): Promise<any>;
unlinkRichMenuFromUser(userId: string): Promise<any>;
linkRichMenuToMultipleUsers(richMenuId: string, userIds: string[]): Promise<any>;
unlinkRichMenusFromMultipleUsers(userIds: string[]): Promise<any>;
getRichMenuImage(richMenuId: string): Promise<Readable>;
setRichMenuImage(richMenuId: string, data: Buffer | Readable, contentType?: string): Promise<any>;
getRichMenuList(): Promise<Array<Types.RichMenuResponse>>;
setDefaultRichMenu(richMenuId: string): Promise<{}>;
getDefaultRichMenuId(): Promise<string>;
deleteDefaultRichMenu(): Promise<{}>;
getLinkToken(userId: string): Promise<string>;
getNumberOfSentReplyMessages(date: string): Promise<Types.NumberOfMessagesSentResponse>;
getNumberOfSentPushMessages(date: string): Promise<Types.NumberOfMessagesSentResponse>;
getNumberOfSentMulticastMessages(date: string): Promise<Types.NumberOfMessagesSentResponse>;
getNarrowcastProgress(requestId: string): Promise<Types.NarrowcastProgressResponse>;
getTargetLimitForAdditionalMessages(): Promise<Types.TargetLimitForAdditionalMessages>;
getNumberOfMessagesSentThisMonth(): Promise<Types.NumberOfMessagesSentThisMonth>;
getNumberOfSentBroadcastMessages(date: string): Promise<Types.NumberOfMessagesSentResponse>;
getNumberOfMessageDeliveries(date: string): Promise<Types.NumberOfMessageDeliveriesResponse>;
getNumberOfFollowers(date: string): Promise<Types.NumberOfFollowersResponse>;
getFriendDemographics(): Promise<Types.FriendDemographics>;
getUserInteractionStatistics(requestId: string): Promise<Types.UserInteractionStatistics>;
createUploadAudienceGroup(uploadAudienceGroup: {
description: string;
isIfaAudience: boolean;
audiences: {
id: string;
}[];
uploadDescription?: string;
}): Promise<{
audienceGroupId: number;
type: string;
description: string;
created: number;
}>;
updateUploadAudienceGroup(uploadAudienceGroup: {
audienceGroupId: number;
description?: string;
uploadDescription?: string;
audiences: {
id: string;
}[];
}, httpConfig?: Partial<AxiosRequestConfig>): Promise<{}>;
createClickAudienceGroup(clickAudienceGroup: {
description: string;
requestId: string;
clickUrl?: string;
}): Promise<{
audienceGroupId: number;
type: string;
created: number;
} & {
description: string;
requestId: string;
clickUrl?: string;
}>;
createImpAudienceGroup(impAudienceGroup: {
requestId: string;
description: string;
}): Promise<{
audienceGroupId: number;
type: string;
created: number;
} & {
requestId: string;
description: string;
}>;
setDescriptionAudienceGroup(description: string, audienceGroupId: string): Promise<{}>;
deleteAudienceGroup(audienceGroupId: string): Promise<{}>;
getAudienceGroup(audienceGroupId: string): Promise<Types.AudienceGroup>;
getAudienceGroups(page: number, description?: string, status?: Types.AudienceGroupStatus, size?: number, createRoute?: Types.AudienceGroupCreateRoute, includesExternalPublicGroups?: boolean): Promise<{
audienceGroups: Types.AudienceGroups;
hasNextPage: boolean;
totalCount: number;
readWriteAudienceGroupTotalCount: number;
page: number;
size: number;
}>;
getAudienceGroupAuthorityLevel(): Promise<{
authorityLevel: Types.AudienceGroupAuthorityLevel;
}>;
changeAudienceGroupAuthorityLevel(authorityLevel: Types.AudienceGroupAuthorityLevel): Promise<{}>;
}
export declare class OAuth {
private http;
constructor();
issueAccessToken(client_id: string, client_secret: string): Promise<Types.ChannelAccessToken>;
revokeAccessToken(access_token: string): Promise<{}>;
issueChannelAccessTokenV2_1(client_assertion: string): Promise<Types.ChannelAccessToken>;
getIssuedChannelAccessTokenV2_1(client_assertion: string): Promise<{
access_tokens: string[];
}>;
revokeChannelAccessTokenV2_1(client_id: string, client_secret: string, access_token: string): Promise<{}>;
}
export {};
+319
View File
@@ -0,0 +1,319 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OAuth = void 0;
const http_1 = require("./http");
const Types = require("./types");
const utils_1 = require("./utils");
const endpoints_1 = require("./endpoints");
class Client {
constructor(config) {
this.requestOption = {};
if (!config.channelAccessToken) {
throw new Error("no channel access token");
}
this.config = config;
this.http = new http_1.default(Object.assign({ defaultHeaders: {
Authorization: "Bearer " + this.config.channelAccessToken,
}, responseParser: this.parseHTTPResponse.bind(this) }, config.httpConfig));
}
setRequestOptionOnce(option) {
this.requestOption = option;
}
generateRequestConfig() {
const config = { headers: {} };
if (this.requestOption.retryKey) {
config.headers["X-Line-Retry-Key"] = this.requestOption.retryKey;
}
// clear requestOption
this.requestOption = {};
return config;
}
parseHTTPResponse(response) {
const { LINE_REQUEST_ID_HTTP_HEADER_NAME } = Types;
let resBody = Object.assign({}, response.data);
if (response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME]) {
resBody[LINE_REQUEST_ID_HTTP_HEADER_NAME] =
response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME];
}
return resBody;
}
pushMessage(to, messages, notificationDisabled = false) {
return this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/message/push`, {
messages: utils_1.toArray(messages),
to,
notificationDisabled,
}, this.generateRequestConfig());
}
replyMessage(replyToken, messages, notificationDisabled = false) {
return this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/message/reply`, {
messages: utils_1.toArray(messages),
replyToken,
notificationDisabled,
});
}
async multicast(to, messages, notificationDisabled = false) {
return this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/message/multicast`, {
messages: utils_1.toArray(messages),
to,
notificationDisabled,
}, this.generateRequestConfig());
}
async narrowcast(messages, recipient, filter, limit, notificationDisabled = false) {
return this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/message/narrowcast`, {
messages: utils_1.toArray(messages),
recipient,
filter,
limit,
notificationDisabled,
}, this.generateRequestConfig());
}
async broadcast(messages, notificationDisabled = false) {
return this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/message/broadcast`, {
messages: utils_1.toArray(messages),
notificationDisabled,
}, this.generateRequestConfig());
}
async getProfile(userId) {
const profile = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/profile/${userId}`);
return utils_1.ensureJSON(profile);
}
async getChatMemberProfile(chatType, chatId, userId) {
const profile = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/${chatType}/${chatId}/member/${userId}`);
return utils_1.ensureJSON(profile);
}
async getGroupMemberProfile(groupId, userId) {
return this.getChatMemberProfile("group", groupId, userId);
}
async getRoomMemberProfile(roomId, userId) {
return this.getChatMemberProfile("room", roomId, userId);
}
async getChatMemberIds(chatType, chatId) {
let memberIds = [];
let start;
do {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/${chatType}/${chatId}/members/ids`, start ? { start } : null);
utils_1.ensureJSON(res);
memberIds = memberIds.concat(res.memberIds);
start = res.next;
} while (start);
return memberIds;
}
async getGroupMemberIds(groupId) {
return this.getChatMemberIds("group", groupId);
}
async getRoomMemberIds(roomId) {
return this.getChatMemberIds("room", roomId);
}
async getGroupMembersCount(groupId) {
const groupMemberCount = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/group/${groupId}/members/count`);
return utils_1.ensureJSON(groupMemberCount);
}
async getRoomMembersCount(roomId) {
const roomMemberCount = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/room/${roomId}/members/count`);
return utils_1.ensureJSON(roomMemberCount);
}
async getGroupSummary(groupId) {
const groupSummary = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/group/${groupId}/summary`);
return utils_1.ensureJSON(groupSummary);
}
async getMessageContent(messageId) {
return this.http.getStream(`${endpoints_1.DATA_API_PREFIX}/message/${messageId}/content`);
}
leaveChat(chatType, chatId) {
return this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/${chatType}/${chatId}/leave`);
}
async leaveGroup(groupId) {
return this.leaveChat("group", groupId);
}
async leaveRoom(roomId) {
return this.leaveChat("room", roomId);
}
async getRichMenu(richMenuId) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/richmenu/${richMenuId}`);
return utils_1.ensureJSON(res);
}
async createRichMenu(richMenu) {
const res = await this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/richmenu`, richMenu);
return utils_1.ensureJSON(res).richMenuId;
}
async deleteRichMenu(richMenuId) {
return this.http.delete(`${endpoints_1.MESSAGING_API_PREFIX}/richmenu/${richMenuId}`);
}
async getRichMenuIdOfUser(userId) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/user/${userId}/richmenu`);
return utils_1.ensureJSON(res).richMenuId;
}
async linkRichMenuToUser(userId, richMenuId) {
return this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/user/${userId}/richmenu/${richMenuId}`);
}
async unlinkRichMenuFromUser(userId) {
return this.http.delete(`${endpoints_1.MESSAGING_API_PREFIX}/user/${userId}/richmenu`);
}
async linkRichMenuToMultipleUsers(richMenuId, userIds) {
return this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/richmenu/bulk/link`, {
richMenuId,
userIds,
});
}
async unlinkRichMenusFromMultipleUsers(userIds) {
return this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/richmenu/bulk/unlink`, {
userIds,
});
}
async getRichMenuImage(richMenuId) {
return this.http.getStream(`${endpoints_1.DATA_API_PREFIX}/richmenu/${richMenuId}/content`);
}
async setRichMenuImage(richMenuId, data, contentType) {
return this.http.postBinary(`${endpoints_1.DATA_API_PREFIX}/richmenu/${richMenuId}/content`, data, contentType);
}
async getRichMenuList() {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/richmenu/list`);
return utils_1.ensureJSON(res).richmenus;
}
async setDefaultRichMenu(richMenuId) {
return this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/user/all/richmenu/${richMenuId}`);
}
async getDefaultRichMenuId() {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/user/all/richmenu`);
return utils_1.ensureJSON(res).richMenuId;
}
async deleteDefaultRichMenu() {
return this.http.delete(`${endpoints_1.MESSAGING_API_PREFIX}/user/all/richmenu`);
}
async getLinkToken(userId) {
const res = await this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/user/${userId}/linkToken`);
return utils_1.ensureJSON(res).linkToken;
}
async getNumberOfSentReplyMessages(date) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/message/delivery/reply?date=${date}`);
return utils_1.ensureJSON(res);
}
async getNumberOfSentPushMessages(date) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/message/delivery/push?date=${date}`);
return utils_1.ensureJSON(res);
}
async getNumberOfSentMulticastMessages(date) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/message/delivery/multicast?date=${date}`);
return utils_1.ensureJSON(res);
}
async getNarrowcastProgress(requestId) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/message/progress/narrowcast?requestId=${requestId}`);
return utils_1.ensureJSON(res);
}
async getTargetLimitForAdditionalMessages() {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/message/quota`);
return utils_1.ensureJSON(res);
}
async getNumberOfMessagesSentThisMonth() {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/message/quota/consumption`);
return utils_1.ensureJSON(res);
}
async getNumberOfSentBroadcastMessages(date) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/message/delivery/broadcast?date=${date}`);
return utils_1.ensureJSON(res);
}
async getNumberOfMessageDeliveries(date) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/insight/message/delivery?date=${date}`);
return utils_1.ensureJSON(res);
}
async getNumberOfFollowers(date) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/insight/followers?date=${date}`);
return utils_1.ensureJSON(res);
}
async getFriendDemographics() {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/insight/demographic`);
return utils_1.ensureJSON(res);
}
async getUserInteractionStatistics(requestId) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/insight/message/event?requestId=${requestId}`);
return utils_1.ensureJSON(res);
}
async createUploadAudienceGroup(uploadAudienceGroup) {
const res = await this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/audienceGroup/upload`, Object.assign({}, uploadAudienceGroup));
return utils_1.ensureJSON(res);
}
async updateUploadAudienceGroup(uploadAudienceGroup,
// for set request timeout
httpConfig) {
const res = await this.http.put(`${endpoints_1.MESSAGING_API_PREFIX}/audienceGroup/upload`, Object.assign({}, uploadAudienceGroup), httpConfig);
return utils_1.ensureJSON(res);
}
async createClickAudienceGroup(clickAudienceGroup) {
const res = await this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/audienceGroup/click`, Object.assign({}, clickAudienceGroup));
return utils_1.ensureJSON(res);
}
async createImpAudienceGroup(impAudienceGroup) {
const res = await this.http.post(`${endpoints_1.MESSAGING_API_PREFIX}/audienceGroup/imp`, Object.assign({}, impAudienceGroup));
return utils_1.ensureJSON(res);
}
async setDescriptionAudienceGroup(description, audienceGroupId) {
const res = await this.http.put(`${endpoints_1.MESSAGING_API_PREFIX}/audienceGroup/${audienceGroupId}/updateDescription`, {
description,
});
return utils_1.ensureJSON(res);
}
async deleteAudienceGroup(audienceGroupId) {
const res = await this.http.delete(`${endpoints_1.MESSAGING_API_PREFIX}/audienceGroup/${audienceGroupId}`);
return utils_1.ensureJSON(res);
}
async getAudienceGroup(audienceGroupId) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/audienceGroup/${audienceGroupId}`);
return utils_1.ensureJSON(res);
}
async getAudienceGroups(page, description, status, size, createRoute, includesExternalPublicGroups) {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/audienceGroup/list`, {
page,
description,
status,
size,
createRoute,
includesExternalPublicGroups,
});
return utils_1.ensureJSON(res);
}
async getAudienceGroupAuthorityLevel() {
const res = await this.http.get(`${endpoints_1.MESSAGING_API_PREFIX}/audienceGroup/authorityLevel`);
return utils_1.ensureJSON(res);
}
async changeAudienceGroupAuthorityLevel(authorityLevel) {
const res = await this.http.put(`${endpoints_1.MESSAGING_API_PREFIX}/audienceGroup/authorityLevel`, { authorityLevel });
return utils_1.ensureJSON(res);
}
}
exports.default = Client;
class OAuth {
constructor() {
this.http = new http_1.default();
}
issueAccessToken(client_id, client_secret) {
return this.http.postForm(`${endpoints_1.OAUTH_BASE_PREFIX}/accessToken`, {
grant_type: "client_credentials",
client_id,
client_secret,
});
}
revokeAccessToken(access_token) {
return this.http.postForm(`${endpoints_1.OAUTH_BASE_PREFIX}/revoke`, { access_token });
}
issueChannelAccessTokenV2_1(client_assertion) {
return this.http.postForm(`${endpoints_1.OAUTH_BASE_PREFIX}/v2.1/token`, {
grant_type: "client_credentials",
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion,
});
}
getIssuedChannelAccessTokenV2_1(client_assertion) {
return this.http.get(`${endpoints_1.OAUTH_BASE_PREFIX}/v2.1/tokens`, {
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion,
});
}
revokeChannelAccessTokenV2_1(client_id, client_secret, access_token) {
return this.http.postForm(`${endpoints_1.OAUTH_BASE_PREFIX}/v2.1/revoke`, {
client_id,
client_secret,
access_token,
});
}
}
exports.OAuth = OAuth;
+3
View File
@@ -0,0 +1,3 @@
export declare const MESSAGING_API_PREFIX = "https://api.line.me/v2/bot";
export declare const DATA_API_PREFIX = "https://api-data.line.me/v2/bot";
export declare const OAUTH_BASE_PREFIX = "https://api.line.me/v2/oauth";
+6
View File
@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OAUTH_BASE_PREFIX = exports.DATA_API_PREFIX = exports.MESSAGING_API_PREFIX = void 0;
exports.MESSAGING_API_PREFIX = `https://api.line.me/v2/bot`;
exports.DATA_API_PREFIX = `https://api-data.line.me/v2/bot`;
exports.OAUTH_BASE_PREFIX = `https://api.line.me/v2/oauth`;
+23
View File
@@ -0,0 +1,23 @@
export declare class SignatureValidationFailed extends Error {
signature?: string;
constructor(message: string, signature?: string);
}
export declare class JSONParseError extends Error {
raw: any;
constructor(message: string, raw: any);
}
export declare class RequestError extends Error {
code: string;
private originalError;
constructor(message: string, code: string, originalError: Error);
}
export declare class ReadError extends Error {
private originalError;
constructor(originalError: Error);
}
export declare class HTTPError extends Error {
statusCode: number;
statusMessage: string;
originalError: any;
constructor(message: string, statusCode: number, statusMessage: string, originalError: any);
}
+41
View File
@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTTPError = exports.ReadError = exports.RequestError = exports.JSONParseError = exports.SignatureValidationFailed = void 0;
class SignatureValidationFailed extends Error {
constructor(message, signature) {
super(message);
this.signature = signature;
}
}
exports.SignatureValidationFailed = SignatureValidationFailed;
class JSONParseError extends Error {
constructor(message, raw) {
super(message);
this.raw = raw;
}
}
exports.JSONParseError = JSONParseError;
class RequestError extends Error {
constructor(message, code, originalError) {
super(message);
this.code = code;
this.originalError = originalError;
}
}
exports.RequestError = RequestError;
class ReadError extends Error {
constructor(originalError) {
super(originalError.message);
this.originalError = originalError;
}
}
exports.ReadError = ReadError;
class HTTPError extends Error {
constructor(message, statusCode, statusMessage, originalError) {
super(message);
this.statusCode = statusCode;
this.statusMessage = statusMessage;
this.originalError = originalError;
}
}
exports.HTTPError = HTTPError;
+23
View File
@@ -0,0 +1,23 @@
/// <reference types="node" />
import { AxiosResponse, AxiosRequestConfig } from "axios";
import { Readable } from "stream";
interface httpClientConfig extends Partial<AxiosRequestConfig> {
baseURL?: string;
defaultHeaders?: any;
responseParser?: <T>(res: AxiosResponse) => T;
}
export default class HTTPClient {
private instance;
private config;
constructor(config?: httpClientConfig);
get<T>(url: string, params?: any): Promise<T>;
getStream(url: string, params?: any): Promise<Readable>;
post<T>(url: string, body?: any, config?: Partial<AxiosRequestConfig>): Promise<T>;
private responseParse;
put<T>(url: string, body?: any, config?: Partial<AxiosRequestConfig>): Promise<T>;
postForm<T>(url: string, body?: any): Promise<T>;
postBinary<T>(url: string, data: Buffer | Readable, contentType?: string): Promise<T>;
delete<T>(url: string, params?: any): Promise<T>;
private wrapError;
}
export {};
+101
View File
@@ -0,0 +1,101 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const axios_1 = require("axios");
const stream_1 = require("stream");
const exceptions_1 = require("./exceptions");
const fileType = require("file-type");
const qs = require("querystring");
const pkg = require("../package.json");
class HTTPClient {
constructor(config = {}) {
this.config = config;
const { baseURL, defaultHeaders } = config;
this.instance = axios_1.default.create({
baseURL,
headers: Object.assign({}, defaultHeaders, {
"User-Agent": `${pkg.name}/${pkg.version}`,
}),
});
this.instance.interceptors.response.use(res => res, err => Promise.reject(this.wrapError(err)));
}
async get(url, params) {
const res = await this.instance.get(url, { params });
return res.data;
}
async getStream(url, params) {
const res = await this.instance.get(url, {
params,
responseType: "stream",
});
return res.data;
}
async post(url, body, config) {
const res = await this.instance.post(url, body, Object.assign({ headers: Object.assign({ "Content-Type": "application/json" }, (config && config.headers)) }, config));
return this.responseParse(res);
}
responseParse(res) {
const { responseParser } = this.config;
if (responseParser)
return responseParser(res);
else
return res.data;
}
async put(url, body, config) {
const res = await this.instance.put(url, body, Object.assign({ headers: Object.assign({ "Content-Type": "application/json" }, (config && config.headers)) }, config));
return this.responseParse(res);
}
async postForm(url, body) {
const res = await this.instance.post(url, qs.stringify(body), {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
return res.data;
}
async postBinary(url, data, contentType) {
const buffer = await (async () => {
if (Buffer.isBuffer(data)) {
return data;
}
else if (data instanceof stream_1.Readable) {
return new Promise((resolve, reject) => {
const buffers = [];
let size = 0;
data.on("data", (chunk) => {
buffers.push(chunk);
size += chunk.length;
});
data.on("end", () => resolve(Buffer.concat(buffers, size)));
data.on("error", reject);
});
}
else {
throw new Error("invalid data type for postBinary");
}
})();
const res = await this.instance.post(url, buffer, {
headers: {
"Content-Type": contentType || fileType(buffer).mime,
"Content-Length": buffer.length,
},
});
return res.data;
}
async delete(url, params) {
const res = await this.instance.delete(url, { params });
return res.data;
}
wrapError(err) {
if (err.response) {
return new exceptions_1.HTTPError(err.message, err.response.status, err.response.statusText, err);
}
else if (err.code) {
return new exceptions_1.RequestError(err.message, err.code, err);
}
else if (err.config) {
// unknown, but from axios
return new exceptions_1.ReadError(err);
}
// otherwise, just rethrow
return err;
}
}
exports.default = HTTPClient;
+6
View File
@@ -0,0 +1,6 @@
import Client, { OAuth } from "./client";
import middleware from "./middleware";
import validateSignature from "./validate-signature";
export { Client, middleware, validateSignature, OAuth };
export * from "./exceptions";
export * from "./types";
+23
View File
@@ -0,0 +1,23 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OAuth = exports.validateSignature = exports.middleware = exports.Client = void 0;
const client_1 = require("./client");
exports.Client = client_1.default;
Object.defineProperty(exports, "OAuth", { enumerable: true, get: function () { return client_1.OAuth; } });
const middleware_1 = require("./middleware");
exports.middleware = middleware_1.default;
const validate_signature_1 = require("./validate-signature");
exports.validateSignature = validate_signature_1.default;
// re-export exceptions and types
__exportStar(require("./exceptions"), exports);
__exportStar(require("./types"), exports);
+10
View File
@@ -0,0 +1,10 @@
/// <reference types="node" />
import * as http from "http";
import * as Types from "./types";
export declare type Request = http.IncomingMessage & {
body: any;
};
export declare type Response = http.ServerResponse;
export declare type NextCallback = (err?: Error) => void;
export declare type Middleware = (req: Request, res: Response, next: NextCallback) => void | Promise<void>;
export default function middleware(config: Types.MiddlewareConfig): Middleware;
+53
View File
@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const body_parser_1 = require("body-parser");
const exceptions_1 = require("./exceptions");
const Types = require("./types");
const validate_signature_1 = require("./validate-signature");
function isValidBody(body) {
return (body && typeof body === "string") || Buffer.isBuffer(body);
}
function middleware(config) {
if (!config.channelSecret) {
throw new Error("no channel secret");
}
const secret = config.channelSecret;
const _middleware = async (req, res, next) => {
// header names are lower-cased
// https://nodejs.org/api/http.html#http_message_headers
const signature = req.headers[Types.LINE_SIGNATURE_HTTP_HEADER_NAME];
if (!signature) {
next(new exceptions_1.SignatureValidationFailed("no signature"));
return;
}
const body = await (async () => {
if (isValidBody(req.rawBody)) {
// rawBody is provided in Google Cloud Functions and others
return req.rawBody;
}
else if (isValidBody(req.body)) {
return req.body;
}
else {
// body may not be parsed yet, parse it to a buffer
return new Promise((resolve, reject) => body_parser_1.raw({ type: "*/*" })(req, res, (error) => error ? reject(error) : resolve(req.body)));
}
})();
if (!validate_signature_1.default(body, secret, signature)) {
next(new exceptions_1.SignatureValidationFailed("signature validation failed", signature));
return;
}
const strBody = Buffer.isBuffer(body) ? body.toString() : body;
try {
req.body = JSON.parse(strBody);
next();
}
catch (err) {
next(new exceptions_1.JSONParseError(err.message, strBody));
}
};
return (req, res, next) => {
_middleware(req, res, next).catch(next);
};
}
exports.default = middleware;
+2107
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LINE_SIGNATURE_HTTP_HEADER_NAME = exports.LINE_REQUEST_ID_HTTP_HEADER_NAME = void 0;
exports.LINE_REQUEST_ID_HTTP_HEADER_NAME = "x-line-request-id";
exports.LINE_SIGNATURE_HTTP_HEADER_NAME = "x-line-signature";
+2
View File
@@ -0,0 +1,2 @@
export declare function toArray<T>(maybeArr: T | T[]): T[];
export declare function ensureJSON<T>(raw: T): T;
+17
View File
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensureJSON = exports.toArray = void 0;
const exceptions_1 = require("./exceptions");
function toArray(maybeArr) {
return Array.isArray(maybeArr) ? maybeArr : [maybeArr];
}
exports.toArray = toArray;
function ensureJSON(raw) {
if (typeof raw === "object") {
return raw;
}
else {
throw new exceptions_1.JSONParseError("Failed to parse response body as JSON", raw);
}
}
exports.ensureJSON = ensureJSON;
+2
View File
@@ -0,0 +1,2 @@
/// <reference types="node" />
export default function validateSignature(body: string | Buffer, channelSecret: string, signature: string): boolean;
+16
View File
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const crypto_1 = require("crypto");
function s2b(str, encoding) {
return Buffer.from(str, encoding);
}
function safeCompare(a, b) {
if (a.length !== b.length) {
return false;
}
return crypto_1.timingSafeEqual(a, b);
}
function validateSignature(body, channelSecret, signature) {
return safeCompare(crypto_1.createHmac("SHA256", channelSecret).update(body).digest(), s2b(signature, "base64"));
}
exports.default = validateSignature;
+650
View File
@@ -0,0 +1,650 @@
import { Readable } from "stream";
import HTTPClient from "./http";
import * as Types from "./types";
import { AxiosResponse, AxiosRequestConfig } from "axios";
import { ensureJSON, toArray } from "./utils";
type ChatType = "group" | "room";
type RequestOption = {
retryKey: string;
};
import {
MESSAGING_API_PREFIX,
DATA_API_PREFIX,
OAUTH_BASE_PREFIX,
} from "./endpoints";
export default class Client {
public config: Types.ClientConfig;
private http: HTTPClient;
private requestOption: Partial<RequestOption> = {};
constructor(config: Types.ClientConfig) {
if (!config.channelAccessToken) {
throw new Error("no channel access token");
}
this.config = config;
this.http = new HTTPClient({
defaultHeaders: {
Authorization: "Bearer " + this.config.channelAccessToken,
},
responseParser: this.parseHTTPResponse.bind(this),
...config.httpConfig,
});
}
public setRequestOptionOnce(option: Partial<RequestOption>) {
this.requestOption = option;
}
private generateRequestConfig(): Partial<AxiosRequestConfig> {
const config: Partial<AxiosRequestConfig> = { headers: {} };
if (this.requestOption.retryKey) {
config.headers["X-Line-Retry-Key"] = this.requestOption.retryKey;
}
// clear requestOption
this.requestOption = {};
return config;
}
private parseHTTPResponse(response: AxiosResponse) {
const { LINE_REQUEST_ID_HTTP_HEADER_NAME } = Types;
let resBody = {
...response.data,
};
if (response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME]) {
resBody[LINE_REQUEST_ID_HTTP_HEADER_NAME] =
response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME];
}
return resBody;
}
public pushMessage(
to: string,
messages: Types.Message | Types.Message[],
notificationDisabled: boolean = false,
): Promise<Types.MessageAPIResponseBase> {
return this.http.post(
`${MESSAGING_API_PREFIX}/message/push`,
{
messages: toArray(messages),
to,
notificationDisabled,
},
this.generateRequestConfig(),
);
}
public replyMessage(
replyToken: string,
messages: Types.Message | Types.Message[],
notificationDisabled: boolean = false,
): Promise<Types.MessageAPIResponseBase> {
return this.http.post(`${MESSAGING_API_PREFIX}/message/reply`, {
messages: toArray(messages),
replyToken,
notificationDisabled,
});
}
public async multicast(
to: string[],
messages: Types.Message | Types.Message[],
notificationDisabled: boolean = false,
): Promise<Types.MessageAPIResponseBase> {
return this.http.post(
`${MESSAGING_API_PREFIX}/message/multicast`,
{
messages: toArray(messages),
to,
notificationDisabled,
},
this.generateRequestConfig(),
);
}
public async narrowcast(
messages: Types.Message | Types.Message[],
recipient?: Types.ReceieptObject,
filter?: { demographic: Types.DemographicFilterObject },
limit?: { max: number },
notificationDisabled: boolean = false,
): Promise<Types.MessageAPIResponseBase> {
return this.http.post(
`${MESSAGING_API_PREFIX}/message/narrowcast`,
{
messages: toArray(messages),
recipient,
filter,
limit,
notificationDisabled,
},
this.generateRequestConfig(),
);
}
public async broadcast(
messages: Types.Message | Types.Message[],
notificationDisabled: boolean = false,
): Promise<Types.MessageAPIResponseBase> {
return this.http.post(
`${MESSAGING_API_PREFIX}/message/broadcast`,
{
messages: toArray(messages),
notificationDisabled,
},
this.generateRequestConfig(),
);
}
public async getProfile(userId: string): Promise<Types.Profile> {
const profile = await this.http.get<Types.Profile>(
`${MESSAGING_API_PREFIX}/profile/${userId}`,
);
return ensureJSON(profile);
}
private async getChatMemberProfile(
chatType: ChatType,
chatId: string,
userId: string,
): Promise<Types.Profile> {
const profile = await this.http.get<Types.Profile>(
`${MESSAGING_API_PREFIX}/${chatType}/${chatId}/member/${userId}`,
);
return ensureJSON(profile);
}
public async getGroupMemberProfile(
groupId: string,
userId: string,
): Promise<Types.Profile> {
return this.getChatMemberProfile("group", groupId, userId);
}
public async getRoomMemberProfile(
roomId: string,
userId: string,
): Promise<Types.Profile> {
return this.getChatMemberProfile("room", roomId, userId);
}
private async getChatMemberIds(
chatType: ChatType,
chatId: string,
): Promise<string[]> {
let memberIds: string[] = [];
let start: string;
do {
const res = await this.http.get<{ memberIds: string[]; next?: string }>(
`${MESSAGING_API_PREFIX}/${chatType}/${chatId}/members/ids`,
start ? { start } : null,
);
ensureJSON(res);
memberIds = memberIds.concat(res.memberIds);
start = res.next;
} while (start);
return memberIds;
}
public async getGroupMemberIds(groupId: string): Promise<string[]> {
return this.getChatMemberIds("group", groupId);
}
public async getRoomMemberIds(roomId: string): Promise<string[]> {
return this.getChatMemberIds("room", roomId);
}
public async getGroupMembersCount(
groupId: string,
): Promise<Types.MembersCountResponse> {
const groupMemberCount = await this.http.get<Types.MembersCountResponse>(
`${MESSAGING_API_PREFIX}/group/${groupId}/members/count`,
);
return ensureJSON(groupMemberCount);
}
public async getRoomMembersCount(
roomId: string,
): Promise<Types.MembersCountResponse> {
const roomMemberCount = await this.http.get<Types.MembersCountResponse>(
`${MESSAGING_API_PREFIX}/room/${roomId}/members/count`,
);
return ensureJSON(roomMemberCount);
}
public async getGroupSummary(
groupId: string,
): Promise<Types.GroupSummaryResponse> {
const groupSummary = await this.http.get<Types.GroupSummaryResponse>(
`${MESSAGING_API_PREFIX}/group/${groupId}/summary`,
);
return ensureJSON(groupSummary);
}
public async getMessageContent(messageId: string): Promise<Readable> {
return this.http.getStream(
`${DATA_API_PREFIX}/message/${messageId}/content`,
);
}
private leaveChat(chatType: ChatType, chatId: string): Promise<any> {
return this.http.post(
`${MESSAGING_API_PREFIX}/${chatType}/${chatId}/leave`,
);
}
public async leaveGroup(groupId: string): Promise<any> {
return this.leaveChat("group", groupId);
}
public async leaveRoom(roomId: string): Promise<any> {
return this.leaveChat("room", roomId);
}
public async getRichMenu(
richMenuId: string,
): Promise<Types.RichMenuResponse> {
const res = await this.http.get<Types.RichMenuResponse>(
`${MESSAGING_API_PREFIX}/richmenu/${richMenuId}`,
);
return ensureJSON(res);
}
public async createRichMenu(richMenu: Types.RichMenu): Promise<string> {
const res = await this.http.post<any>(
`${MESSAGING_API_PREFIX}/richmenu`,
richMenu,
);
return ensureJSON(res).richMenuId;
}
public async deleteRichMenu(richMenuId: string): Promise<any> {
return this.http.delete(`${MESSAGING_API_PREFIX}/richmenu/${richMenuId}`);
}
public async getRichMenuIdOfUser(userId: string): Promise<string> {
const res = await this.http.get<any>(
`${MESSAGING_API_PREFIX}/user/${userId}/richmenu`,
);
return ensureJSON(res).richMenuId;
}
public async linkRichMenuToUser(
userId: string,
richMenuId: string,
): Promise<any> {
return this.http.post(
`${MESSAGING_API_PREFIX}/user/${userId}/richmenu/${richMenuId}`,
);
}
public async unlinkRichMenuFromUser(userId: string): Promise<any> {
return this.http.delete(`${MESSAGING_API_PREFIX}/user/${userId}/richmenu`);
}
public async linkRichMenuToMultipleUsers(
richMenuId: string,
userIds: string[],
): Promise<any> {
return this.http.post(`${MESSAGING_API_PREFIX}/richmenu/bulk/link`, {
richMenuId,
userIds,
});
}
public async unlinkRichMenusFromMultipleUsers(
userIds: string[],
): Promise<any> {
return this.http.post(`${MESSAGING_API_PREFIX}/richmenu/bulk/unlink`, {
userIds,
});
}
public async getRichMenuImage(richMenuId: string): Promise<Readable> {
return this.http.getStream(
`${DATA_API_PREFIX}/richmenu/${richMenuId}/content`,
);
}
public async setRichMenuImage(
richMenuId: string,
data: Buffer | Readable,
contentType?: string,
): Promise<any> {
return this.http.postBinary(
`${DATA_API_PREFIX}/richmenu/${richMenuId}/content`,
data,
contentType,
);
}
public async getRichMenuList(): Promise<Array<Types.RichMenuResponse>> {
const res = await this.http.get<any>(
`${MESSAGING_API_PREFIX}/richmenu/list`,
);
return ensureJSON(res).richmenus;
}
public async setDefaultRichMenu(richMenuId: string): Promise<{}> {
return this.http.post(
`${MESSAGING_API_PREFIX}/user/all/richmenu/${richMenuId}`,
);
}
public async getDefaultRichMenuId(): Promise<string> {
const res = await this.http.get<any>(
`${MESSAGING_API_PREFIX}/user/all/richmenu`,
);
return ensureJSON(res).richMenuId;
}
public async deleteDefaultRichMenu(): Promise<{}> {
return this.http.delete(`${MESSAGING_API_PREFIX}/user/all/richmenu`);
}
public async getLinkToken(userId: string): Promise<string> {
const res = await this.http.post<any>(
`${MESSAGING_API_PREFIX}/user/${userId}/linkToken`,
);
return ensureJSON(res).linkToken;
}
public async getNumberOfSentReplyMessages(
date: string,
): Promise<Types.NumberOfMessagesSentResponse> {
const res = await this.http.get<Types.NumberOfMessagesSentResponse>(
`${MESSAGING_API_PREFIX}/message/delivery/reply?date=${date}`,
);
return ensureJSON(res);
}
public async getNumberOfSentPushMessages(
date: string,
): Promise<Types.NumberOfMessagesSentResponse> {
const res = await this.http.get<Types.NumberOfMessagesSentResponse>(
`${MESSAGING_API_PREFIX}/message/delivery/push?date=${date}`,
);
return ensureJSON(res);
}
public async getNumberOfSentMulticastMessages(
date: string,
): Promise<Types.NumberOfMessagesSentResponse> {
const res = await this.http.get<Types.NumberOfMessagesSentResponse>(
`${MESSAGING_API_PREFIX}/message/delivery/multicast?date=${date}`,
);
return ensureJSON(res);
}
public async getNarrowcastProgress(
requestId: string,
): Promise<Types.NarrowcastProgressResponse> {
const res = await this.http.get<Types.NarrowcastProgressResponse>(
`${MESSAGING_API_PREFIX}/message/progress/narrowcast?requestId=${requestId}`,
);
return ensureJSON(res);
}
public async getTargetLimitForAdditionalMessages(): Promise<
Types.TargetLimitForAdditionalMessages
> {
const res = await this.http.get<Types.TargetLimitForAdditionalMessages>(
`${MESSAGING_API_PREFIX}/message/quota`,
);
return ensureJSON(res);
}
public async getNumberOfMessagesSentThisMonth(): Promise<
Types.NumberOfMessagesSentThisMonth
> {
const res = await this.http.get<Types.NumberOfMessagesSentThisMonth>(
`${MESSAGING_API_PREFIX}/message/quota/consumption`,
);
return ensureJSON(res);
}
public async getNumberOfSentBroadcastMessages(
date: string,
): Promise<Types.NumberOfMessagesSentResponse> {
const res = await this.http.get<Types.NumberOfMessagesSentResponse>(
`${MESSAGING_API_PREFIX}/message/delivery/broadcast?date=${date}`,
);
return ensureJSON(res);
}
public async getNumberOfMessageDeliveries(
date: string,
): Promise<Types.NumberOfMessageDeliveriesResponse> {
const res = await this.http.get<Types.NumberOfMessageDeliveriesResponse>(
`${MESSAGING_API_PREFIX}/insight/message/delivery?date=${date}`,
);
return ensureJSON(res);
}
public async getNumberOfFollowers(
date: string,
): Promise<Types.NumberOfFollowersResponse> {
const res = await this.http.get<Types.NumberOfFollowersResponse>(
`${MESSAGING_API_PREFIX}/insight/followers?date=${date}`,
);
return ensureJSON(res);
}
public async getFriendDemographics(): Promise<Types.FriendDemographics> {
const res = await this.http.get<Types.FriendDemographics>(
`${MESSAGING_API_PREFIX}/insight/demographic`,
);
return ensureJSON(res);
}
public async getUserInteractionStatistics(
requestId: string,
): Promise<Types.UserInteractionStatistics> {
const res = await this.http.get<Types.UserInteractionStatistics>(
`${MESSAGING_API_PREFIX}/insight/message/event?requestId=${requestId}`,
);
return ensureJSON(res);
}
public async createUploadAudienceGroup(uploadAudienceGroup: {
description: string;
isIfaAudience: boolean;
audiences: { id: string }[];
uploadDescription?: string;
}) {
const res = await this.http.post<{
audienceGroupId: number;
type: string;
description: string;
created: number;
}>(`${MESSAGING_API_PREFIX}/audienceGroup/upload`, {
...uploadAudienceGroup,
});
return ensureJSON(res);
}
public async updateUploadAudienceGroup(
uploadAudienceGroup: {
audienceGroupId: number;
description?: string;
uploadDescription?: string;
audiences: { id: string }[];
},
// for set request timeout
httpConfig?: Partial<AxiosRequestConfig>,
) {
const res = await this.http.put<{}>(
`${MESSAGING_API_PREFIX}/audienceGroup/upload`,
{
...uploadAudienceGroup,
},
httpConfig,
);
return ensureJSON(res);
}
public async createClickAudienceGroup(clickAudienceGroup: {
description: string;
requestId: string;
clickUrl?: string;
}) {
const res = await this.http.post<
{
audienceGroupId: number;
type: string;
created: number;
} & typeof clickAudienceGroup
>(`${MESSAGING_API_PREFIX}/audienceGroup/click`, {
...clickAudienceGroup,
});
return ensureJSON(res);
}
public async createImpAudienceGroup(impAudienceGroup: {
requestId: string;
description: string;
}) {
const res = await this.http.post<
{
audienceGroupId: number;
type: string;
created: number;
} & typeof impAudienceGroup
>(`${MESSAGING_API_PREFIX}/audienceGroup/imp`, {
...impAudienceGroup,
});
return ensureJSON(res);
}
public async setDescriptionAudienceGroup(
description: string,
audienceGroupId: string,
) {
const res = await this.http.put<{}>(
`${MESSAGING_API_PREFIX}/audienceGroup/${audienceGroupId}/updateDescription`,
{
description,
},
);
return ensureJSON(res);
}
public async deleteAudienceGroup(audienceGroupId: string) {
const res = await this.http.delete<{}>(
`${MESSAGING_API_PREFIX}/audienceGroup/${audienceGroupId}`,
);
return ensureJSON(res);
}
public async getAudienceGroup(audienceGroupId: string) {
const res = await this.http.get<Types.AudienceGroup>(
`${MESSAGING_API_PREFIX}/audienceGroup/${audienceGroupId}`,
);
return ensureJSON(res);
}
public async getAudienceGroups(
page: number,
description?: string,
status?: Types.AudienceGroupStatus,
size?: number,
createRoute?: Types.AudienceGroupCreateRoute,
includesExternalPublicGroups?: boolean,
) {
const res = await this.http.get<{
audienceGroups: Types.AudienceGroups;
hasNextPage: boolean;
totalCount: number;
readWriteAudienceGroupTotalCount: number;
page: number;
size: number;
}>(`${MESSAGING_API_PREFIX}/audienceGroup/list`, {
page,
description,
status,
size,
createRoute,
includesExternalPublicGroups,
});
return ensureJSON(res);
}
public async getAudienceGroupAuthorityLevel() {
const res = await this.http.get<{
authorityLevel: Types.AudienceGroupAuthorityLevel;
}>(`${MESSAGING_API_PREFIX}/audienceGroup/authorityLevel`);
return ensureJSON(res);
}
public async changeAudienceGroupAuthorityLevel(
authorityLevel: Types.AudienceGroupAuthorityLevel,
) {
const res = await this.http.put<{}>(
`${MESSAGING_API_PREFIX}/audienceGroup/authorityLevel`,
{ authorityLevel },
);
return ensureJSON(res);
}
}
export class OAuth {
private http: HTTPClient;
constructor() {
this.http = new HTTPClient();
}
public issueAccessToken(
client_id: string,
client_secret: string,
): Promise<Types.ChannelAccessToken> {
return this.http.postForm(`${OAUTH_BASE_PREFIX}/accessToken`, {
grant_type: "client_credentials",
client_id,
client_secret,
});
}
public revokeAccessToken(access_token: string): Promise<{}> {
return this.http.postForm(`${OAUTH_BASE_PREFIX}/revoke`, { access_token });
}
public issueChannelAccessTokenV2_1(
client_assertion: string,
): Promise<Types.ChannelAccessToken> {
return this.http.postForm(`${OAUTH_BASE_PREFIX}/v2.1/token`, {
grant_type: "client_credentials",
client_assertion_type:
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion,
});
}
public getIssuedChannelAccessTokenV2_1(
client_assertion: string,
): Promise<{ access_tokens: string[] }> {
return this.http.get(`${OAUTH_BASE_PREFIX}/v2.1/tokens`, {
client_assertion_type:
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion,
});
}
public revokeChannelAccessTokenV2_1(
client_id: string,
client_secret: string,
access_token: string,
): Promise<{}> {
return this.http.postForm(`${OAUTH_BASE_PREFIX}/v2.1/revoke`, {
client_id,
client_secret,
access_token,
});
}
}
+3
View File
@@ -0,0 +1,3 @@
export const MESSAGING_API_PREFIX = `https://api.line.me/v2/bot`;
export const DATA_API_PREFIX = `https://api-data.line.me/v2/bot`;
export const OAUTH_BASE_PREFIX = `https://api.line.me/v2/oauth`;
+38
View File
@@ -0,0 +1,38 @@
export class SignatureValidationFailed extends Error {
constructor(message: string, public signature?: string) {
super(message);
}
}
export class JSONParseError extends Error {
constructor(message: string, public raw: any) {
super(message);
}
}
export class RequestError extends Error {
constructor(
message: string,
public code: string,
private originalError: Error,
) {
super(message);
}
}
export class ReadError extends Error {
constructor(private originalError: Error) {
super(originalError.message);
}
}
export class HTTPError extends Error {
constructor(
message: string,
public statusCode: number,
public statusMessage: string,
public originalError: any,
) {
super(message);
}
}
+156
View File
@@ -0,0 +1,156 @@
import axios, {
AxiosInstance,
AxiosError,
AxiosResponse,
AxiosRequestConfig,
} from "axios";
import { Readable } from "stream";
import { HTTPError, ReadError, RequestError } from "./exceptions";
import * as fileType from "file-type";
import * as qs from "querystring";
const pkg = require("../package.json");
interface httpClientConfig extends Partial<AxiosRequestConfig> {
baseURL?: string;
defaultHeaders?: any;
responseParser?: <T>(res: AxiosResponse) => T;
}
export default class HTTPClient {
private instance: AxiosInstance;
private config: httpClientConfig;
constructor(config: httpClientConfig = {}) {
this.config = config;
const { baseURL, defaultHeaders } = config;
this.instance = axios.create({
baseURL,
headers: Object.assign({}, defaultHeaders, {
"User-Agent": `${pkg.name}/${pkg.version}`,
}),
});
this.instance.interceptors.response.use(
res => res,
err => Promise.reject(this.wrapError(err)),
);
}
public async get<T>(url: string, params?: any): Promise<T> {
const res = await this.instance.get(url, { params });
return res.data;
}
public async getStream(url: string, params?: any): Promise<Readable> {
const res = await this.instance.get(url, {
params,
responseType: "stream",
});
return res.data as Readable;
}
public async post<T>(
url: string,
body?: any,
config?: Partial<AxiosRequestConfig>,
): Promise<T> {
const res = await this.instance.post(url, body, {
headers: {
"Content-Type": "application/json",
...(config && config.headers),
},
...config,
});
return this.responseParse(res);
}
private responseParse(res: AxiosResponse) {
const { responseParser } = this.config;
if (responseParser) return responseParser(res);
else return res.data;
}
public async put<T>(
url: string,
body?: any,
config?: Partial<AxiosRequestConfig>,
): Promise<T> {
const res = await this.instance.put(url, body, {
headers: {
"Content-Type": "application/json",
...(config && config.headers),
},
...config,
});
return this.responseParse(res);
}
public async postForm<T>(url: string, body?: any): Promise<T> {
const res = await this.instance.post(url, qs.stringify(body), {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
return res.data;
}
public async postBinary<T>(
url: string,
data: Buffer | Readable,
contentType?: string,
): Promise<T> {
const buffer = await (async (): Promise<Buffer> => {
if (Buffer.isBuffer(data)) {
return data;
} else if (data instanceof Readable) {
return new Promise<Buffer>((resolve, reject) => {
const buffers: Buffer[] = [];
let size = 0;
data.on("data", (chunk: Buffer) => {
buffers.push(chunk);
size += chunk.length;
});
data.on("end", () => resolve(Buffer.concat(buffers, size)));
data.on("error", reject);
});
} else {
throw new Error("invalid data type for postBinary");
}
})();
const res = await this.instance.post(url, buffer, {
headers: {
"Content-Type": contentType || fileType(buffer).mime,
"Content-Length": buffer.length,
},
});
return res.data;
}
public async delete<T>(url: string, params?: any): Promise<T> {
const res = await this.instance.delete(url, { params });
return res.data;
}
private wrapError(err: AxiosError): Error {
if (err.response) {
return new HTTPError(
err.message,
err.response.status,
err.response.statusText,
err,
);
} else if (err.code) {
return new RequestError(err.message, err.code, err);
} else if (err.config) {
// unknown, but from axios
return new ReadError(err);
}
// otherwise, just rethrow
return err;
}
}
+9
View File
@@ -0,0 +1,9 @@
import Client, { OAuth } from "./client";
import middleware from "./middleware";
import validateSignature from "./validate-signature";
export { Client, middleware, validateSignature, OAuth };
// re-export exceptions and types
export * from "./exceptions";
export * from "./types";
+75
View File
@@ -0,0 +1,75 @@
import { raw } from "body-parser";
import * as http from "http";
import { JSONParseError, SignatureValidationFailed } from "./exceptions";
import * as Types from "./types";
import validateSignature from "./validate-signature";
export type Request = http.IncomingMessage & { body: any };
export type Response = http.ServerResponse;
export type NextCallback = (err?: Error) => void;
export type Middleware = (
req: Request,
res: Response,
next: NextCallback,
) => void | Promise<void>;
function isValidBody(body?: any): body is string | Buffer {
return (body && typeof body === "string") || Buffer.isBuffer(body);
}
export default function middleware(config: Types.MiddlewareConfig): Middleware {
if (!config.channelSecret) {
throw new Error("no channel secret");
}
const secret = config.channelSecret;
const _middleware: Middleware = async (req, res, next) => {
// header names are lower-cased
// https://nodejs.org/api/http.html#http_message_headers
const signature = req.headers[
Types.LINE_SIGNATURE_HTTP_HEADER_NAME
] as string;
if (!signature) {
next(new SignatureValidationFailed("no signature"));
return;
}
const body = await (async (): Promise<string | Buffer> => {
if (isValidBody((req as any).rawBody)) {
// rawBody is provided in Google Cloud Functions and others
return (req as any).rawBody;
} else if (isValidBody(req.body)) {
return req.body;
} else {
// body may not be parsed yet, parse it to a buffer
return new Promise<Buffer>((resolve, reject) =>
raw({ type: "*/*" })(req as any, res as any, (error: Error) =>
error ? reject(error) : resolve(req.body),
),
);
}
})();
if (!validateSignature(body, secret, signature)) {
next(
new SignatureValidationFailed("signature validation failed", signature),
);
return;
}
const strBody = Buffer.isBuffer(body) ? body.toString() : body;
try {
req.body = JSON.parse(strBody);
next();
} catch (err) {
next(new JSONParseError(err.message, strBody));
}
};
return (req, res, next): void => {
(<Promise<void>>_middleware(req, res, next)).catch(next);
};
}
+2454
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
import { JSONParseError } from "./exceptions";
export function toArray<T>(maybeArr: T | T[]): T[] {
return Array.isArray(maybeArr) ? maybeArr : [maybeArr];
}
export function ensureJSON<T>(raw: T): T {
if (typeof raw === "object") {
return raw;
} else {
throw new JSONParseError("Failed to parse response body as JSON", raw);
}
}
+23
View File
@@ -0,0 +1,23 @@
import { createHmac, timingSafeEqual } from "crypto";
function s2b(str: string, encoding: BufferEncoding): Buffer {
return Buffer.from(str, encoding);
}
function safeCompare(a: Buffer, b: Buffer): boolean {
if (a.length !== b.length) {
return false;
}
return timingSafeEqual(a, b);
}
export default function validateSignature(
body: string | Buffer,
channelSecret: string,
signature: string,
): boolean {
return safeCompare(
createHmac("SHA256", channelSecret).update(body).digest(),
s2b(signature, "base64"),
);
}
+118
View File
@@ -0,0 +1,118 @@
{
"_args": [
[
"@line/bot-sdk@7.0.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "@line/bot-sdk@7.0.0",
"_id": "@line/bot-sdk@7.0.0",
"_inBundle": false,
"_integrity": "sha512-piS9LUg/fyFFlSMP9OdnxtlAS0pcEkhABVCG2xVaZ2wlaSCfG8pMSAOrVsZQYMRQwS3p3QTEFwuVaXnN4vn7tw==",
"_location": "/@line/bot-sdk",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@line/bot-sdk@7.0.0",
"name": "@line/bot-sdk",
"escapedName": "@line%2fbot-sdk",
"scope": "@line",
"rawSpec": "7.0.0",
"saveSpec": null,
"fetchSpec": "7.0.0"
},
"_requiredBy": [
"/@line/liff"
],
"_resolved": "https://registry.npmjs.org/@line/bot-sdk/-/bot-sdk-7.0.0.tgz",
"_spec": "7.0.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"bugs": {
"url": "https://github.com/line/line-bot-sdk-nodejs/issues"
},
"dependencies": {
"@types/body-parser": "^1.19.0",
"@types/file-type": "^5.2.2",
"@types/node": "^12.12.47",
"axios": "^0.19.2",
"body-parser": "^1.19.0",
"file-type": "^7.7.1"
},
"description": "Node.js SDK for LINE Messaging API",
"devDependencies": {
"@types/express": "^4.17.6",
"@types/finalhandler": "^1.1.0",
"@types/mocha": "^7.0.2",
"express": "^4.17.1",
"finalhandler": "^1.1.2",
"husky": "^4.2.5",
"mocha": "^8.0.1",
"nock": "^12.0.3",
"nyc": "^15.1.0",
"prettier": "^2.0.5",
"ts-node": "^8.10.2",
"typescript": "^3.9.5",
"vuepress": "^1.5.2"
},
"engines": {
"node": ">=8"
},
"files": [
"dist",
"lib"
],
"homepage": "https://github.com/line/line-bot-sdk-nodejs#readme",
"husky": {
"hooks": {
"pre-commit": "npm run format:check",
"pre-push": "npm run format:check && npm run build && npm run test"
}
},
"keywords": [
"node",
"line",
"sdk"
],
"license": "Apache-2.0",
"main": "dist/index.js",
"mocha": {
"require": "ts-node/register",
"spec": "test/*.spec.ts"
},
"name": "@line/bot-sdk",
"nyc": {
"require": [
"ts-node/register"
],
"extension": [
".ts"
],
"reporter": [
"lcov",
"text"
],
"sourceMap": true,
"instrument": true
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/line/line-bot-sdk-nodejs.git"
},
"scripts": {
"build": "tsc",
"clean": "rm -rf dist/*",
"docs": "vuepress dev docs",
"docs:build": "vuepress build docs",
"docs:deploy": "./deploy-docs.sh",
"format": "npm run prettier -- --write",
"format:check": "npm run prettier -- -l",
"prebuild": "npm run format:check && npm run clean",
"pretest": "npm run format && npm run build",
"prettier": "prettier --parser typescript --trailing-comma all --arrow-parens avoid \"{lib,test}/**/*.ts\"",
"release": "npm run build && npm publish --access public",
"test": "TEST_PORT=1234 TS_NODE_CACHE=0 nyc mocha"
},
"types": "dist/index.d.ts",
"version": "7.0.0"
}
+50
View File
@@ -0,0 +1,50 @@
# LINE Front-end Framework
LINE Front-end Framework (LIFF) is a platform for web apps provided by LINE.
The web apps running on this platform are called LIFF apps.
LIFF apps can get data from the LINE Platform such as the LINE user ID.
The LIFF app can use such data to provide features that utilize user information and send messages on the user's behalf.
## Installation
Using npm
```
$ npm install --save @line/liff
```
Using yarn
```
$ yarn add @line/liff
```
**Note:** It is the developer's responsibility to use an appropriate SDK version. To keep your SDK version up to date, check the [LIFF release notes](https://developers.line.biz/en/docs/liff/release-notes/#liff-version-and-release-date) on a regular basis, and update your local SDK frequently. For more information about LIFF's versioning policy, see [LIFF SDK (sdk.js) update policy](https://developers.line.biz/en/docs/liff/versioning-policy/#update-policy).
## Usage
Include the SDK in your JavaScript or TypeScript files:
```ts
import liff from '@line/liff';
liff.init({ liffID: 'myLiffId' });
```
*Type definitions for TypeScript are already included in the `@line/liff` package*
### Warning
For backward compatibility, please do not declare or modify the global LIFF instance `window.liff`.
Declaring or modifying `window.liff` may cause malfunctioning of the LINE app.
### CDN
The LIFF SDK is also available on LINE's CDN.
```html
<script src="https://static.line-scdn.net/liff/edge/versions/2/sdk.js"></script>
```
See [integrating LIFF](https://developers.line.biz/en/docs/liff/developing-liff-apps/#integrating-sdk) for more details.
## Documentation
See [LIFF Document](https://developers.line.biz/en/docs/liff/) and [API Reference](https://developers.line.biz/en/reference/liff/).
## License
Using LIFF means you agree to the [LINE Developers Agreement](https://terms2.line.me/LINE_Developers_Agreement).
+11
View File
@@ -0,0 +1,11 @@
interface Friendship {
friendFlag: boolean;
}
/**
* Gets the friendship status of the LINE Official Account that's linked to the LINE Login channel to which the LIFF app is added.
* Learn more on how to {@link https://developers.line.biz/en/docs/line-login/link-a-bot/|link a LINE Official Account to a LINE Login channel.}
* @export
* @returns {Promise<Friendship>}
*/
export default function getFriendship(): Promise<Friendship>;
export {};
+13
View File
@@ -0,0 +1,13 @@
interface Profile {
userId: string;
displayName: string;
pictureUrl?: string;
statusMessage?: string;
}
/**
* Gets the current user's profile.
* @export
* @returns {Promise<Profile>}
*/
export default function getProfile(): Promise<Profile>;
export {};
+27
View File
@@ -0,0 +1,27 @@
import { Message, ImageMapMessage } from '@line/bot-sdk/lib/types';
/**
* the options to choose the way to send message in sendMessages method
* none: send a message to context
* ott: send messages to users bound to One-Time-Token (can send up to 10 ppl/group)
*/
export declare enum SendMessagesOptionsType {
none = "none",
ott = "ott"
}
declare type LiffMessage = Exclude<Message, ImageMapMessage>;
export declare type SendMessagesParams = LiffMessage[];
export interface SendMessagesOptions {
type: SendMessagesOptionsType;
token: string;
}
/**
* Sends messages on behalf of the user to the chat screen where the LIFF app is opened.
* If the LIFF app is opened on a screen other than the chat screen, messages cannot be sent.
* @export
* @param {Array<{type: string, text: string, }>} messages
* @returns {Promise<void>}
*/
export default function sendMessages(messages: SendMessagesParams,
/** DEPRECATED. options to support old specs. */
options?: SendMessagesOptions): Promise<void>;
export {};
@@ -0,0 +1,95 @@
import { InitParams, ShareTargetPickerResult } from './def';
/**
* ShareTargetPicker
* (Singleton Class)
*/
export default class ShareTargetPicker {
private static instance;
private liffId;
private allowPostMessageOrigin;
private payloadToShareTargetPicker;
private ott;
private popupWindow;
private timeoutIDForHealthCheck;
private abortController;
private internalError;
private doesWaitForSubwindowResult;
private constructor();
/**
* return Singleton instance
*/
static getInstance(): ShareTargetPicker;
/**
* initialize
* @param params
*/
init(params: InitParams): Promise<ShareTargetPickerResult | void>;
private resetAllVariables;
/**
* reset instance
*/
private reset;
/**
* called whenever the process is finished
*/
private finalize;
/**
* create payload data in accordance with the relevant format
* So far, the format of `PayloadToShareTargetPicker` is same as `InitParams`
* @param params
*/
private buildPayloadToShareTargetPicker;
/**
* return string like "https://example.com" (not includes last `/`)
* @param url
*/
private initAllowPostMessageOrigin;
private initOtt;
/**
* get a reference to the new window
*/
private prepareAnotherWindow;
/**
* emit corresponding events
*/
private openAnotherWindow;
/**
* start to listen corresponding events
*/
private initListener;
/**
* this function will be called every second
*/
private healthCheck;
/**
* called after receiving the event named "receivedHealthCheck" . this function is called except on LIFF
*/
private onReceivedHealthcheck;
/**
* post share result (only for external browser)
*/
private onCanceled;
/**
* Fetch Get Share Result
*/
private getShareResult;
/**
* check if elapsed max duration of polling
*
* @static
* @param {number} startTime
* @param {number} endTime
* @returns {boolean}
* @memberof ShareTargetPicker
*/
static isPollingTimeOut(startTime: number, endTime: number): boolean;
/**
* Polling server side API to get result from shareTargetPicker screen,
* which has an access to access.line.me, via server API.
*
* @private
* @returns {(Promise<ShareTargetPickerResult|void>)}
* @memberof ShareTargetPicker
*/
private pollingShareResult;
}
+10
View File
@@ -0,0 +1,10 @@
import { PayloadToShareTargetPicker } from './def';
/**
* open another window for shareTargetPicker
* @param liffId
*/
export declare function openAnotherWindow(popupWindow: Window, liffId: string, ott: string): void;
export declare function initListener(cb: Function, allowPostMessageOrigin: string): void;
export declare function healthCheck(popupWindow: Window, allowPostMessageOrigin: string): void;
export declare function finalize(timeoutIDForHealthCheck: number | null, popupWindow: Window | null): void;
export declare function onReceivedHealthcheck(popupWindow: Window, payloadToShareTargetPicker: PayloadToShareTargetPicker, allowPostMessageOrigin: string): void;
+8
View File
@@ -0,0 +1,8 @@
import { PayloadToShareTargetPicker } from './def';
/**
* call LINE Scheme to let client receive infos to open another window for shareTargetPicker
* @param liffId
* @param ott
* @param message
*/
export declare function openAnotherWindow(liffId: string, ott: string, data: PayloadToShareTargetPicker): void;
+58
View File
@@ -0,0 +1,58 @@
import { SendMessagesParams } from './../sendMessages';
import { TellEvent } from '../../util/dom';
export declare const CLOSE_SHARETARGETPICKER_FAIL = 0;
export declare const CLOSE_SHARETARGETPICKER_SUCCEED = 1;
/**
* the format to send data to another window
*/
export interface PayloadToShareTargetPicker {
messages: SendMessagesParams;
referrer: {
liffId: string;
url: string;
};
}
/**
* the format in common with each closed event called when lfw/sub window closed
*/
export interface ClosedBody {
status?: number;
res?: {
message?: string;
};
callbackId: string;
}
/**
* the event called when lfw is closed
*/
export interface ClosedWebview extends CustomEvent {
detail: ClosedBody;
}
/**
* the event called when popup window is closed
*/
export interface ClosedPopupWindow extends TellEvent {
data: {
name: string;
body: ClosedBody;
};
}
/**
* the format of parameters for init is same as the format of payload to send to another screen so far.
* but the way to use is different. So I defined 2 types
*/
export interface InitParams extends PayloadToShareTargetPicker {
options?: {
waitForSubwindowResult: boolean;
};
}
export interface RetrievedOtt {
ott: string;
}
export interface RetrievedShareResult {
result?: 'SUCCESS' | 'FAILURE' | 'CANCEL';
resultDescription: string;
}
export interface ShareTargetPickerResult {
status: 'success';
}
+10
View File
@@ -0,0 +1,10 @@
import { SendMessagesParams } from '../sendMessages';
import { ShareTargetPickerResult } from './def';
/**
* Displays the target picker and sends the message created by the developer to the selected target.
* This message appears to your group or friends as if you had sent it
* @export
* @param {string} routerMode
* @returns {Promise<string|null>}
*/
export default function shareTargetPicker(messages: SendMessagesParams): Promise<ShareTargetPickerResult | void>;
+10
View File
@@ -0,0 +1,10 @@
import { ROUTER_MODE } from '../util/consts';
/**
* open a user picker screen in new window or iframe
* @export
* @param {string} routerMode
* @returns {Promise<string|null>}
*/
export default function userPicker(options?: {
routerMode: ROUTER_MODE;
}): Promise<string | null>;
+1
View File
@@ -0,0 +1 @@
export default function checkExpired(): boolean;
+7
View File
@@ -0,0 +1,7 @@
/**
* createCodeChallenge for PKCE Login
* @export
* @param {string} code_verifier
* @return {string}
*/
export default function createCodeChallenge(code_verifier: string): string;
+6
View File
@@ -0,0 +1,6 @@
/**
* createCodeVerifier for PKCE login
* @export
* @return {string}
*/
export default function createCodeVerifier(): string;
+1
View File
@@ -0,0 +1 @@
export default function cryptoVerify(pub: any, encodedBuf: any, signatureBuf: any): Promise<boolean>;
+6
View File
@@ -0,0 +1,6 @@
/**
* Checks whether the user is logged in.
* @export
* @returns {boolean}
*/
export default function isLoggedIn(): boolean;
@@ -0,0 +1,6 @@
/**
* Detect whether user is logged in by accessToken in sessionStorage
* @export
* @returns {boolean}
*/
export default function isRedirectedFromLoginServer(code: string): boolean;
+7
View File
@@ -0,0 +1,7 @@
/**
* Performs the LINE Login process (web login) for the Web app.
* @export
*/
export default function login(loginConfig?: {
redirectUri?: string;
}): void;
+5
View File
@@ -0,0 +1,5 @@
/**
* Logs out.
* @export
*/
export default function logout(): void;
+21
View File
@@ -0,0 +1,21 @@
/**
* Give developer ability to add extra params to permanent link.
* Can be used for tracking parameters for example
* @param {string} paramsToAdd - Extra params to add to permanent link
*/
declare const setExtraQueryParam: (paramsToAdd: string) => void;
/**
* Create Permanent Link function.
* @returns {string} - Generated permanent link
*/
declare const createUrl: () => string;
/**
* Decode liff.state
* If valid, return decoded state (with any double slashes removed)
* Else LiffError is thrown
* @param {string} state - liff.state's value
* @throws {LiffError}
* @returns {string} - Decoded liff.state with any double slashes removed
*/
declare const decodeState: (state: string) => string;
export { decodeState, createUrl, setExtraQueryParam };
+16
View File
@@ -0,0 +1,16 @@
interface AccessToken {
access_token: string;
token_type: string;
refresh_token: string;
expires_in: number;
scope: string;
id_token?: string;
}
/**
* Request OAuth server to issue access_token
* API Reference https://developers.line.biz/en/reference/social-api/
* @export
* @returns {Promise<any>}
*/
export default function requestAccessToken(): Promise<AccessToken>;
export {};
+9
View File
@@ -0,0 +1,9 @@
interface CertKey {
kid: string;
alg?: string;
}
interface Certs {
keys: CertKey[];
}
declare function requestCerts(): Promise<Certs>;
export default requestCerts;
+14
View File
@@ -0,0 +1,14 @@
export declare const INVALID_ALG = "Invalid \"alg\" value in ID_TOKEN";
export declare const FAILED_CRYPTO = "Failed to use Crypto API to verify ID_TOKEN";
export declare const INVALID_KID = "Invalid \"kid\" value in ID_TOKEN";
export declare const INVALID_ISS = "Invalid \"iss\" value in ID_TOKEN";
export declare const INVALID_AUD = "Invalid \"aud\" value in ID_TOKEN";
export declare const INVALID_EXP = "Invalid \"exp\" value in ID_TOKEN";
export declare const INVALID_SIG = "Invalid signature in ID_TOKEN";
/**
* verify ID Token in JWT format
* @export
* @param {string} idToken - string
* @returns {Promise<Object>}
*/
export default function verifyIDToken(idToken: string, liffClientId: string): Promise<unknown>;
+3
View File
@@ -0,0 +1,3 @@
import { AddToHomeScreen } from './common';
import { LiffCore } from '../../liff';
export default function createAddToHomeScreen(liff: LiffCore): AddToHomeScreen;
+18
View File
@@ -0,0 +1,18 @@
import { LiffCore } from '../../liff';
export interface AddToHomeScreenParams {
name: string;
iconUrl: string;
state: string;
}
export declare type AddToHomeScreen = (params: AddToHomeScreenParams) => Promise<number>;
export declare const SCHEME_HEADER = "app/";
export declare const ADD_TO_HOME_SCREEN_RESULT: {
UNKNOWN: number;
};
/**
* verification()
* @export
* @param {AddToHomeScreenParams} params
* @returns {boolean}
*/
export declare function verification(params: AddToHomeScreenParams, liff: LiffCore): boolean;
+3
View File
@@ -0,0 +1,3 @@
import { AddToHomeScreen } from './common';
import { LiffCore } from '../../liff';
export default function createAddToHomeScreen(liff: LiffCore): AddToHomeScreen;
+9
View File
@@ -0,0 +1,9 @@
import { ClientCallback } from './types';
export declare const callbacks: {};
/**
* Add callback to LIFFEvent listener.
* @export
* @param {string} type
* @param {ClientCallback} callback
*/
export default function addListener(type: string, callback: ClientCallback): void;
+19
View File
@@ -0,0 +1,19 @@
/**
* call()
* Let js pass event and data to client
* and wait for callback
* @export
* @param {string} type - name of event
* @param {*} [params={}] - data object to pass to client
* @param {{ - options
* callbackId?: string - use randomly string by default, use `''` if client needs
* once?: boolean - will remove listener once callback is fired if is true
* }} [options={
* once: true,
* }]
* @returns {Promise<any>}
*/
export default function call(type: string, params?: any, options?: {
callbackId?: string;
once?: boolean;
}): Promise<any>;
+2
View File
@@ -0,0 +1,2 @@
import './customEventPolyFill';
export default function createEvent<T>(detail: T): CustomEvent;
@@ -0,0 +1,3 @@
/**
* https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
*/
+6
View File
@@ -0,0 +1,6 @@
/**
* Wrap window.dispatchEvent
* Expose `window.liff._dispatchEvent()` for client to interact with js
* @param {string} json
*/
export default function dispatch(json: string): void;
+6
View File
@@ -0,0 +1,6 @@
export { default as createEvent } from './createEvent';
export { default as addListener } from './addListener';
export { default as removeListener } from './removeListener';
export { default as dispatch } from './dispatch';
export { default as postMessage } from './postMessage';
export { default as call } from './call';
+8
View File
@@ -0,0 +1,8 @@
/**
* Wrapper of window._liff.postMessage
* @export
* @param {string} type - name of message to client
* @param {{}} [params={}] - object data sent to client
* @param {string} [callbackId=''] - callbackId to identify client callback
*/
export default function postMessage(type: string, params?: {}, callbackId?: string): void;
+7
View File
@@ -0,0 +1,7 @@
import { ClientCallback } from './types';
/**
* Remove callback from LIFFEvent listener.
* @param {string} type [description]
* @param {Function} callback [description]
*/
export default function removeListener(type: string, callback: ClientCallback): void;
+1
View File
@@ -0,0 +1 @@
export declare type ClientCallback = (e: CustomEvent) => void;
+7
View File
@@ -0,0 +1,7 @@
/**
* checkFeature()
* Check whether the permission of the feature is approved
* @param {string} feature [name of feature]
* @return {boolean}
*/
export default function checkFeature(feature: string): boolean;
+1
View File
@@ -0,0 +1 @@
export {};
+4
View File
@@ -0,0 +1,4 @@
/**
* Closes the LIFF app.
*/
export default function closeWindow(): void;
+2
View File
@@ -0,0 +1,2 @@
import { AIdInterface } from '../store';
export default function getAId(): AIdInterface | undefined;
+5
View File
@@ -0,0 +1,5 @@
import { LiffCore } from '../liff';
declare type GetAdvertisingIdResult = null | string;
export declare type GetAdvertisingId = () => Promise<GetAdvertisingIdResult>;
export default function createGetAdvertisingId(liff: LiffCore): GetAdvertisingId;
export {};
+1
View File
@@ -0,0 +1 @@
export default function getIsVideoAutoPlay(): boolean;
+11
View File
@@ -0,0 +1,11 @@
/**
* Gets the user's LINE version.
* @export
* @returns {string|null} LINE Version, if inside LINE client. Null, otherwise
* @example
* const lineVersion = liff.getLineVersion()
* if (!lineVersion || lineVersion === '9.1.0') {
* // do something
* }
*/
export default function getLineVersion(): string | null;
+2
View File
@@ -0,0 +1,2 @@
import { ProfilePlusInterface } from '../store';
export default function getProfilePlus(): ProfilePlusInterface | undefined;
+11
View File
@@ -0,0 +1,11 @@
export interface OpenWindowParams {
url: string;
external?: boolean;
}
/**
* Opens the specified URL in the in-app browser of LINE or external browser.
* @export
* @param {OpenWindowParams} params
* @returns {void}
*/
export default function openWindow(params: OpenWindowParams): void;
+2
View File
@@ -0,0 +1,2 @@
import { Context } from '../store';
export default function parseContext(contextStr: string): Context | null;
+7
View File
@@ -0,0 +1,7 @@
/**
* listen to `ready` event from client
* set client features in client callback
* @export
* @returns {Promise<any>}
*/
export default function ready(): Promise<void>;
+9
View File
@@ -0,0 +1,9 @@
import { LiffCore } from '../liff';
/**
* open scan qr code appview
*/
export interface ScanCodeResult {
value: string | null;
}
export declare type ScanCode = () => Promise<ScanCodeResult>;
export default function createScanCode(liff: LiffCore): ScanCode;
+5
View File
@@ -0,0 +1,5 @@
/**
* Gets the language settings of the environment in which the LIFF app is running.
* @export
*/
export default function getLanguage(): string;
+12
View File
@@ -0,0 +1,12 @@
declare type OS = 'ios' | 'android' | 'web' | undefined;
/**
* Gets the environment in which the user is running the LIFF app.
* @export
*/
export default function getOS(): OS;
/**
* Cleanup cached OS, testing use
* @export
*/
export declare function _cleanupCachedOS(): void;
export {};
+5
View File
@@ -0,0 +1,5 @@
/**
* Get LIFF SDK Version
* @return {string}
*/
export default function getVersion(): string;
+11
View File
@@ -0,0 +1,11 @@
/**
* Determines whether the LIFF app is running in LINE's in-app browser.
* @export
* @returns {boolean}
*/
export default function isInClient(): boolean;
/**
* Cleanup cached OS, testing use
* @export
*/
export declare function _cleanupCachedIsInClient(): void;
+5
View File
@@ -0,0 +1,5 @@
import 'whatwg-fetch';
import 'promise-polyfill/src/polyfill';
import liff from './liff';
export { liff };
export default liff;
+2
View File
File diff suppressed because one or more lines are too long
+18
View File
@@ -0,0 +1,18 @@
import { ScanCode } from '../../client/scanCode';
import { GetAdvertisingId } from '../../client/getAdvertisingId';
import { AddToHomeScreen } from '../../client/addToHomeScreen/common';
import { InitPlugins } from '../../plugin/initPlugins';
import { LiffCore } from '../../liff';
interface LiffExtendableAll {
addToHomeScreen: AddToHomeScreen;
scanCode: ScanCode;
getAdvertisingId: GetAdvertisingId;
initPlugins: InitPlugins;
}
export declare type ExtendableKeys = keyof LiffExtendableAll;
export declare type LiffExtendableFunctions = Partial<LiffExtendableAll>;
export interface LiffExtension {
install: (liff: LiffCore) => void;
}
export declare type ExtendedFunctionCreator = (liff: LiffCore) => Function;
export {};
@@ -0,0 +1,3 @@
import { LiffExtension, ExtendedFunctionCreator, ExtendableKeys } from './LiffExtension';
export declare type CreatorDefPair = [ExtendableKeys, ExtendedFunctionCreator];
export default function createExtension(creatorDefPairs: CreatorDefPair[]): LiffExtension;
+3
View File
@@ -0,0 +1,3 @@
import { LiffExtension } from './LiffExtension';
declare const ios9_18Extension: LiffExtension;
export default ios9_18Extension;
+3
View File
@@ -0,0 +1,3 @@
import { LiffExtension } from './LiffExtension';
declare const ios9_19Extension: LiffExtension;
export default ios9_19Extension;
+3
View File
@@ -0,0 +1,3 @@
import { LiffExtension } from './LiffExtension';
declare const othersExtension: LiffExtension;
export default othersExtension;
+1
View File
@@ -0,0 +1 @@
export default function fetchAndSetContext(): Promise<void>;
+1
View File
@@ -0,0 +1 @@
export default function handleLiffState(state: string): void;
@@ -0,0 +1,3 @@
export default function handleLoginAndInitFeatures(config: {
liffId: string;
}): Promise<void>;
@@ -0,0 +1 @@
export default function handleLoginExternalBrowser(): Promise<void>;
+2
View File
@@ -0,0 +1,2 @@
import LiffError from '../util/LiffError';
export default function handleOAuthError(errorType: string, errorDescription: string): LiffError;
@@ -0,0 +1 @@
export default function handleRedirectedFromLoginServer(liffClientId: string): Promise<void>;
+10
View File
@@ -0,0 +1,10 @@
import { LiffCore } from '../liff';
/**
* Initializes a LIFF app.
* You can only call other LIFF SDK methods after calling liff.init().
* The LIFF SDK gets access tokens and ID tokens from the LINE platform
* when you initialize the LIFF app.
*/
export default function init(this: LiffCore, config: {
liffId: string;
}, successCallback?: () => void, errorCallback?: (error: Error) => void): Promise<void>;
+9
View File
@@ -0,0 +1,9 @@
export declare let initDone: () => void;
/**
* A property holding the Promise object that resolves when you run liff.init() for the first time after starting the LIFF app.
*
* If you use liff.ready, you can execute any process after the completion of liff.init().
*
* liff.ready can be used before liff.init() finishes initializing the LIFF app.
*/
export declare const ready: Promise<void>;
+6
View File
@@ -0,0 +1,6 @@
export interface Config {
liffId: string;
redirectUri?: string;
}
export declare type SubsequentInit = (config: Config) => Promise<void>;
export default function subsequentInit(config: Config): Promise<void>;
+1
View File
@@ -0,0 +1 @@
export default function validateFeatureToken(liffId: any, featureToken: any): boolean;
@@ -0,0 +1 @@
(window.webpackJsonpliff=window.webpackJsonpliff||[]).push([[0],{120:function(n,f){},122:function(n,f){},154:function(n,f){},155:function(n,f){}}]);
+70
View File
@@ -0,0 +1,70 @@
import init from './init';
import getOS from './common/getOS';
import getVersion from './common/getVersion';
import getLanguage from './common/getLanguage';
import isInClient from './common/isInClient';
import isLoggedIn from './auth/isLoggedIn';
import login from './auth/login';
import logout from './auth/logout';
import { getAccessToken, getFeatures, getContext, getIDToken, getDecodedIDToken } from './store';
import { dispatch, call, postMessage, addListener, removeListener } from './client/bridge';
import checkFeature from './client/checkFeature';
import openWindow from './client/openWindow';
import closeWindow from './client/closeWindow';
import getAId from './client/getAId';
import getProfilePlus from './client/getProfilePlus';
import getIsVideoAutoPlay from './client/getIsVideoAutoPlay';
import getLineVersion from './client/getLineVersion';
import getProfile from './api/getProfile';
import sendMessages from './api/sendMessages';
import userPicker from './api/userPicker';
import shareTargetPicker from './api/shareTargetPicker';
import getFriendship from './api/getFriendship';
import { LiffExtendableFunctions } from './init/definition/LiffExtension';
declare const liffCore: {
init: typeof init;
getOS: typeof getOS;
getVersion: typeof getVersion;
getLanguage: typeof getLanguage;
isInClient: typeof isInClient;
isLoggedIn: typeof isLoggedIn;
login: typeof login;
logout: typeof logout;
getAccessToken: typeof getAccessToken;
getIDToken: typeof getIDToken;
getDecodedIDToken: typeof getDecodedIDToken;
getContext: typeof getContext;
openWindow: typeof openWindow;
closeWindow: typeof closeWindow;
getFeatures: typeof getFeatures;
getFriendship: typeof getFriendship;
checkFeature: typeof checkFeature;
getAId: typeof getAId;
getProfilePlus: typeof getProfilePlus;
getIsVideoAutoPlay: typeof getIsVideoAutoPlay;
getLineVersion: typeof getLineVersion;
isApiAvailable: (apiName: "shareTargetPicker") => boolean;
getProfile: typeof getProfile;
sendMessages: typeof sendMessages;
userPicker: typeof userPicker;
shareTargetPicker: typeof shareTargetPicker;
permanentLink: {
createUrl: () => string;
setExtraQueryParam: (paramsToAdd: string) => void;
};
ready: Promise<void>;
/**
* The property that holds the LIFF app ID (String type) passed to liff.init().
* The value of liff.id is null until you run liff.init().
*/
readonly id: string | null;
_dispatchEvent: typeof dispatch;
_call: typeof call;
_addListener: typeof addListener;
_removeListener: typeof removeListener;
_postMessage: typeof postMessage;
};
export declare type LiffCore = typeof liffCore;
declare type Liff = LiffCore & LiffExtendableFunctions;
declare const liff: Liff;
export default liff;

Some files were not shown because too many files have changed in this diff Show More