feat(react-native-test-app-msal): MSAL module for react-native-test-app (#730)

This commit is contained in:
Tommy Nguyen 2021-11-03 16:55:47 +01:00 коммит произвёл GitHub
Родитель 3bbcd777b4
Коммит ef7b6705d3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
17 изменённых файлов: 877 добавлений и 4 удалений

Просмотреть файл

@ -135,6 +135,7 @@ individually, as features are added and fixes are made.
| [@rnx-kit/metro-serializer-esbuild](https://github.com/microsoft/rnx-kit/tree/main/packages/metro-serializer-esbuild) | Experimental esbuild serializer for Metro |
| [@rnx-kit/metro-service](https://github.com/microsoft/rnx-kit/tree/main/packages/metro-service) | Metro service for bundling and bundle-serving |
| [@rnx-kit/metro-swc-worker](https://github.com/microsoft/rnx-kit/tree/main/packages/metro-swc-worker) | Metro transform worker that uses swc under the hood |
| [@rnx-kit/react-native-test-app-msal](https://github.com/microsoft/rnx-kit/tree/main/packages/react-native-test-app-msal) | Microsoft Authentication Library (MSAL) module for react-native-test-app |
| [@rnx-kit/third-party-notices](https://github.com/microsoft/rnx-kit/tree/main/packages/third-party-notices) | Library and tool to build a third party notices file based on a js bundle's source map |
| [@rnx-kit/tools-language](https://github.com/microsoft/rnx-kit/tree/main/packages/tools-language) | A collection of supplemental JavaScript functions and types |
| [@rnx-kit/tools-node](https://github.com/microsoft/rnx-kit/tree/main/packages/tools-node) | A collection of supplemental NodeJS functions and types |

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "MSAL module for react-native-test-app",
"packageName": "@rnx-kit/react-native-test-app-msal",
"email": "4123478+tido64@users.noreply.github.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -0,0 +1,4 @@
disabled_rules:
- function_body_length
- opening_brace # Conflicts with SwiftFormat
- trailing_comma

Просмотреть файл

@ -0,0 +1,98 @@
# @rnx-kit/react-native-test-app-msal
[Microsoft Authentication Library](http://aka.ms/aadv2) (MSAL) module for
[React Native Test App](https://github.com/microsoft/react-native-test-app#readme).
## Install
Add `@rnx-kit/react-native-test-app-msal` as a dev dependency:
```
yarn add @rnx-kit/react-native-test-app-msal --dev
```
or if you're using `npm`:
```
npm add --save-dev @rnx-kit/react-native-test-app-msal
```
### iOS/macOS
We need to set the deployment target for iOS and macOS to 14.0 and 11.0
respectively, and add `MSAL` to `Podfile`:
```diff
+platform :ios, '14.0' # If targeting iOS, discard the line below
+platform :macos, '11.0' # If targeting macOS, discard the line above
+
require_relative '../node_modules/react-native-test-app/test_app'
workspace 'MyTestApp.xcworkspace'
+use_test_app! do |target|
+ target.app do
+ # We must use modular headers here otherwise Swift compiler will fail
+ pod 'MSAL', :modular_headers => true
+ end
+end
```
## Usage
Add an entry for the account switcher in your `app.json`, e.g.:
```diff
{
"name": "MyTestApp",
"displayName": "MyTestApp",
"components": [
{
"appKey": "MyTestApp",
+ },
+ {
+ "appKey": "MicrosoftAccounts"
}
],
"resources": {
"android": ["dist/res", "dist/main.android.bundle"],
"ios": ["dist/assets", "dist/main.ios.jsbundle"],
"macos": ["dist/assets", "dist/main.macos.jsbundle"],
"windows": ["dist/assets", "dist/main.windows.bundle"]
}
}
```
Register your app with a unique bundle identifier to get your Azure Active
Directory client identifier and related scopes
([quickstart here](https://docs.microsoft.com/en-gb/azure/active-directory/develop/quickstart-v2-ios#register-and-download-your-quickstart-app)),
then fill out the following fields in `app.json`:
```diff
{
"name": "MyTestApp",
"displayName": "MyTestApp",
"components": [
{
"appKey": "MyTestApp",
},
{
"appKey": "MicrosoftAccounts"
}
],
+ "ios": {
+ "bundleIdentifier": "com.contoso.MyTestApp"
+ },
+ "react-native-test-app-msal": {
+ "clientId": "00000000-0000-0000-0000-000000000000",
+ "msaScopes": ["user.read"],
+ "orgScopes": ["<Application ID URL>/scope"]
+ },
"resources": {
"android": ["dist/res", "dist/main.android.jsbundle"],
"ios": ["dist/assets", "dist/main.ios.jsbundle"],
"macos": ["dist/assets", "dist/main.macos.jsbundle"],
"windows": ["dist/assets", "dist/main.windows.bundle"]
}
}
```

Просмотреть файл

@ -0,0 +1,23 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
version = package['version']
Pod::Spec.new do |s|
s.name = 'ReactTestApp-MSAL'
s.version = version
s.author = { package['author']['name'] => package['author']['email'] }
s.license = package['license']
s.homepage = package['homepage']
s.source = { :git => package['repository']['url'], :tag => "#{package['name']}_v#{version}" }
s.summary = package['description']
s.ios.deployment_target = '14.0'
s.osx.deployment_target = '11.0'
s.dependency 'MSAL'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.source_files = 'ios/*.swift'
end

Просмотреть файл

@ -0,0 +1,71 @@
@objc
public enum AccountType: Int, CaseIterable {
case microsoftAccount
case organizational
}
@objc
public final class Account: NSObject, Identifiable {
// swiftlint:disable:next identifier_name
public var id: String {
"\(accountType.description):\(userPrincipalName)"
}
let userPrincipalName: String
let accountType: AccountType
init(userPrincipalName: String, accountType: AccountType) {
self.userPrincipalName = userPrincipalName
self.accountType = accountType
}
}
extension AccountType {
static func from(issuer: String) -> AccountType {
issuer.contains(TokenBroker.Constants.MicrosoftAccountTenant)
? .microsoftAccount
: .organizational
}
static func from(string: String) -> AccountType {
allCases.first { $0.description == string } ?? .organizational
}
var authority: URL! {
switch self {
case .microsoftAccount:
return URL(string: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize")
case .organizational:
return URL(string: "https://login.microsoftonline.com/common/")
}
}
var description: String {
switch self {
case .microsoftAccount:
return "personal"
case .organizational:
return "work"
}
}
}
extension Array where Element: Account {
func find(userPrincipalName: String, accountType: AccountType) -> Account? {
first {
$0.accountType == accountType && $0.userPrincipalName == userPrincipalName
}
}
func find(string: String) -> Account? {
let components = string.split(
separator: ":",
maxSplits: 1,
omittingEmptySubsequences: false
)
return find(
userPrincipalName: String(components[1]),
accountType: AccountType.from(string: String(components[0]))
)
}
}

Просмотреть файл

@ -0,0 +1,65 @@
import Foundation
import SwiftUI
// "Forward-declare" RCTBridge to avoid dependency on React-Core
typealias RCTBridge = AnyObject
#if os(iOS)
public typealias RTAViewController = UIViewController
typealias RTAHostingController = UIHostingController
#else
public typealias RTAViewController = NSViewController
typealias RTAHostingController = NSHostingController
#endif
final class ObservableHostingController: ObservableObject {
// swiftlint:disable:next identifier_name
@Published var id: Int = 0
let hostingController: RTAViewController
init(_ hostingController: RTAViewController) {
self.hostingController = hostingController
}
}
@objc(MicrosoftAccounts)
public final class AccountsHostingController: RTAViewController {
@objc
init(bridge _: RCTBridge) {
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
dynamic required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#if os(macOS)
let contentView = NSStackView()
override public func loadView() {
contentView.orientation = .vertical
contentView.edgeInsets = NSEdgeInsets(top: 24, left: 24, bottom: 24, right: 24)
view = contentView
}
#endif
override public func viewDidLoad() {
super.viewDidLoad()
let rootView = AccountsView().environmentObject(ObservableHostingController(self))
let hostingController = RTAHostingController(rootView: rootView)
addChild(hostingController)
#if os(iOS)
hostingController.view.autoresizingMask = view.autoresizingMask
hostingController.view.frame = view.frame
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
#else
contentView.addView(hostingController.view, in: .leading)
#endif
}
}

Просмотреть файл

@ -0,0 +1,194 @@
import SwiftUI
struct AccountsView: View {
private lazy var config = Config.load()
@EnvironmentObject private var hostingController: ObservableHostingController
@State private var accounts: [Account]
@State private var didLoad = false
@State private var formDisabled = false
@State private var selectAccountType = false
@State private var selectedAccount: Account? {
didSet {
onAccountChanged(selectedAccount)
}
}
init(accounts: [Account] = TokenBroker.shared.allAccounts()) {
self.accounts = accounts
}
var body: some View {
Form {
Section {
Picker("Account:", selection: $selectedAccount) {
ForEach(accounts) { account in
#if os(iOS)
VStack(alignment: .leading) {
Text(account.userPrincipalName)
.lineLimit(1)
.truncationMode(.middle)
Text("Account type: \(account.accountType.description)")
.font(.subheadline)
.foregroundColor(Color.secondary)
.lineLimit(1)
.truncationMode(.middle)
}
.tag(account as Account?)
#else
Text("\(account.userPrincipalName) (\(account.accountType.description))")
.lineLimit(1)
.truncationMode(.middle)
.tag(account as Account?)
#endif
}
.truncationMode(.middle)
}
.onChange(of: selectedAccount) {
// `didSet` is not called when selection is changed
onAccountChanged($0)
}
.disabled(accounts.isEmpty)
#if os(iOS)
Button("Add Account…") { selectAccountType = true }
.actionSheet(isPresented: $selectAccountType) {
ActionSheet(title: Text("Select account type"), buttons: [
.default(Text("Personal")) {
var mutableSelf = self
mutableSelf.onAddAccount(accountType: .microsoftAccount)
},
.default(Text("Work or School")) {
var mutableSelf = self
mutableSelf.onAddAccount(accountType: .organizational)
},
.cancel(),
])
}
#else
Button("Add Personal Account…") {
var mutableSelf = self
mutableSelf.onAddAccount(accountType: .microsoftAccount)
}
Button("Add Work or School Account…") {
var mutableSelf = self
mutableSelf.onAddAccount(accountType: .organizational)
}
#endif
}
if selectedAccount != nil {
Section {
#if os(iOS)
if #available(iOS 15.0, macOS 12.0, *) {
Button("Sign Out", role: .destructive) { onSignOut() }
} else {
Button("Sign Out") { onSignOut() }
.foregroundColor(Color.red)
}
#else
Button("Sign Out") { onSignOut() }
.foregroundColor(Color.red)
#endif
}
}
if accounts.count > 1 {
Section {
#if os(iOS)
if #available(iOS 15.0, macOS 12.0, *) {
Button("Remove All Accounts", role: .destructive) {
onRemoveAllAccounts()
}
} else {
Button("Remove All Accounts") { onRemoveAllAccounts() }
.foregroundColor(Color.red)
}
#else
Button("Remove All Accounts") { onRemoveAllAccounts() }
.foregroundColor(Color.red)
#endif
}
}
}
.onAppear {
guard !didLoad else {
return
}
defer {
didLoad = true
}
guard let secret = SecretStore.get() else {
return
}
selectedAccount = accounts.find(string: secret)
}
.disabled(formDisabled)
}
private func onAccountChanged(_ account: Account?) {
guard didLoad else {
return
}
TokenBroker.shared.currentAccount = account
guard let accountId = account?.id else {
SecretStore.remove()
return
}
SecretStore.set(secret: accountId)
}
private mutating func onAddAccount(accountType: AccountType) {
formDisabled = true
let mutableSelf = self
TokenBroker.shared.acquireToken(
scopes: config.scopes(for: accountType),
userPrincipalName: nil,
accountType: accountType,
sender: hostingController.hostingController
) { userPrincipalName, _, _ in
let allAccounts = TokenBroker.shared.allAccounts()
mutableSelf.accounts = allAccounts
mutableSelf.selectedAccount = allAccounts.find(
userPrincipalName: userPrincipalName,
accountType: accountType
)
mutableSelf.formDisabled = false
}
}
private func onRemoveAllAccounts() {
formDisabled = true
TokenBroker.shared.removeAllAccounts(sender: hostingController.hostingController)
accounts = []
selectedAccount = nil
formDisabled = false
}
private func onSignOut() {
formDisabled = true
TokenBroker.shared.signOut(sender: hostingController.hostingController) { _, _ in
accounts = TokenBroker.shared.allAccounts()
selectedAccount = nil
formDisabled = false
}
}
}
struct AccountsView_Previews: PreviewProvider {
static var previews: some View {
AccountsView(accounts: [
Account(userPrincipalName: "arnold@contoso.com", accountType: .organizational),
Account(userPrincipalName: "arnold@contoso.com", accountType: .microsoftAccount),
])
}
}

Просмотреть файл

@ -0,0 +1,38 @@
import Foundation
struct Config: Decodable {
let clientId: String
let msaScopes: [String]?
let orgScopes: [String]?
static func load() -> Config {
guard let manifestURL = Bundle.main.url(forResource: "app", withExtension: "json"),
let data = try? Data(contentsOf: manifestURL, options: .uncached)
else {
fatalError("Failed to load 'app.json'")
}
guard let manifest = try? JSONDecoder().decode(Manifest.self, from: data) else {
fatalError("Failed to parse 'app.json'")
}
return manifest.msalConfig
}
public func scopes(for accountType: AccountType) -> [String] {
switch accountType {
case .microsoftAccount:
return msaScopes ?? []
case .organizational:
return orgScopes ?? []
}
}
}
struct Manifest: Decodable {
let msalConfig: Config
enum CodingKeys: String, CodingKey {
case msalConfig = "react-native-test-app-msal"
}
}

Просмотреть файл

@ -0,0 +1 @@
/* Dummy file so @react-native-community/cli recognizes this as an iOS package */

Просмотреть файл

@ -0,0 +1,58 @@
import Foundation
import Security
struct SecretStore {
static func get() -> String? {
var result: AnyObject?
SecItemCopyMatching(CFDictionary.query(for: nil, returnData: true), &result)
guard let data = result as? Data else {
return nil
}
return String(data: data, encoding: .utf8)
}
static func remove() {
SecItemDelete(CFDictionary.query())
}
static func set(secret: String) {
let data = secret.data(using: .utf8)
let status = SecItemAdd(CFDictionary.query(for: data), nil)
if status != 0 {
if status == errSecDuplicateItem {
let attributesToUpdate = [kSecValueData: data as Any] as CFDictionary
SecItemUpdate(CFDictionary.query(), attributesToUpdate)
} else {
if let message = SecCopyErrorMessageString(status, nil) as String? {
NSLog("Failed to set secret: \(message)")
} else {
NSLog("Failed to set secret (code \(status))")
}
}
}
}
}
extension CFDictionary {
static func query(for secret: Data? = nil, returnData: Bool = false) -> CFDictionary {
let service = "com.microsoft.ReactTestApp-MSAL"
let account = "account"
guard let secret = secret else {
return [
kSecAttrService: service,
kSecAttrAccount: account,
kSecClass: kSecClassGenericPassword,
kSecReturnData: returnData,
] as CFDictionary
}
return [
kSecAttrService: service,
kSecAttrAccount: account,
kSecClass: kSecClassGenericPassword,
kSecValueData: secret as Any,
] as CFDictionary
}
}

Просмотреть файл

@ -0,0 +1,266 @@
import Foundation
import MSAL
public typealias TokenAcquiredHandler = (_ accountUPN: String, _ accessToken: String?, _ error: String?) -> Void
@objc(TokenBroker)
public final class TokenBroker: NSObject {
enum Constants {
// Source: https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens
static let MicrosoftAccountTenant = "9188040d-6c67-4c5b-b112-36a304b66dad"
static let RedirectURI = "msauth.\(Bundle.main.bundleIdentifier ?? "")://auth"
}
@objc(sharedBroker)
public static let shared = TokenBroker()
@objc
public var currentAccount: Account?
private let condition = NSCondition()
private let dispatchQueue = DispatchQueue(label: "com.microsoft.ReactTestApp-MSAL.TokenBroker")
private lazy var config = Config.load()
private var _publicClientApplication: MSALPublicClientApplication?
private var publicClientApplication: MSALPublicClientApplication? {
if _publicClientApplication == nil {
do {
_publicClientApplication = try MSALPublicClientApplication(
configuration: MSALPublicClientApplicationConfig(
clientId: config.clientId,
redirectUri: Constants.RedirectURI,
authority: nil
)
)
} catch {
NSLog("Failed to instantiate MSALPublicClientApplication: \(error.localizedDescription)")
}
}
return _publicClientApplication
}
@objc
public func acquireToken(
scopes: [String],
sender: RTAViewController,
onTokenAcquired: @escaping TokenAcquiredHandler
) {
guard let currentAccount = currentAccount else {
onTokenAcquired("", nil, "No current account")
return
}
acquireToken(
scopes: scopes,
userPrincipalName: currentAccount.userPrincipalName,
accountType: currentAccount.accountType,
sender: sender,
onTokenAcquired: onTokenAcquired
)
}
@objc
public func acquireToken(
scopes: [String],
userPrincipalName: String?,
accountType: AccountType,
sender: RTAViewController,
onTokenAcquired: @escaping TokenAcquiredHandler
) {
dispatchQueue.async {
self.acquireTokenSilent(
scopes: scopes,
userPrincipalName: userPrincipalName,
accountType: accountType,
sender: sender,
onTokenAcquired: onTokenAcquired
)
self.condition.wait()
}
}
@objc
public func allAccounts() -> [Account] {
var allAccounts: [Account] = []
if let accounts = try? publicClientApplication?.allAccounts() {
accounts.forEach {
guard let username = $0.username,
let issuer = $0.accountClaims?["iss"] as? String
else {
return
}
let accountType = AccountType.from(issuer: issuer)
let account = Account(userPrincipalName: username, accountType: accountType)
allAccounts.append(account)
}
}
return allAccounts
}
@objc
public func removeAllAccounts(sender: RTAViewController) {
defer {
currentAccount = nil
}
guard let application = publicClientApplication,
let accounts = try? application.allAccounts()
else {
return
}
let completion = { (_: Bool, _: Error?) in }
accounts.forEach {
signOut(account: $0, sender: sender, completion: completion)
}
}
@objc
public func signOut(
sender: RTAViewController,
completion: @escaping (_ success: Bool, _ error: Error?) -> Void
) {
defer {
currentAccount = nil
}
guard let username = currentAccount?.userPrincipalName,
let account = try? publicClientApplication?.account(forUsername: username)
else {
completion(true, nil)
return
}
signOut(account: account, sender: sender, completion: completion)
}
private func signOut(
account: MSALAccount,
sender: RTAViewController,
completion: @escaping (_ success: Bool, _ error: Error?) -> Void
) {
guard let application = publicClientApplication else {
completion(true, nil)
return
}
let webviewParameters = MSALWebviewParameters(authPresentationViewController: sender)
let signoutParameters = MSALSignoutParameters(webviewParameters: webviewParameters)
signoutParameters.wipeAccount = true
application.signout(with: account, signoutParameters: signoutParameters) { success, error in
if success {
try? application.remove(account)
}
completion(success, error)
}
}
private func acquireTokenInteractive(
scopes: [String],
userPrincipalName _: String?,
accountType: AccountType,
sender: RTAViewController,
onTokenAcquired: @escaping TokenAcquiredHandler
) {
guard let application = publicClientApplication else {
return
}
let parameters = MSALInteractiveTokenParameters(
scopes: scopes,
webviewParameters: MSALWebviewParameters(authPresentationViewController: sender)
)
parameters.authority = try? MSALAuthority(url: accountType.authority)
parameters.promptType = .selectAccount
DispatchQueue.main.async {
application.acquireToken(with: parameters) { result, error in
self.condition.signal()
let username = result?.account.username ?? ""
let accessToken = result?.accessToken
onTokenAcquired(username, accessToken, error?.localizedDescription)
}
}
}
private func acquireTokenSilent(
scopes: [String],
userPrincipalName: String?,
accountType: AccountType,
sender: RTAViewController,
onTokenAcquired: @escaping TokenAcquiredHandler
) {
guard let application = publicClientApplication else {
return
}
guard let userPrincipalName = userPrincipalName,
let cachedAccount = try? application.account(forUsername: userPrincipalName)
else {
acquireTokenInteractive(
scopes: scopes,
userPrincipalName: nil,
accountType: accountType,
sender: sender,
onTokenAcquired: onTokenAcquired
)
return
}
let parameters = MSALSilentTokenParameters(scopes: scopes, account: cachedAccount)
parameters.authority = try? MSALAuthority(url: accountType.authority)
DispatchQueue.main.async {
application.acquireTokenSilent(with: parameters) { result, error in
if let error = error as NSError? {
if error.domain == MSALErrorDomain, error.code == MSALError.interactionRequired.rawValue {
self.acquireTokenInteractive(
scopes: scopes,
userPrincipalName: userPrincipalName,
accountType: accountType,
sender: sender,
onTokenAcquired: onTokenAcquired
)
} else {
self.condition.signal()
// Handle "Cannot start ASWebAuthenticationSession
// without providing presentation context. Set
// presentationContextProvider before calling
// -start." This can occur while the test app is
// navigating to the initial component.
if error.isMissingPresentationContextError() {
self.acquireToken(
scopes: scopes,
userPrincipalName: userPrincipalName,
accountType: accountType,
sender: sender,
onTokenAcquired: onTokenAcquired
)
return
}
onTokenAcquired("", nil, error.localizedDescription)
}
return
}
self.condition.signal()
onTokenAcquired(userPrincipalName, result?.accessToken, nil)
}
}
}
}
extension NSError {
func isMissingPresentationContextError() -> Bool {
let domain = "com.apple.AuthenticationServices.WebAuthenticationSession"
return self.domain == domain && code == 2
}
}

Просмотреть файл

@ -0,0 +1,26 @@
{
"name": "@rnx-kit/react-native-test-app-msal",
"version": "0.0.1",
"description": "Microsoft Authentication Library (MSAL) module for react-native-test-app",
"homepage": "https://github.com/microsoft/rnx-kit/tree/main/packages/react-native-test-app-msal#readme",
"license": "MIT",
"author": {
"name": "Microsoft Open Source",
"email": "microsoftopensource@users.noreply.github.com"
},
"repository": {
"type": "git",
"url": "https://github.com/microsoft/rnx-kit.git",
"directory": "packages/react-native-test-app-msal"
},
"scripts": {
"build": "echo Build done.",
"format": "echo Format done.",
"format:swift": "swiftformat --swiftversion 5.5 ios",
"lint": "echo Lint done.",
"lint:swift": "swiftlint"
},
"peerDependencies": {
"react-native-test-app": ">=0.9.8"
}
}

Просмотреть файл

@ -5,6 +5,9 @@
{
"appKey": "SampleCrossApp",
"displayName": "SampleCrossApp"
},
{
"appKey": "MicrosoftAccounts"
}
],
"resources": {

Просмотреть файл

@ -1,6 +1,11 @@
platform :ios, '14.0'
require_relative '../../../node_modules/react-native-test-app/test_app'
workspace 'SampleCrossApp.xcworkspace'
use_flipper! false
use_test_app!
use_test_app! do |target|
target.app do
pod 'MSAL', :modular_headers => true
end
end

Просмотреть файл

@ -11,6 +11,9 @@ PODS:
- ReactCommon/turbomodule/core (= 0.66.1)
- fmt (6.2.1)
- glog (0.3.5)
- MSAL (1.1.22):
- MSAL/app-lib (= 1.1.22)
- MSAL/app-lib (1.1.22)
- QRCodeReader.swift (10.1.0)
- RCT-Folly (2021.06.28.00-v2):
- boost
@ -275,9 +278,11 @@ PODS:
- React-jsi (= 0.66.1)
- React-logger (= 0.66.1)
- React-perflogger (= 0.66.1)
- ReactTestApp-DevSupport (0.9.8):
- ReactTestApp-DevSupport (0.9.11):
- React-Core
- React-jsi
- ReactTestApp-MSAL (0.0.1):
- MSAL
- ReactTestApp-Resources (1.0.0-dev)
- SwiftLint (0.44.0)
- Yoga (1.14.0)
@ -288,6 +293,7 @@ DEPENDENCIES:
- FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../../../node_modules/react-native/React/FBReactNativeSpec`)
- glog (from `../../../node_modules/react-native/third-party-podspecs/glog.podspec`)
- MSAL
- QRCodeReader.swift
- RCT-Folly (from `../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../../../node_modules/react-native/Libraries/RCTRequired`)
@ -316,6 +322,7 @@ DEPENDENCIES:
- React-runtimeexecutor (from `../../../node_modules/react-native/ReactCommon/runtimeexecutor`)
- ReactCommon/turbomodule/core (from `../../../node_modules/react-native/ReactCommon`)
- ReactTestApp-DevSupport (from `../../../node_modules/react-native-test-app`)
- ReactTestApp-MSAL (from `../../react-native-test-app-msal`)
- ReactTestApp-Resources (from `..`)
- SwiftLint
- Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`)
@ -323,6 +330,7 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- fmt
- MSAL
- QRCodeReader.swift
- SwiftLint
@ -387,6 +395,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native/ReactCommon"
ReactTestApp-DevSupport:
:path: "../../../node_modules/react-native-test-app"
ReactTestApp-MSAL:
:path: "../../react-native-test-app-msal"
ReactTestApp-Resources:
:path: ".."
Yoga:
@ -399,6 +409,7 @@ SPEC CHECKSUMS:
FBReactNativeSpec: 74c869e2cffa2ffec685cd1bac6788c021da6005
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 5337263514dd6f09803962437687240c5dc39aa4
MSAL: 0d88c5430e0ffb8863f41e9f45248981a38a7610
QRCodeReader.swift: 373a389fe9a22d513c879a32a6f647c58f4ef572
RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9
RCTRequired: 3cc065b52aa18db729268b9bd78a2feffb4d0f91
@ -424,11 +435,12 @@ SPEC CHECKSUMS:
React-RCTVibration: 6600b5eed7c0fda4a433fa1198d1cb2690151791
React-runtimeexecutor: 33a949a51bec5f8a3c9e8d8092deb259600d761e
ReactCommon: 620442811dc6f707b4bf5e3b27d4f19c12d5a821
ReactTestApp-DevSupport: c0546bccf7c0fb5ec7102a5af4cc6d20359d0154
ReactTestApp-DevSupport: 01c78db18948245da37893bf6390f7fc7459617b
ReactTestApp-MSAL: e957be17b0f9419113a1f521d35f70b36440bf2e
ReactTestApp-Resources: 74a1cf509f4e7962b16361ea4e73cba3648fff5d
SwiftLint: e96c0a8c770c7ebbc4d36c55baf9096bb65c4584
Yoga: 2b4a01651f42a32f82e6cef3830a3ba48088237f
PODFILE CHECKSUM: 71b08e582fb06a8ccbdbaed0cfe518f40b10bffc
PODFILE CHECKSUM: d42888eb03ca7eac15b23817b1a07f9b4264db11
COCOAPODS: 1.11.2

Просмотреть файл

@ -42,6 +42,7 @@
"@rnx-kit/metro-serializer": "*",
"@rnx-kit/metro-serializer-esbuild": "*",
"@rnx-kit/metro-swc-worker": "*",
"@rnx-kit/react-native-test-app-msal": "*",
"@types/react": "^17.0.2",
"@types/react-native": "^0.64.0",
"metro-react-native-babel-preset": "^0.66.2",