Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/MagicSDK.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "MagicSDK"
BuildableName = "MagicSDK"
BlueprintName = "MagicSDK"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "MagicSDKTests"
BuildableName = "MagicSDKTests"
BlueprintName = "MagicSDKTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "MagicSDK"
BuildableName = "MagicSDK"
BlueprintName = "MagicSDK"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
33 changes: 2 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,9 @@ The Magic CocoaPods SDK version (v8.0.0) is currently out of sync with the Magic
## ⚠️ Removal of `loginWithMagicLink()` ⚠️
As of `v9.0.0`, passcodes (ie. `loginWithSMS()`, `loginWithEmailOTP()`) are replacing Magic Links (ie. `loginWithMagicLink()`) for all of our Mobile SDKs⁠. [Learn more](https://magic.link/docs/auth/login-methods/email/email-link-update-march-2023)

## Cocoapods
## Example

### Set up the local development env
1. To start the demo app with local development SDK, download following projects
```bash
# demo app
$ git clone https://github.com/magiclabs/magic-ios-demo
# ios SDK
$ git clone https://github.com/magiclabs/magic-ios
```

2. To enable the demo use the local development SDK. Navigate to `magic-ios-demo/Podfile` and edit the following lines.
This will make pod file install local dependencies instead of the ones distributed.

```ruby
# Distributed Library on Cocoapods
# pod 'MagicSDK', '~> 4.0'
# pod 'MagicExt-OAuth', '~> 1.0'

# Local development library
pod 'MagicSDK', :path => '../magic-ios/MagicSDK.podspec'
pod 'MagicExt-OAuth', :path => '../magic-ios-ext/MagicExt-OAuth.podspec'
```

```bash
$ cd /YOUR/PATH/TO/magic-ios-demo

# Install dependencies
$ pod install
```

3. Open `/YOUR/PATH/TO/magic-ios-demo/magic-ios-demo.xcworkspace` with XCode and try it out!
A SwiftUI demo app is included in `Example/MagicDemo`. Add the MagicSDK package to your app (Swift Package Manager) and copy the example source files into your target. Set your publishable API key in `MagicDemoApp.swift`.

---

93 changes: 75 additions & 18 deletions Sources/MagicSDK/Core/Provider/RpcProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
import MagicSDK_Web3
import WebKit
import PromiseKit
import Security

/// A custom Web3 HttpProvider that is specifically configured for use with Magic Links.
public class RpcProvider: NetworkClient, Web3Provider {

/// Various errors that may occur while processing Web3 requests
public enum ProviderError: Swift.Error {
/// The provider is not configured with an authDelegate
Expand All @@ -24,49 +25,106 @@ public class RpcProvider: NetworkClient, Web3Provider {
/// Missing callback
case missingPayloadCallback(json: String)
}

let overlay: WebViewController
public let urlBuilder: URLBuilder


/// Keychain service name for refresh token storage, namespaced by API key to avoid collisions.
private var rtKeychainService: String { "magic_rt_\(urlBuilder.apiKey)" }
private let rtKeychainAccount = "refresh_token"

required init(urlBuilder: URLBuilder) {
self.overlay = WebViewController(url: urlBuilder)
self.urlBuilder = urlBuilder
super.init()
}


// MARK: - Refresh Token

private func getRefreshToken() -> String? {
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: rtKeychainService,
kSecAttrAccount: rtKeychainAccount,
kSecReturnData: true,
kSecMatchLimit: kSecMatchLimitOne,
]
var result: AnyObject?
guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,
let data = result as? Data,
let rt = String(data: data, encoding: .utf8) else { return nil }
return rt
}

func clearRefreshToken() {
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: rtKeychainService,
kSecAttrAccount: rtKeychainAccount,
]
SecItemDelete(query as CFDictionary)
}

private func persistRefreshToken(_ rt: String) {
guard let data = rt.data(using: .utf8) else { return }
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: rtKeychainService,
kSecAttrAccount: rtKeychainAccount,
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
kSecAttrSynchronizable: false,
]
let attributes: [CFString: Any] = [kSecValueData: data]
if SecItemUpdate(query as CFDictionary, attributes as CFDictionary) == errSecItemNotFound {
SecItemAdd(query.merging(attributes) { $1 } as CFDictionary, nil)
}
}

// MARK: - Sending Requests

/// Sends an RPCRequest and parses the result
/// Web3 Provider protocal conformed
/// Web3 Provider protocol conformed
///
/// - Parameters:
/// - request: RPCRequest to send
/// - response: A completion handler for the response. Includes either the result or an error.
public func send<Params, Result>(request: RPCRequest<Params>, response: @escaping Web3ResponseCompletion<Result>) {
let msgType = OutboundMessageType.MAGIC_HANDLE_REQUEST

// Re-assign ID to the payload
let newRequest = RPCRequest(method: request.method, params: request.params)


// Retrieve persisted refresh token and DPoP JWT
let rt = getRefreshToken()
let jwt = createJwt()

// construct message data
let eventMessage = MagicRequestData(msgType: "\(msgType.rawValue)-\(urlBuilder.encodedParams)", payload: newRequest, rt: nil, jwt: createJwt())

let eventMessage = MagicRequestData(
msgType: "\(msgType.rawValue)-\(urlBuilder.encodedParams)",
payload: request,
rt: (jwt != nil) ? rt : nil, // only send rt when jwt is available (matches magic-js behavior)
jwt: jwt
)

// encode to JSON
firstly {
encode(body: eventMessage)
}.done {body throws -> Void in

let str = try String(body)

// enqueue and send to webview
try self.overlay.enqueue(message: str, id: newRequest.id) { ( responseString: String) in
try self.overlay.enqueue(message: str, id: request.id) { ( responseString: String) in
guard let jsonData = responseString.data(using: .utf8) else {
throw ProviderError.invalidJsonResponse(json: str)
}

// Decode JSON string into string
do {
let rpcResponse = try self.decoder.decode(MagicResponseData<RPCResponse<Result>>.self, from: jsonData)
let rpcResponse = try self.decoder.decode(MagicResponseData<RPCResponse<Result>>.self, from: jsonData)

// Persist refresh token if the relayer returned a new one
if let newRt = rpcResponse.rt {
self.persistRefreshToken(newRt)
}

let result = Web3Response<Result>(rpcResponse: rpcResponse.response)
response(result)
} catch {
Expand All @@ -76,7 +134,6 @@ public class RpcProvider: NetworkClient, Web3Provider {
}.catch { error in
let errResponse = Web3Response<Result>(error: ProviderError.encodingFailed(error))
response(errResponse)
// handleRollbarError(error, log: false)
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/MagicSDK/Core/Relayer/Types/BasicTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ enum InboundMessageType: String, CaseIterable {
case MAGIC_HIDE_OVERLAY
case MAGIC_HANDLE_EVENT
case MAGIC_SEND_PRODUCT_ANNOUNCEMENT
case MAGIC_PONG
}

enum OutboundMessageType: String, CaseIterable {
case MAGIC_HANDLE_REQUEST
case MAGIC_PING
}

struct MagicRequestData<T: Codable>: Codable {
Expand Down
Loading