@qore-id/react-native-qoreid-sdk 2.0.2 โ†’ 2.1.0

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.
@@ -0,0 +1,365 @@
1
+ # qoreid_sdk_pods.rb
2
+ require 'fileutils'
3
+ require 'xcodeproj'
4
+ require 'json'
5
+
6
+ def log(msg)
7
+ Pod::UI.puts "[QOREID] #{msg}"
8
+ end
9
+
10
+ def warn(msg)
11
+ Pod::UI.warn "[QOREID] #{msg}"
12
+ end
13
+
14
+ def qoreid_sdk_post_install(installer)
15
+ sandbox_root = installer.sandbox.root.to_s
16
+ project_root = sandbox_root.sub(/\/Pods$/, "")
17
+
18
+ app_target = installer.aggregate_targets.first.name.sub(/^Pods-/, "")
19
+ log "Detected app target: #{app_target}"
20
+
21
+ ios_path = File.join(project_root, app_target)
22
+
23
+ app_delegate_swift = File.join(ios_path, "AppDelegate.swift")
24
+ app_delegate_m = File.join(ios_path, "AppDelegate.m")
25
+ scene_delegate_swift = File.join(ios_path, "SceneDelegate.swift")
26
+ info_plist_path = File.join(ios_path, "Info.plist")
27
+
28
+ log "๐Ÿ” Running iOS auto-configuration..."
29
+
30
+ # ------------------------------
31
+ # Detect AppDelegate file
32
+ # ------------------------------
33
+ if File.exist?(app_delegate_swift)
34
+ modify_app_delegate_swift(app_delegate_swift)
35
+ elsif File.exist?(app_delegate_m)
36
+ modify_app_delegate_objc(app_delegate_m)
37
+ else
38
+ warn "โš ๏ธ No AppDelegate.swift or AppDelegate.m found."
39
+ end
40
+
41
+ # ------------------------------
42
+ # Create SceneDelegate.swift
43
+ # ------------------------------
44
+ if File.exist?(scene_delegate_swift)
45
+ log "โœ”๏ธ SceneDelegate.swift already exists."
46
+ else
47
+ create_scene_delegate_swift(scene_delegate_swift)
48
+ log "๐Ÿ†• SceneDelegate.swift created."
49
+
50
+ end
51
+ # NEW: Add to Xcode project
52
+ add_file_to_xcode_project(installer, scene_delegate_swift, app_target)
53
+
54
+
55
+ # ------------------------------
56
+ # Update Info.plist
57
+ # ------------------------------
58
+ # Ensure the file exists
59
+ if File.exist?(info_plist_path)
60
+ update_info_plist(info_plist_path)
61
+ else
62
+ warn "โš ๏ธ Info.plist not found at path: #{info_plist_path}"
63
+ end
64
+
65
+ log "๐ŸŽ‰ iOS setup complete."
66
+ end
67
+
68
+
69
+ # ==============================================================
70
+ # MODIFY APPDELEGATE (SWIFT)
71
+ # ==============================================================
72
+
73
+ def modify_app_delegate_swift(path)
74
+ content = File.read(path)
75
+
76
+ if content.include?("configurationForConnecting")
77
+ log "โœ”๏ธ AppDelegate.swift already configured โ€” skipping."
78
+ return
79
+ end
80
+
81
+ insertion = <<-SWIFT
82
+
83
+ // MARK: - QoreID Scene Support
84
+ func application(_ application: UIApplication,
85
+ configurationForConnecting connectingSceneSession: UISceneSession,
86
+ options: UIScene.ConnectionOptions) -> UISceneConfiguration {
87
+ let config = UISceneConfiguration(name: "Default Configuration",
88
+ sessionRole: connectingSceneSession.role)
89
+ config.delegateClass = SceneDelegate.self
90
+ return config
91
+ }
92
+ SWIFT
93
+
94
+ # New Regex: Finds the class definition and its content, up to the closing brace.
95
+ # The use of `.*?` makes the matching non-greedy, stopping at the first logical match.
96
+ # We look for the final brace of the AppDelegate class specifically.
97
+ app_delegate_regex = /(\s+class\s+AppDelegate[^{]*\{[\s\S]*?)(\n^\})/m
98
+
99
+ # Search for the class block start and stop
100
+ if content.match(app_delegate_regex)
101
+ # The insertion point should be the line just before the final `}` of the AppDelegate.
102
+ # We replace the final closing brace with the new code followed by the closing brace.
103
+ # We use a pattern that finds the opening brace of AppDelegate and captures the content
104
+ # inside, making the capture non-greedy until it finds the first `}` on its own line.
105
+
106
+ # Pattern:
107
+ # 1. `class AppDelegate ... {` (captured in group 1: `\\1`)
108
+ # 2. `[\s\S]*?` (all content, non-greedy)
109
+ # 3. `^\}` (the final closing brace on a new line, captured in group 2: `\\2`)
110
+
111
+ # We use a simplified and more direct approach by looking for the content
112
+ # and replacing the LAST line of the AppDelegate class body before its closing brace.
113
+
114
+ # Find content from 'class AppDelegate' up to its final '}'
115
+ # We use a more robust search pattern focusing on the end of the class.
116
+ # (Group 1: everything up to the last line before '}', Group 2: the final '}')
117
+ replacement_successful = content.sub!(
118
+ app_delegate_regex,
119
+ "\\1\n#{insertion}\n\\2"
120
+ )
121
+
122
+ content = comment_out_rn_start_method(content)
123
+
124
+ if replacement_successful
125
+ File.write(path, content)
126
+ log "๐Ÿ”ง Added Scene configuration to AppDelegate.swift inside AppDelegate class"
127
+ else
128
+ log "โš ๏ธ Could not perform targeted substitution"
129
+ end
130
+ else
131
+ log "โš ๏ธ Could not find AppDelegate class โ€” Scene snippet not inserted"
132
+ end
133
+ end
134
+
135
+ def comment_out_rn_start_method(content)
136
+ rn_start_regx = /factory.startReactNative\([\s\S]*?\)/
137
+ if content.match?(rn_start_regx)
138
+ content = content.sub!(rn_start_regx, "")
139
+ end
140
+ content
141
+ end
142
+
143
+ # ==============================================================
144
+ # MODIFY APPDELEGATE (OBJECTIVE-C)
145
+ # ==============================================================
146
+
147
+ def modify_app_delegate_objc(path)
148
+ content = File.read(path)
149
+
150
+ if content.include?("configurationForConnectingSceneSession")
151
+ log "โœ”๏ธ AppDelegate.m already configured โ€” skipping."
152
+ return
153
+ end
154
+
155
+ insertion = <<-OBJC
156
+
157
+ // MARK: - QoreID Scene Support
158
+ - (UISceneConfiguration *)application:(UIApplication *)application
159
+ configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
160
+ options:(UISceneConnectionOptions *)options {
161
+ UISceneConfiguration *configuration =
162
+ [[UISceneConfiguration alloc] initWithName:@"Default Configuration"
163
+ sessionRole:connectingSceneSession.role];
164
+ configuration.delegateClass = [SceneDelegate class];
165
+ return configuration;
166
+ }
167
+ OBJC
168
+
169
+ content.sub!(/@end\s*\z/, "#{insertion}\n@end")
170
+
171
+ File.write(path, content)
172
+ log "๐Ÿ”ง Added Scene configuration to AppDelegate.m"
173
+ end
174
+
175
+
176
+ # ==============================================================
177
+ # CREATE SCENEDELEGATE.SWIFT
178
+ # ==============================================================
179
+
180
+ def create_scene_delegate_swift(path)
181
+ # Load RN_APP_NAME from qoreid.config.json
182
+ relative_path = File.join(File.dirname(__FILE__), '..', 'qoreid.config.json')
183
+ file_contents = File.read(relative_path)
184
+ app_name = JSON.parse(file_contents)['RN_APP_NAME'] || "main"
185
+
186
+ template = <<-SWIFT
187
+ import UIKit
188
+
189
+ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
190
+
191
+ var window: UIWindow?
192
+
193
+ func scene(_ scene: UIScene,
194
+ willConnectTo session: UISceneSession,
195
+ options connectionOptions: UIScene.ConnectionOptions) {
196
+
197
+ guard let windowScene = scene as? UIWindowScene else { return }
198
+
199
+ let window = UIWindow(windowScene: windowScene)
200
+
201
+ let appDelegate = UIApplication.shared.delegate as! AppDelegate
202
+ guard let factory = appDelegate.reactNativeFactory else {
203
+ fatalError("React Native factory not initialized")
204
+ }
205
+
206
+ let rootView = factory.rootViewFactory.view(
207
+ withModuleName: "#{app_name}",
208
+ initialProperties: [:],
209
+ launchOptions: nil
210
+ )
211
+
212
+ let viewController = UIViewController()
213
+ viewController.view = rootView
214
+
215
+ let navController = UINavigationController(rootViewController: viewController)
216
+ navController.setNavigationBarHidden(true, animated: false)
217
+
218
+ window.rootViewController = navController
219
+ window.makeKeyAndVisible()
220
+
221
+ self.window = window
222
+ }
223
+ }
224
+ SWIFT
225
+
226
+ File.write(path, template)
227
+ end
228
+
229
+
230
+ def add_file_to_xcode_project(installer, file_path, target_name)
231
+ project = installer.pods_project
232
+
233
+ # WARNING: This path calculation is fragile and assumes a specific file hierarchy.
234
+ app_project_path = project.path.dirname + "../#{target_name}.xcodeproj"
235
+
236
+ unless File.exist?(app_project_path)
237
+ warn "โš ๏ธ Application Xcode project not found at calculated path: #{app_project_path}"
238
+ return
239
+ end
240
+
241
+ app_project = Xcodeproj::Project.open(app_project_path)
242
+ app_target = app_project.targets.find { |t| t.name == target_name }
243
+
244
+ if app_target.nil?
245
+ warn "โš ๏ธ Could not find Xcode target: #{target_name}"
246
+ return
247
+ end
248
+
249
+ # 1. Find or create the target group (usually matching the target_name folder)
250
+ group = app_project.main_group.find_subpath(target_name, true)
251
+ group.set_source_tree('SOURCE_ROOT')
252
+
253
+ # 2. Determine relative path for file reference
254
+ begin
255
+ # Get path relative to the .xcodeproj directory
256
+ rel_path = Pathname.new(file_path).relative_path_from(app_project.path.dirname).to_s
257
+ rescue
258
+ # Fallback to absolute path if calculation fails
259
+ rel_path = file_path
260
+ warn "โš ๏ธ Could not calculate relative path for #{file_path}. Using absolute path."
261
+ end
262
+
263
+ # 3. Create file reference (or find existing one if already added)
264
+ # NOTE: Xcodeproj will not create a duplicate reference if one with the same path exists in the group.
265
+ file_ref = group.new_file(rel_path)
266
+
267
+ # 4. Add file reference to the target's build phases
268
+ file_basename = File.basename(file_path)
269
+
270
+ # Check if the file reference is already associated with the target's Source Build Phase.
271
+ # This is the correct, though verbose, check for Swift/Obj-C source files.
272
+ is_already_a_source = app_target.source_build_phase.files.any? { |f| f.file_ref.path == file_ref.path }
273
+
274
+ if is_already_a_source
275
+ log "File #{file_basename} is already referenced by the Source Build Phase. Skipping addition."
276
+ else
277
+ # Use the preferred method which handles all build phases (.swift, .h, .m, resources)
278
+ begin
279
+ app_target.add_file_references([file_ref])
280
+ rescue => e
281
+ # Fallback if add_file_references fails
282
+ warn "โŒ Primary method failed. Attempting to force Source membership: #{e.message}"
283
+
284
+ # Manual fallback to ensure it is added to Compile Sources if it is a Swift file
285
+ if file_path.end_with?('.swift')
286
+ app_target.source_build_phase.add_file_reference(file_ref)
287
+ log "Forced addition to Compile Sources build phase."
288
+ else
289
+ warn "File type not recognized for forced Source/Resource addition."
290
+ end
291
+ end
292
+ end
293
+
294
+ # 5. Guaranteed Save
295
+ begin
296
+ app_project.save
297
+ rescue => e
298
+ warn "โŒ Failed to save Xcode project: #{e.message}"
299
+ end
300
+ end
301
+
302
+ # ==============================================================
303
+ # UPDATE INFOPLIST (if needed)
304
+ # ==============================================================
305
+
306
+ # Function to update Info.plist with Scene Manifest configuration.
307
+ # This logic checks for the key and inserts the required XML structure
308
+ # before the final </dict> tag of the root plist dictionary.
309
+ def update_info_plist(path)
310
+ content = File.read(path)
311
+
312
+ # 1. Define the XML snippet to insert
313
+ insertion = <<-XML
314
+ <key>UIApplicationSceneManifest</key>
315
+ <dict>
316
+ <key>UIApplicationSupportsMultipleScenes</key>
317
+ <false/>
318
+ <key>UISceneConfigurations</key>
319
+ <dict>
320
+ <key>UIWindowSceneSessionRoleApplication</key>
321
+ <array>
322
+ <dict>
323
+ <key>UISceneClassName</key>
324
+ <string>UIWindowScene</string>
325
+ <key>UISceneDelegateClassName</key>
326
+ <string>SceneDelegate</string>
327
+ <key>UISceneConfigurationName</key>
328
+ <string>Default Configuration</string>
329
+ </dict>
330
+ </array>
331
+ </dict>
332
+ </dict>
333
+ XML
334
+
335
+ # 2. Check if the configuration already exists
336
+ if content.include?("<key>UIApplicationSceneManifest</key>")
337
+ log "โœ”๏ธ Info.plist already contains UIApplicationSceneManifest โ€” skipping."
338
+ return
339
+ end
340
+
341
+ # 3. Find the injection point: the final closing </dict> before </plist>
342
+ # We use a pattern that specifically targets the `</dict>` tag that is followed
343
+ # by `</plist>` (potentially with leading whitespace). This ensures we are
344
+ # inside the root dictionary.
345
+
346
+ # The regex captures:
347
+ # Group 1 (\\1): The closing </dict> tag
348
+ # Group 2 (\\2): The closing </plist> tag
349
+
350
+ # We look for the last instance of </dict> followed by </plist> at the end of the file.
351
+
352
+ # We escape the `<` and `>` tags for regex safety.
353
+ final_dict_regex = /<\/dict>(\s*)<\/plist>/m
354
+
355
+ # Perform the substitution: inject the insertion before the closing </dict>
356
+ replacement_successful = content.sub!(final_dict_regex, "#{insertion}\\1</dict>\\2</plist>")
357
+
358
+ if replacement_successful
359
+ File.write(path, content)
360
+ log "๐Ÿ”ง Added UIApplicationSceneManifest configuration to Info.plist."
361
+ else
362
+ log "โš ๏ธ Failed to find the final </dict> tag in Info.plist for substitution."
363
+ end
364
+ end
365
+
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { execSync } = require('child_process');
5
+ const {
6
+ sharedEnv,
7
+ extractAndroidNamespace,
8
+ patchExpoAndroidGeneratedAutolinking,
9
+ } = require('./utils');
10
+
11
+ const androidPath = path.join(process.cwd(), 'android');
12
+
13
+ // execute ./gradlew removeQoreidSetup from android directory
14
+ const gradlewPath = process.platform === 'win32' ? 'gradlew.bat' : './gradlew';
15
+ const gradlewFullPath = path.join(androidPath, gradlewPath);
16
+
17
+ if (!fs.existsSync(gradlewFullPath)) {
18
+ console.error(
19
+ 'Gradle wrapper not found. Are you in the root of a React Native project?'
20
+ );
21
+ process.exit(1);
22
+ }
23
+
24
+ try {
25
+ console.log('๐Ÿ”ง Setting up Android Qoreid SDK...');
26
+ const androidNamespace = extractAndroidNamespace();
27
+ sharedEnv.set(androidNamespace, 'FULL_ANDROID_NAMESPACE');
28
+ const command = sharedEnv.getCommand('FULL_ANDROID_NAMESPACE');
29
+ execSync(`${command ? command + ' && ' : ''}${gradlewFullPath} setupQoreid`, {
30
+ cwd: androidPath,
31
+ stdio: 'inherit',
32
+ });
33
+ androidNamespace && patchExpoAndroidGeneratedAutolinking(androidNamespace);
34
+ } catch (error) {
35
+ console.error('Error during Android setup:', error);
36
+ process.exit(1);
37
+ }
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { injectAppName } = require('./utils');
6
+
7
+ const podfilePath = path.join(process.cwd(), 'ios', 'Podfile');
8
+ const requiredLinePath =
9
+ "require_relative '../node_modules/@qore-id/react-native-qoreid-sdk/scripts/ios/qoreid_sdk_pods.rb'";
10
+
11
+ const postInstallLine = 'qoreid_sdk_post_install(installer)';
12
+
13
+ if (!fs.existsSync(podfilePath)) {
14
+ console.error(
15
+ 'Podfile not found. Are you in the root of a React Native project?'
16
+ );
17
+ process.exit(1);
18
+ }
19
+
20
+ injectAppName();
21
+
22
+ let podfileContent = fs.readFileSync(podfilePath, 'utf8');
23
+
24
+ // Check and add the require_relative line if it's not present
25
+ if (!podfileContent.includes(requiredLinePath)) {
26
+ podfileContent = `${requiredLinePath}\n` + podfileContent;
27
+ console.log('โœ… Added Qoreid require_relative line to Podfile');
28
+ } else {
29
+ console.log('โ„น๏ธ Qoreid require_relative line already exists in Podfile');
30
+ }
31
+
32
+ // Check and add the post_install call if it's not present
33
+ if (!podfileContent.includes(postInstallLine)) {
34
+ // Find the post_install block
35
+ const postInstallIndex = podfileContent.indexOf(
36
+ 'post_install do |installer|'
37
+ );
38
+ if (postInstallIndex !== -1) {
39
+ // Find the end of the post_install block
40
+ const endOfPostInstallIndex = podfileContent.indexOf(
41
+ 'end',
42
+ postInstallIndex
43
+ );
44
+ if (endOfPostInstallIndex !== -1) {
45
+ // Insert the post_install call before the end of the block
46
+ podfileContent =
47
+ podfileContent.slice(0, endOfPostInstallIndex) +
48
+ ` ${postInstallLine}\n` +
49
+ podfileContent.slice(endOfPostInstallIndex);
50
+ console.log('โœ… Added Qoreid post_install call to Podfile');
51
+ } else {
52
+ console.error(
53
+ 'Could not find the end of the post_install block in Podfile.',
54
+ 'Add the line manually: ' + postInstallLine
55
+ );
56
+ process.exit(1);
57
+ }
58
+ } else {
59
+ console.error(
60
+ 'post_install block not found in Podfile. Please ensure your Podfile has a post_install block.'
61
+ );
62
+ process.exit(1);
63
+ }
64
+ } else {
65
+ console.log('โ„น๏ธ Qoreid post_install call already exists in Podfile');
66
+ }
67
+
68
+ // Write the updated content back to the Podfile
69
+ fs.writeFileSync(podfilePath, podfileContent, 'utf8');
70
+
71
+ console.log('โœ… iOS Qoreid SDK setup complete!');
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { run, injectAppName } = require('./utils');
4
+
5
+ injectAppName();
6
+
7
+ Promise.all([run('android-setup.js'), run('ios-setup.js')]).catch((err) => {
8
+ console.error(err);
9
+ process.exit(1);
10
+ });
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { removeQoreIdExpoPlugin } = require('./utils');
5
+
6
+ removeQoreIdExpoPlugin();
7
+
8
+ const cwd = process.cwd();
9
+ const androidPath = path.join(cwd, 'android');
10
+
11
+ // execute ./gradlew removeQoreidSetup from android directory
12
+ const gradlewPath = process.platform === 'win32' ? 'gradlew.bat' : './gradlew';
13
+ const gradlewFullPath = path.join(androidPath, gradlewPath);
14
+
15
+ if (!fs.existsSync(gradlewFullPath)) {
16
+ console.warn(
17
+ 'Gradle wrapper not found. Are you in the root of a React Native project?'
18
+ );
19
+ // exit with success to avoid showing an error in the terminal/CI
20
+ return;
21
+ }
22
+
23
+ const { execSync } = require('child_process');
24
+
25
+ try {
26
+ console.log('๐Ÿงน Rolling back Android Qoreid setup...');
27
+ execSync(`${gradlewFullPath} removeQoreidSetup`, {
28
+ cwd: androidPath,
29
+ stdio: 'inherit',
30
+ });
31
+ } catch (error) {
32
+ console.error('Error during Android rollback:', error);
33
+ process.exit(0);
34
+ }
35
+
36
+ console.log('๐Ÿงน Android rollback complete!');
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { removeQoreIdExpoPlugin } = require('./utils');
5
+
6
+ removeQoreIdExpoPlugin();
7
+
8
+ const cwd = process.cwd();
9
+
10
+ const podfilePath = path.join(cwd, 'ios', 'Podfile');
11
+ const requiredLinePath =
12
+ "require_relative '../node_modules/@qore-id/react-native-qoreid-sdk/scripts/ios/qoreid_sdk_pods.rb'";
13
+ const postInstallLine = 'qoreid_sdk_post_install(installer)';
14
+
15
+ if (!fs.existsSync(podfilePath)) {
16
+ console.error(
17
+ 'Podfile not found. Are you in the root of a React Native project?'
18
+ );
19
+ return;
20
+ }
21
+
22
+ let podfileContent = fs.readFileSync(podfilePath, 'utf8');
23
+
24
+ // Remove the require_relative line if it exists
25
+ const requireLineRegex = new RegExp(
26
+ `^\\s*${requiredLinePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`,
27
+ 'm'
28
+ );
29
+ podfileContent = podfileContent.replace(requireLineRegex, '');
30
+
31
+ // Remove the post_install call if it exists
32
+ const postInstallRegex = new RegExp(
33
+ `^\\s*${postInstallLine.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`,
34
+ 'm'
35
+ );
36
+
37
+ podfileContent = podfileContent.replace(postInstallRegex, '');
38
+
39
+ // Write the updated content back to the Podfile
40
+ fs.writeFileSync(podfilePath, podfileContent, 'utf8');
41
+
42
+ console.log('๐Ÿงน iOS Qoreid SDK uninstallation complete!');
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { run } = require('./utils');
4
+
5
+ Promise.all([run('uninstall-android.js'), run('uninstall-ios.js')]).catch(
6
+ (err) => {
7
+ console.error(err);
8
+ process.exit(1);
9
+ }
10
+ );