@novastera-oss/nitro-metamask 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app.plugin.js CHANGED
@@ -7,12 +7,12 @@ const withMetamaskAppDelegate = (config) => {
7
7
  // Check if AppDelegate is Swift
8
8
  if (modResults.language === 'swift') {
9
9
  // Check if the method already exists
10
- if (modResults.contents.includes('MetaMaskSDK.shared.handleUrl')) {
10
+ if (modResults.contents.includes('MetaMaskSDK.sharedInstance?.handleUrl')) {
11
11
  return config;
12
12
  }
13
13
 
14
14
  // Add import if not present
15
- if (!modResults.contents.includes('import MetaMaskSDK')) {
15
+ if (!modResults.contents.includes('import metamask_ios_sdk')) {
16
16
  // Find the last import statement and add after it
17
17
  const importRegex = /^import\s+.*$/gm;
18
18
  const imports = modResults.contents.match(importRegex);
@@ -21,14 +21,14 @@ const withMetamaskAppDelegate = (config) => {
21
21
  const lastImportIndex = modResults.contents.lastIndexOf(lastImport);
22
22
  modResults.contents =
23
23
  modResults.contents.slice(0, lastImportIndex + lastImport.length) +
24
- '\nimport MetaMaskSDK' +
24
+ '\nimport metamask_ios_sdk' +
25
25
  modResults.contents.slice(lastImportIndex + lastImport.length);
26
26
  } else {
27
27
  // No imports found, add at the top after the first line
28
28
  const firstLineIndex = modResults.contents.indexOf('\n');
29
29
  modResults.contents =
30
30
  modResults.contents.slice(0, firstLineIndex + 1) +
31
- 'import MetaMaskSDK\n' +
31
+ 'import metamask_ios_sdk\n' +
32
32
  modResults.contents.slice(firstLineIndex + 1);
33
33
  }
34
34
  }
@@ -47,7 +47,7 @@ const withMetamaskAppDelegate = (config) => {
47
47
  if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
48
48
  components.host == "mmsdk" {
49
49
  // Handle MetaMask deep link return
50
- MetaMaskSDK.shared.handleUrl(url)
50
+ MetaMaskSDK.sharedInstance?.handleUrl(url)
51
51
  return true
52
52
  }
53
53
 
@@ -1,36 +1,133 @@
1
1
  import NitroModules
2
- import MetaMaskSDK
2
+ import metamask_ios_sdk
3
3
  import Foundation
4
4
 
5
5
  final class HybridNitroMetamask: HybridNitroMetamaskSpec {
6
- private let sdk = MetaMaskSDK.shared
6
+ // SDK instance - can be recreated when configure() is called
7
+ // Aligned with Android: SDK is recreated when configure() changes values
8
+ private var sdkInstance: MetaMaskSDK? = nil
9
+ private var lastUsedUrl: String? = nil
10
+ private var lastUsedScheme: String? = nil
7
11
 
8
- // Configurable dapp URL - stored for consistency with Android
9
- // iOS SDK handles deep linking automatically via Info.plist
12
+ // Get or create MetaMask SDK instance
13
+ // Aligned with Android: SDK is recreated when configure() changes values
14
+ private var sdk: MetaMaskSDK {
15
+ let currentUrl = dappUrl ?? "https://metamask.io"
16
+ let currentScheme = deepLinkScheme ?? getDefaultDappScheme()
17
+
18
+ // Check if we need to recreate the SDK
19
+ if let existing = sdkInstance,
20
+ lastUsedUrl == currentUrl,
21
+ lastUsedScheme == currentScheme {
22
+ return existing
23
+ }
24
+
25
+ // Check if there's a shared instance we should use (only if it matches our config)
26
+ if let existing = MetaMaskSDK.sharedInstance,
27
+ lastUsedUrl == currentUrl,
28
+ lastUsedScheme == currentScheme {
29
+ sdkInstance = existing
30
+ return existing
31
+ }
32
+
33
+ // Create new SDK instance with current configuration
34
+ let appMetadata = AppMetadata(
35
+ name: "NitroMetamask",
36
+ url: currentUrl,
37
+ iconUrl: nil,
38
+ base64Icon: nil,
39
+ apiVersion: nil
40
+ )
41
+
42
+ NSLog("NitroMetamask: Initializing SDK with url=\(currentUrl), scheme=\(currentScheme)")
43
+
44
+ let newSdk = MetaMaskSDK.shared(
45
+ appMetadata,
46
+ transport: .deeplinking(dappScheme: currentScheme),
47
+ enableDebug: true,
48
+ sdkOptions: nil
49
+ )
50
+
51
+ sdkInstance = newSdk
52
+ lastUsedUrl = currentUrl
53
+ lastUsedScheme = currentScheme
54
+
55
+ return newSdk
56
+ }
57
+
58
+ // Configurable dapp URL and deep link scheme
10
59
  private var dappUrl: String? = nil
60
+ private var deepLinkScheme: String? = nil
11
61
 
12
62
  func configure(dappUrl: String?, deepLinkScheme: String?) {
13
- // iOS SDK handles deep linking automatically via Info.plist
14
- // Store the URL for consistency with Android implementation
15
- // deepLinkScheme is ignored on iOS as it's handled automatically
16
- self.dappUrl = dappUrl
17
- NSLog("NitroMetamask: configure: Dapp URL set to \(dappUrl ?? "default"). Deep link handled automatically via Info.plist")
63
+ let urlToUse = dappUrl ?? "https://metamask.io"
64
+ let schemeToUse = deepLinkScheme ?? getDefaultDappScheme()
65
+
66
+ var changed = false
67
+ if self.dappUrl != urlToUse {
68
+ self.dappUrl = urlToUse
69
+ changed = true
70
+ }
71
+ if self.deepLinkScheme != schemeToUse {
72
+ self.deepLinkScheme = schemeToUse
73
+ changed = true
74
+ }
75
+
76
+ if changed {
77
+ // Invalidate existing instance to force recreation with new values
78
+ // This aligns with Android behavior
79
+ sdkInstance = nil
80
+ lastUsedUrl = nil
81
+ lastUsedScheme = nil
82
+ NSLog("NitroMetamask: configure: Dapp URL=\(urlToUse), Scheme=\(schemeToUse). SDK will be recreated on next access.")
83
+ } else {
84
+ NSLog("NitroMetamask: configure: No changes, keeping existing SDK instance.")
85
+ }
86
+ }
87
+
88
+ // Helper to get default deep link scheme from Info.plist
89
+ private func getDefaultDappScheme() -> String {
90
+ // Try to get the first URL scheme from Info.plist
91
+ if let urlTypes = Bundle.main.object(forInfoDictionaryKey: "CFBundleURLTypes") as? [[String: Any]],
92
+ let firstType = urlTypes.first,
93
+ let schemes = firstType["CFBundleURLSchemes"] as? [String],
94
+ let firstScheme = schemes.first {
95
+ return firstScheme
96
+ }
97
+ // Fallback to a default scheme
98
+ return "nitrometamask"
18
99
  }
19
100
 
20
101
  func connect() -> Promise<ConnectResult> {
21
102
  // Use Promise.async with Swift async/await for best practice in Nitro modules
22
103
  // Reference: https://nitro.margelo.com/docs/types/promises
23
104
  return Promise.async {
24
- // Based on MetaMask iOS SDK docs: let connectResult = await metamaskSDK.connect()
105
+ NSLog("NitroMetamask: connect() called")
106
+
107
+ // Check if MetaMask is installed before attempting to connect
108
+ if !self.sdk.isMetaMaskInstalled {
109
+ let errorMessage = "MetaMask is not installed. Please install MetaMask from the App Store to continue."
110
+ NSLog("NitroMetamask: MetaMask not installed - \(errorMessage)")
111
+ throw NSError(
112
+ domain: "MetamaskConnector",
113
+ code: -2,
114
+ userInfo: [NSLocalizedDescriptionKey: errorMessage]
115
+ )
116
+ }
117
+
118
+ // Based on MetaMask iOS SDK docs: connect() returns Result<[String], RequestError>
25
119
  // Reference: https://github.com/MetaMask/metamask-ios-sdk
26
- let connectResult = try await self.sdk.connect()
120
+ let connectResult = await self.sdk.connect()
121
+
122
+ NSLog("NitroMetamask: connect() result: \(connectResult)")
27
123
 
28
124
  switch connectResult {
29
125
  case .success:
30
126
  // After successful connection, get account info from SDK
31
- // Note: sdk.account is a String (address), not an object
127
+ // Note: sdk.account is a String (not optional), check if empty
32
128
  // Reference: https://raw.githubusercontent.com/MetaMask/metamask-ios-sdk/924d91bb3e98a5383c3082d6d5ba3ddac9e1c565/README.md
33
- guard let address = self.sdk.account, !address.isEmpty else {
129
+ let address = self.sdk.account
130
+ guard !address.isEmpty else {
34
131
  throw NSError(
35
132
  domain: "MetamaskConnector",
36
133
  code: -1,
@@ -40,11 +137,9 @@ final class HybridNitroMetamask: HybridNitroMetamaskSpec {
40
137
 
41
138
  // Parse chainId from hex string (e.g., "0x1") to number
42
139
  // Nitro requires chainId to be a number, not a string, for type safety
43
- guard
44
- let chainIdHex = self.sdk.chainId,
45
- !chainIdHex.isEmpty,
46
- let chainIdInt = Int(chainIdHex.replacingOccurrences(of: "0x", with: ""), radix: 16)
47
- else {
140
+ let chainIdHex = self.sdk.chainId
141
+ guard !chainIdHex.isEmpty,
142
+ let chainIdInt = Int(chainIdHex.replacingOccurrences(of: "0x", with: ""), radix: 16) else {
48
143
  throw NSError(
49
144
  domain: "MetamaskConnector",
50
145
  code: -1,
@@ -58,6 +153,7 @@ final class HybridNitroMetamask: HybridNitroMetamaskSpec {
58
153
  )
59
154
 
60
155
  case .failure(let error):
156
+ NSLog("NitroMetamask: connect() failed: \(error)")
61
157
  throw error
62
158
  }
63
159
  }
@@ -70,7 +166,8 @@ final class HybridNitroMetamask: HybridNitroMetamaskSpec {
70
166
  // Use explicit sign() method (requires connection first via connect())
71
167
  // This is more explicit and predictable than connectAndSign() which forces connection
72
168
  // Nitro encourages explicit object state, not convenience shortcuts
73
- guard let account = self.sdk.account, !account.isEmpty else {
169
+ let account = self.sdk.account
170
+ guard !account.isEmpty else {
74
171
  throw NSError(
75
172
  domain: "MetamaskConnector",
76
173
  code: -1,
@@ -88,20 +185,19 @@ final class HybridNitroMetamask: HybridNitroMetamaskSpec {
88
185
  )
89
186
 
90
187
  // Make the request using the SDK's async request method
91
- let result = try await self.sdk.request(request)
188
+ // request() returns Result<String, RequestError>
189
+ NSLog("NitroMetamask: signMessage() calling request")
190
+ let result = await self.sdk.request(request)
191
+
192
+ NSLog("NitroMetamask: signMessage() result: \(result)")
92
193
 
93
194
  // Extract signature from response
94
- // The signature should be a hex-encoded string (0x-prefixed)
95
- if let signature = result as? String {
195
+ switch result {
196
+ case .success(let signature):
96
197
  return signature
97
- } else if let dict = result as? [String: Any], let sig = dict["signature"] as? String ?? dict["result"] as? String {
98
- return sig
99
- } else {
100
- throw NSError(
101
- domain: "MetamaskConnector",
102
- code: -1,
103
- userInfo: [NSLocalizedDescriptionKey: "Invalid signature response format"]
104
- )
198
+ case .failure(let error):
199
+ NSLog("NitroMetamask: signMessage() failed: \(error)")
200
+ throw error
105
201
  }
106
202
  }
107
203
  }
@@ -133,12 +229,27 @@ final class HybridNitroMetamask: HybridNitroMetamaskSpec {
133
229
  // Use the SDK's connectAndSign convenience method - it will connect if needed and sign the message
134
230
  // This is the recommended approach per MetaMask iOS SDK documentation
135
231
  // Reference: https://github.com/MetaMask/metamask-ios-sdk
136
- let connectSignResult = try await self.sdk.connectAndSign(message: message)
232
+ // Check if MetaMask is installed before attempting to connect and sign
233
+ if !self.sdk.isMetaMaskInstalled {
234
+ let errorMessage = "MetaMask is not installed. Please install MetaMask from the App Store to continue."
235
+ NSLog("NitroMetamask: MetaMask not installed - \(errorMessage)")
236
+ throw NSError(
237
+ domain: "MetamaskConnector",
238
+ code: -2,
239
+ userInfo: [NSLocalizedDescriptionKey: errorMessage]
240
+ )
241
+ }
242
+
243
+ NSLog("NitroMetamask: connectSign() calling connectAndSign")
244
+ let connectSignResult = await self.sdk.connectAndSign(message: message)
245
+
246
+ NSLog("NitroMetamask: connectSign() result: \(connectSignResult)")
137
247
 
138
248
  switch connectSignResult {
139
249
  case .success(let signature):
140
250
  // After connectSign completes, get the address and chainId from the SDK
141
- guard let address = self.sdk.account, !address.isEmpty else {
251
+ let address = self.sdk.account
252
+ guard !address.isEmpty else {
142
253
  throw NSError(
143
254
  domain: "MetamaskConnector",
144
255
  code: -1,
@@ -146,7 +257,8 @@ final class HybridNitroMetamask: HybridNitroMetamaskSpec {
146
257
  )
147
258
  }
148
259
 
149
- guard let chainIdHex = self.sdk.chainId, !chainIdHex.isEmpty else {
260
+ let chainIdHex = self.sdk.chainId
261
+ guard !chainIdHex.isEmpty else {
150
262
  throw NSError(
151
263
  domain: "MetamaskConnector",
152
264
  code: -1,
@@ -176,18 +288,20 @@ final class HybridNitroMetamask: HybridNitroMetamaskSpec {
176
288
 
177
289
  func getAddress() -> Promise<Variant_NullType_String> {
178
290
  return Promise.async {
179
- if let account = self.sdk.account, !account.isEmpty {
291
+ let account = self.sdk.account
292
+ if !account.isEmpty {
180
293
  return Variant_NullType_String.second(account)
181
294
  } else {
182
- return Variant_NullType_String.first(NullType())
295
+ return Variant_NullType_String.first(NullType.null)
183
296
  }
184
297
  }
185
298
  }
186
299
 
187
300
  func getChainId() -> Promise<Variant_NullType_Int64> {
188
301
  return Promise.async {
189
- guard let chainIdHex = self.sdk.chainId, !chainIdHex.isEmpty else {
190
- return Variant_NullType_Int64.first(NullType())
302
+ let chainIdHex = self.sdk.chainId
303
+ guard !chainIdHex.isEmpty else {
304
+ return Variant_NullType_Int64.first(NullType.null)
191
305
  }
192
306
 
193
307
  // Parse chainId from hex string (e.g., "0x1") to Int64 (bigint maps to Int64 in Swift)
@@ -195,7 +309,7 @@ final class HybridNitroMetamask: HybridNitroMetamaskSpec {
195
309
  return Variant_NullType_Int64.second(chainIdInt)
196
310
  } else {
197
311
  NSLog("NitroMetamask: Invalid chainId format: \(chainIdHex)")
198
- return Variant_NullType_Int64.first(NullType())
312
+ return Variant_NullType_Int64.first(NullType.null)
199
313
  }
200
314
  }
201
315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novastera-oss/nitro-metamask",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Native mobile MetaMask wallet integration for React Native. Part of Novastera CRM/ERP platform ecosystem. Provides secure authentication and message signing for Web3 mobile applications.",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/module/index.js",