diff --git a/README.md b/README.md index a8aa672..33e46d9 100644 --- a/README.md +++ b/README.md @@ -3,33 +3,33 @@ [![npm version](https://img.shields.io/npm/v/@xmartlabs/react-native-line.svg?style=flat-square)](https://www.npmjs.com/package/@xmartlabs/react-native-line) [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) -Line SDK wrapper for React Native 🚀 +LINE SDK wrapper for React Native 🚀 - [LINE SDK v5 for iOS](https://developers.line.biz/en/reference/ios-sdk-swift/), wrapped with [Swift](https://developer.apple.com/swift/). - [LINE SDK v5 for Android](https://developers.line.biz/en/reference/android-sdk/), wrapped with [Kotlin](https://kotlinlang.org/). ## Requirements -- Android `minSdkVersion` needs to be at least version `24`. -- iOS `deploymentTarget` needs to be at least version `15.1`. -- [LINE developer account](https://developers.line.biz/console/) needs to be created and also a channel. +- Android `minSdkVersion` must be `24` or higher. +- iOS `deploymentTarget` must be `15.1` or higher. +- A [LINE developer account](https://developers.line.biz/console/) and a configured channel are required. > [!IMPORTANT] -> @xmartlabs/react-native-line v5 is now a TurboModule and **requires the new architecture to be enabled**. -> - If you want to use @xmartlabs/react-native-line v5, you will need to enable the new architecture in your app (see how to [enable the new architecture for apps](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md)). -> - If you cannot enable the new architecture yet, downgrade to @xmartlabs/react-native-line v4 for now. +> `@xmartlabs/react-native-line` v6 is a TurboModule and **requires the new architecture to be enabled**. +> - To use v6, enable the new architecture in your app (see [how to enable the new architecture](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md)). +> - If you cannot enable the new architecture yet, use `@xmartlabs/react-native-line` v4 instead. ## Installation ### With Expo -1. Install the JavaScript side with: +1. Install the package: ```bash npx expo install @xmartlabs/react-native-line ``` -2. Add the plugins `expo-build-properties` and `@xmartlabs/react-native-line` to your `app.json`: +2. Add the `expo-build-properties` and `@xmartlabs/react-native-line` plugins to your `app.json`: ```json "plugins": [ @@ -45,42 +45,25 @@ Line SDK wrapper for React Native 🚀 ] ``` -### With react-native-cli +### With React Native CLI -1. Install library: +1. Install the package: ```bash npm install @xmartlabs/react-native-line - - # --- or --- - + # or yarn add @xmartlabs/react-native-line ``` -2. Link iOS native code: +2. Install the iOS native dependencies: ```bash cd ios && pod install ``` -3. Change your `AppDelegate` to match the following: - - #### With Objective-C +3. Update your `AppDelegate` to forward URL callbacks to the LINE SDK: - ##### @xmartlabs/react-native-line v4 - - ```objectivec - #import "RNLine-Swift.h" - - ... - - - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options - { - return [LineLogin application:application open:url options:options]; - } - ``` - - ##### @xmartlabs/react-native-line v5 + #### Objective-C ```objectivec #import "react_native_line-Swift.h" @@ -93,21 +76,7 @@ Line SDK wrapper for React Native 🚀 } ``` - #### With Swift - - ##### @xmartlabs/react-native-line v4 - - ```swift - import RNLine - - ... - - override func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { - return LineLogin.application(application, open: url, options: options) - } - ``` - - ##### @xmartlabs/react-native-line v5 + #### Swift ```swift import react_native_line @@ -119,7 +88,7 @@ Line SDK wrapper for React Native 🚀 } ``` -4. Insert the following [snippet](https://developers.line.biz/en/docs/line-login-sdks/ios-sdk/swift/setting-up-project/#config-infoplist-file) in your `Info.plist` just before the last `` tag: +4. Add the following entries to your `Info.plist` just before the closing `` tag ([reference](https://developers.line.biz/en/docs/line-login-sdks/ios-sdk/swift/setting-up-project/#config-infoplist-file)): ```xml CFBundleURLTypes @@ -139,19 +108,19 @@ Line SDK wrapper for React Native 🚀 ``` -## Migration guide +## Migration Guide -You can find the migration guide in the [linked document](./docs/MIGRATION_GUIDE.md). +See the [Migration Guide](./docs/MIGRATION_GUIDE.md) for upgrade instructions between versions. ## Usage -1. Import the `Line` module: +1. Import the module and any enums you need: ```typescript - import Line from '@xmartlabs/react-native-line' + import Line, { Scope, BotPrompt } from '@xmartlabs/react-native-line' ``` -2. Initialize the module with the `setup` method: +2. Initialize the SDK with the `setup` method. Call this once when your app starts: ```typescript useEffect(() => { @@ -162,29 +131,111 @@ You can find the migration guide in the [linked document](./docs/MIGRATION_GUIDE 3. Log in with the `login` method: ```typescript + // Default login — requests the profile scope only Line.login({}) + + // Explicit scopes and bot prompt + Line.login({ + scopes: [Scope.Profile, Scope.OpenId], + botPrompt: BotPrompt.Normal, + }) ``` ## API -| Method | Description | -| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `login(params: LoginParams): Promise` | Starts the login flow of Line's SDK (Opens the apps if it's installed and defaults to the browser otherwise). It accepts the same argumements as the LineSDK, in an object `{ key: value }`, defaults the same way as LineSDK too. | -| `getCurrentAccessToken(): Promise` | Returns the access token of the current user. | -| `getProfile(): Promise` | Returns the current user profile information. | -| `logout(): Promise` | Revokes the access token of the current user. | -| `refreshAccessToken(): Promise` | Refreshes the access token of the current user. | -| `setup(params: SetupParams): Promise` | Initializes the Line SDK. | -| `verifyAccessToken(): Promise` | Checks whether the access token of the current user is valid. | -| `getFriendshipStatus(): Promise` | Gets the friendship status between the LINE Official Account (which is linked to the current channel) and the user if [configured](https://developers.line.biz/en/docs/line-login-sdks/ios-sdk/swift/link-a-bot/). | +### Methods + +| Method | Description | +| --- | --- | +| `setup(params: SetupParams): Promise` | Initializes the LINE SDK. Must be called before any other method. | +| `login(params: LoginParams): Promise` | Starts the LINE login flow. Opens the LINE app if installed, otherwise falls back to the browser. | +| `logout(): Promise` | Revokes the current user's access token and clears the local session. | +| `getCurrentAccessToken(): Promise` | Returns the locally cached access token for the current user, without a network call. | +| `refreshAccessToken(): Promise` | Exchanges the current access token for a fresh one. | +| `verifyAccessToken(): Promise` | Validates the current access token against the LINE server and returns its metadata. | +| `getProfile(): Promise` | Returns the current user's LINE profile. Requires `Scope.Profile`. | +| `getFriendshipStatus(): Promise` | Returns whether the current user has added the channel's linked LINE Official Account as a friend. Requires [bot linkage](https://developers.line.biz/en/docs/line-login/link-a-bot/) to be configured. | + +### Types + +#### `SetupParams` + +| Field | Type | Description | +| --- | --- | --- | +| `channelId` | `string` | Your LINE Login channel ID. | +| `universalLinkUrl` | `string?` | Universal link URL registered for your channel. iOS only. | + +#### `LoginParams` + +| Field | Type | Default | Description | +| --- | --- | --- | --- | +| `scopes` | `Scope[]` | `[Scope.Profile]` | OAuth scopes to request. | +| `onlyWebLogin` | `boolean` | `false` | Skip the LINE app and use the browser-based login flow. | +| `botPrompt` | `BotPrompt` | `BotPrompt.Normal` | Controls the bot friend-add prompt shown during login. | + +#### `Scope` (enum) + +| Value | String | Description | +| --- | --- | --- | +| `Scope.Profile` | `'profile'` | Access to the user's display name, picture URL, status message, and user ID. | +| `Scope.OpenId` | `'openid'` | Issues an OpenID Connect ID token. Required to receive `idToken`. | +| `Scope.Email` | `'email'` | Access to the user's email address. Requires channel approval from LINE. | + +#### `BotPrompt` (enum) + +| Value | Description | +| --- | --- | +| `BotPrompt.Normal` | Adds an inline "Add friend" option in the consent screen. | +| `BotPrompt.Aggressive` | Shows a standalone friend-add screen after the consent screen. | + +#### `AccessToken` + +| Field | Type | Description | +| --- | --- | --- | +| `accessToken` | `string` | Bearer token used to authorize LINE API calls. | +| `expiresIn` | `number` | Seconds until the token expires (OAuth standard `expires_in`). | +| `idToken` | `string?` | Raw OpenID Connect ID token. Present only when `Scope.OpenId` was requested. | + +#### `LoginResult` + +| Field | Type | Description | +| --- | --- | --- | +| `accessToken` | `AccessToken` | The access token issued for this login session. | +| `scope` | `string` | Space-separated list of granted scopes (e.g. `"profile openid"`). | +| `userProfile` | `UserProfile?` | The user's profile. Present only when `Scope.Profile` was requested. | +| `friendshipStatusChanged` | `boolean?` | Whether the user's friendship status with the linked LINE Official Account changed during this login. | +| `idTokenNonce` | `string?` | Nonce for ID token verification. Present only when `Scope.OpenId` was requested. | + +#### `UserProfile` + +| Field | Type | Description | +| --- | --- | --- | +| `userId` | `string` | The user's LINE user ID. Stable across logins for the same channel. | +| `displayName` | `string` | The user's LINE display name. | +| `pictureUrl` | `string?` | URL of the user's profile picture. | +| `statusMessage` | `string?` | The user's LINE status message. | + +#### `FriendshipStatus` + +| Field | Type | Description | +| --- | --- | --- | +| `friendFlag` | `boolean` | `true` if the user has added the linked LINE Official Account as a friend. | + +#### `VerifyResult` + +| Field | Type | Description | +| --- | --- | --- | +| `clientId` | `string` | The channel ID the access token was issued for. | +| `expiresIn` | `number` | Seconds until the token expires. | +| `scope` | `string` | Space-separated list of scopes granted to the token. | ## Example -If you want to see `@xmartlabs/react-native-line` in action, just move into the [example](/example) folder and run `npm install` and then `npm run ios`/`npm run android`. By seeing its source code, you will have a better understanding of the library usage. +Check the [example app](/example) for a complete working implementation. To run it locally, navigate to the [example](/example) folder and run `npm install`, then `npm run ios` or `npm run android`. ## License -`@xmartlabs/react-native-line` is available under the MIT license. See the LICENCE file for more info. +`@xmartlabs/react-native-line` is available under the MIT license. See the [LICENSE](./LICENSE) file for details.

Xmartlabs Logo diff --git a/android/src/main/java/com/xmartlabs/line/LineLoginModule.kt b/android/src/main/java/com/xmartlabs/line/LineLoginModule.kt index e5bb54c..33237a1 100755 --- a/android/src/main/java/com/xmartlabs/line/LineLoginModule.kt +++ b/android/src/main/java/com/xmartlabs/line/LineLoginModule.kt @@ -286,7 +286,7 @@ class LineLoginModule(reactContext: ReactApplicationContext) : private fun buildVerifyResult(credential: LineCredential): WritableMap = Arguments.makeNativeMap( mapOf( - "clientId" to channelId, + "channelId" to channelId, "expiresIn" to credential.accessToken.expiresInMillis / 1000L, "scope" to Scope.join(credential.scopes), ) diff --git a/docs/MIGRATION_GUIDE.md b/docs/MIGRATION_GUIDE.md index 0e5c4f8..5d9bc23 100644 --- a/docs/MIGRATION_GUIDE.md +++ b/docs/MIGRATION_GUIDE.md @@ -1,50 +1,103 @@ -# Migration guides +# Migration Guide ## v3 → v4 -1. A `setup` function has been added and needs to be called before using the library. - ```typescript - Line.setup({ channelId: 'YOUR_CHANNEL_ID' }) - ``` - -2. The `getBotFriendshipStatus` function is now called `getFriendshipStatus`. - -3. The `refreshToken` function is now called `refreshAccessToken`. - -4. Remove the function `application` from `AppDelegate`: - #### With Swift - ```swift - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - LineLogin.setup(channelID: "YOUR_CHANNEL_ID", universalLinkURL: nil) - return true - } - ``` - - #### With Objective-C - ```objectivec - - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions - { - [LineLogin setupWithChannelID:@"YOUR_CHANNEL_ID" universalLinkURL:nil]; - } - ``` - -5. Remove the string `line_channel_id` from Android resources: - ```xml - YOUR_CHANNEL_ID - ``` +1. **`setup()` is now required** + + - SDK initialization has moved from native code to JavaScript. Add a `setup()` call when your app starts: + + ```typescript + useEffect(() => { + Line.setup({ channelId: 'YOUR_CHANNEL_ID' }) + }, []) + ``` + + Then remove the native setup code: + + - **iOS** — remove the setup call from `AppDelegate`: + + ```swift + // Swift — remove these lines + func application(_ application: UIApplication, didFinishLaunchingWithOptions ...) -> Bool { + LineLogin.setup(channelID: "YOUR_CHANNEL_ID", universalLinkURL: nil) + ... + } + ``` + + ```objectivec + // Objective-C — remove these lines + - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [LineLogin setupWithChannelID:@"YOUR_CHANNEL_ID" universalLinkURL:nil]; + ... + } + ``` + + - **Android** — remove the channel ID string from your resources: + + ```xml + + YOUR_CHANNEL_ID + ``` + +2. **Renamed methods** + + | Before | After | + | --- | --- | + | `getBotFriendshipStatus()` | `getFriendshipStatus()` | + | `refreshToken()` | `refreshAccessToken()` | + +--- ## v4 → v5 -1. The file name in the `AppDelegate` import has changed. - ```objectivec - - #import "RNLine-Swift.h" +> [!IMPORTANT] +> v5 is a TurboModule and **requires the new architecture**. Enable it in your app before upgrading — see [how to enable the new architecture](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md). +> If you cannot enable the new architecture yet, stay on v4. + +1. **iOS header rename** + + Update the import in your `AppDelegate`: + + ```objectivec + - #import "RNLine-Swift.h" + + #import "react_native_line-Swift.h" + ``` + +2. **`login()` requires an argument** + + Pass an empty object when using default options: + + ```typescript + - Line.login() + + Line.login({}) + ``` + +--- + +## v5 → v6 + +> [!NOTE] +> v6 continues to require the new architecture. No changes needed if you already enabled it for v5. + +1. **`LoginPermission` renamed to `Scope`** + + The `LoginPermission` enum has been renamed to `Scope` to align with OAuth 2.0 terminology. + + ```typescript + - import { LoginPermission } from '@xmartlabs/react-native-line' + + import { Scope } from '@xmartlabs/react-native-line' + + - Line.login({ scopes: [LoginPermission.Profile, LoginPermission.OpenId] }) + + Line.login({ scopes: [Scope.Profile, Scope.OpenId] }) + ``` + + The underlying string values are unchanged (`'profile'`, `'openid'`, `'email'`), so any code that passes raw strings is unaffected. - + #import "react_native_line-Swift.h" - ``` +2. **`expiresIn` is now a `number`** -2. The `login` function now expects an empty object as a default value. - ```typescript - - Line.login() + `AccessToken.expiresIn` and `VerifyResult.expiresIn` are now typed as `number` (seconds until expiry) instead of `string`. - + Line.login({}) - ``` + ```typescript + - const secondsLeft = parseInt(token.expiresIn, 10) + + const secondsLeft = token.expiresIn + ``` diff --git a/example/app/(app)/index.tsx b/example/app/(app)/index.tsx index 71649ff..f0126bc 100644 --- a/example/app/(app)/index.tsx +++ b/example/app/(app)/index.tsx @@ -73,7 +73,9 @@ export default function HomeScreen() { function verifyAccessToken() { setLoading(true) return Line.verifyAccessToken() - .then(result => Alert.alert(result.clientId, result.expiresIn.toString())) + .then(result => { + Alert.alert(result.channelId, result.expiresIn.toString()) + }) .catch(handleError) .finally(() => setLoading(false)) } diff --git a/ios/LineLoginModule.swift b/ios/LineLoginModule.swift index af3c43f..c24259e 100644 --- a/ios/LineLoginModule.swift +++ b/ios/LineLoginModule.swift @@ -2,68 +2,82 @@ import Foundation import LineSDK @objc(LineLogin) public class LineLogin: NSObject { - - @objc public static func application( + + private let loginLock = NSLock() + private var isLoginInProgress = false + + @MainActor @objc public static func application( _ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { return LoginManager.shared.application(application, open: url, options: options) } - - @objc public static func application( + + @MainActor @objc public static func application( _ application: UIApplication, continue userActivity: NSUserActivity, - restorationHandler: @escaping ([Any]) -> Void) -> Bool + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { return LoginManager.shared.application(application, open: userActivity.webpageURL) } - + @objc func setup(_ arguments: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - - guard !LoginManager.shared.isSetupFinished else { - reject("SETUP_ALREADY_COMPLETED", "Setup is already completed", nil) + guard let channelID = arguments["channelId"] as? String, !channelID.isEmpty else { + reject("INVALID_ARGUMENTS", "channelId must be a non-empty string", nil) return } - - guard let channelID = arguments["channelId"] as? String else { - reject("INVALID_ARGUMENTS", "Missing required argument: channelId", nil) + + guard !LoginManager.shared.isSetupFinished else { + resolve(nil) return } - + let universalLinkURL: URL? = (arguments["universalLinkUrl"] as? String).flatMap { URL(string: $0) } LoginManager.shared.setup(channelID: channelID, universalLinkURL: universalLinkURL) resolve(nil) } - - @objc func login(_ arguments: NSDictionary?, resolver resolve: @escaping RCTPromiseResolveBlock, + + @objc func login(_ arguments: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - - guard let args = arguments else { - reject("INVALID_ARGUMENTS", "Expected argument is nil", NSError(domain: "", code: 200, userInfo: nil)) + loginLock.lock() + guard !isLoginInProgress else { + loginLock.unlock() + reject("LOGIN_IN_PROGRESS", "A login is already in progress", nil) return } - - let scopes = (args["scopes"] as? [String])?.map { LoginPermission(rawValue: $0) } ?? [.profile] - let onlyWebLogin = (args["onlyWebLogin"] as? Bool) ?? false - var parameters = LoginManager.Parameters.init() - + isLoginInProgress = true + loginLock.unlock() + + let scopes = (arguments["scopes"] as? [String])?.map { LoginPermission(rawValue: $0) } ?? [.profile] + let onlyWebLogin = (arguments["onlyWebLogin"] as? Bool) ?? false + var parameters = LoginManager.Parameters() + if onlyWebLogin { parameters.onlyWebLogin = onlyWebLogin } - - if let botPrompt = args["botPrompt"] as? String { - switch botPrompt { + + if let botPromptRaw = arguments["botPrompt"] as? String { + switch botPromptRaw { case "aggressive": parameters.botPromptStyle = LoginManager.BotPrompt(rawValue: "aggressive") - case "normal": parameters.botPromptStyle = LoginManager.BotPrompt(rawValue: "normal") - default: break + case "normal": parameters.botPromptStyle = LoginManager.BotPrompt(rawValue: "normal") + default: + loginLock.lock() + isLoginInProgress = false + loginLock.unlock() + reject("INVALID_ARGUMENT", "Invalid botPrompt '\(botPromptRaw)'. Expected: 'normal' or 'aggressive'", nil) + return } } - + DispatchQueue.main.async { LoginManager.shared.login( permissions: Set(scopes), in: nil, - parameters: parameters) { result in + parameters: parameters) { [weak self] result in + guard let self else { return } + self.loginLock.lock() + self.isLoginInProgress = false + self.loginLock.unlock() switch result { case .success(let value): resolve(self.parseLoginResult(value)) case .failure(let error): error.rejecter(reject) @@ -71,7 +85,7 @@ import LineSDK } } } - + @objc func logout(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { LoginManager.shared.logout { result in @@ -81,7 +95,7 @@ import LineSDK } } } - + @objc func getCurrentAccessToken(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { guard let token = AccessTokenStore.shared.current else { @@ -90,7 +104,7 @@ import LineSDK } resolve(self.parseAccessToken(token)) } - + @objc func getFriendshipStatus(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { API.getBotFriendshipStatus { result in @@ -100,7 +114,7 @@ import LineSDK } } } - + @objc func getProfile(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { API.getProfile { result in @@ -110,7 +124,7 @@ import LineSDK } } } - + @objc func refreshAccessToken(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { API.Auth.refreshAccessToken { result in @@ -120,7 +134,7 @@ import LineSDK } } } - + @objc func verifyAccessToken(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { API.Auth.verifyAccessToken { result in @@ -130,51 +144,60 @@ import LineSDK } } } - + private func parseFriendshipStatus(_ status: GetBotFriendshipStatusRequest.Response) -> NSDictionary { return [ "friendFlag": status.friendFlag ] } - + private func parseAccessToken(_ accessToken: AccessToken) -> NSDictionary { var result: [String: Any] = [ "accessToken": accessToken.value, - "createdAt": accessToken.createdAt, - "expiresIn": accessToken.expiresAt, + "expiresIn": Int(accessToken.expiresAt.timeIntervalSinceNow), ] - if let idToken = accessToken.IDTokenRaw { result["idToken"] = idToken } - return NSDictionary(dictionary: result) } - + private func parseProfile(_ profile: UserProfile) -> NSDictionary { - return [ + var result: [String: Any] = [ "displayName": profile.displayName, - "pictureUrl": profile.pictureURL?.absoluteString, - "statusMessage": profile.statusMessage, - "userId": profile.userID + "userId": profile.userID, ] + if let url = profile.pictureURL { + result["pictureUrl"] = url.absoluteString + } + if let message = profile.statusMessage { + result["statusMessage"] = message + } + return NSDictionary(dictionary: result) } - + private func parseLoginResult(_ loginResult: LoginResult) -> NSDictionary { - return [ + var result: [String: Any] = [ "accessToken": self.parseAccessToken(loginResult.accessToken), - "friendshipStatusChanged": loginResult.friendshipStatusChanged, - "idTokenNonce": loginResult.IDTokenNonce, - "scope": loginResult.permissions.map { $0.rawValue }.joined(separator: " "), - "userProfile": loginResult.userProfile.map(self.parseProfile) + "scope": loginResult.permissions.map { $0.rawValue }.joined(separator: " "), ] + if let changed = loginResult.friendshipStatusChanged { + result["friendshipStatusChanged"] = changed + } + if let nonce = loginResult.IDTokenNonce { + result["idTokenNonce"] = nonce + } + if let profile = loginResult.userProfile { + result["userProfile"] = self.parseProfile(profile) + } + return NSDictionary(dictionary: result) } - + private func parseVerifyAccessToken(_ accessToken: AccessTokenVerifyResult) -> NSDictionary { return [ - "channelId": accessToken.channelID, + "channelId": accessToken.channelID, "expiresIn": accessToken.expiresIn, - "scope": accessToken.permissions.map { $0.rawValue }.joined(separator: " ") + "scope": accessToken.permissions.map { $0.rawValue }.joined(separator: " ") ] } } diff --git a/react-native-line.podspec b/react-native-line.podspec index 60d0afc..a7b98c7 100644 --- a/react-native-line.podspec +++ b/react-native-line.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm,swift}" - s.dependency 'LineSDKSwift', '~> 5.12.0' + s.dependency 'LineSDKSwift', '~> 5.14.0' install_modules_dependencies(s) end diff --git a/src/NativeLineLogin.ts b/src/NativeLineLogin.ts index 1ba68cb..59d0966 100644 --- a/src/NativeLineLogin.ts +++ b/src/NativeLineLogin.ts @@ -121,7 +121,7 @@ export interface LoginResult { export interface VerifyResult { /** The LINE Login channel ID the access token was issued for. */ - readonly clientId: string + readonly channelId: string /** Seconds until the access token expires. */ readonly expiresIn: number /** Space-separated list of scopes granted to this access token. */