@craft-native/ios 0.0.70

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/README.md ADDED
@@ -0,0 +1,619 @@
1
+ # @craft-native/ios
2
+
3
+ Build native iOS apps with web technologies using Craft.
4
+
5
+ ## Features
6
+
7
+ ### Core
8
+
9
+ - **WKWebView** - Native iOS WebView with full JavaScript support
10
+ - **Safe Areas** - Automatic handling of notch and home indicator
11
+ - **Dark Mode** - Native dark/light theme support
12
+
13
+ ### Input & Feedback
14
+
15
+ - **Native Speech Recognition** - iOS SFSpeechRecognizer (works offline!)
16
+ - **Haptic Feedback** - UIImpactFeedbackGenerator, UINotificationFeedbackGenerator
17
+ - **Audio Recording** - AVAudioRecorder with compressed output
18
+ - **Video Recording** - UIImagePickerController video capture
19
+
20
+ ### Device Access
21
+
22
+ - **Camera & Gallery** - Take photos or pick from gallery
23
+ - **Barcode/QR Scanner** - VisionKit DataScanner (iOS 16+)
24
+ - **File Picker** - UIDocumentPickerViewController
25
+ - **File Download** - Download and save files to device
26
+
27
+ ### Sensors & Location
28
+
29
+ - **Geolocation** - GPS and network location via CLLocationManager
30
+ - **Motion Sensors** - Accelerometer & gyroscope via CoreMotion
31
+ - **NFC** - Read NFC tags via CoreNFC
32
+
33
+ ### Communication
34
+
35
+ - **Share Sheet** - Native UIActivityViewController
36
+ - **Clipboard** - Read and write to system clipboard
37
+ - **Push Notifications** - APNs integration
38
+
39
+ ### Security & Auth
40
+
41
+ - **Biometric Auth** - Face ID / Touch ID
42
+ - **Social Auth** - Apple Sign In
43
+ - **Secure Storage** - Keychain integration
44
+
45
+ ### Data & Storage
46
+
47
+ - **Local Database** - SQLite with full SQL support
48
+ - **Bluetooth LE** - CoreBluetooth scanning
49
+
50
+ ### System
51
+
52
+ - **Device Info** - Device model, OS version, screen info
53
+ - **Network Status** - Connection type monitoring (WiFi/Cellular)
54
+ - **App Badge** - Notification badge count
55
+ - **App State** - Foreground/background detection
56
+ - **Flashlight** - Camera flash control
57
+ - **Open URL** - External browser launch
58
+ - **Vibration** - Custom vibration patterns
59
+ - **App Review** - StoreKit review prompt
60
+ - **Screen Capture** - Take screenshots programmatically
61
+ - **Health/Fitness** - HealthKit integration
62
+
63
+ ## Installation
64
+
65
+ The iOS support is built into the main `craft` CLI:
66
+
67
+ ```bash
68
+ bun add craft-native
69
+ ```
70
+
71
+ ## Quick Start
72
+
73
+ ### 1. Initialize Project
74
+
75
+ ```bash
76
+ craft ios init MyApp --bundle-id com.example.myapp
77
+ cd ios
78
+ ```
79
+
80
+ ### 2. Add Your Web Content
81
+
82
+ Replace `dist/index.html` with your web app, or point to a dev server:
83
+
84
+ ```bash
85
+ craft ios build --html-path ../dist/index.html
86
+ # or
87
+ craft ios build --dev-server http://localhost:3456
88
+ ```
89
+
90
+ ### 3. Open in Xcode
91
+
92
+ ```bash
93
+ craft ios open
94
+ ```
95
+
96
+ ### 4. Run on Device
97
+
98
+ In Xcode:
99
+
100
+ 1. Select your Team in Signing & Capabilities
101
+ 2. Connect your iPhone
102
+ 3. Select your device
103
+ 4. Click Run
104
+
105
+ ## Configuration
106
+
107
+ Edit `craft.config.json` in your iOS project:
108
+
109
+ ```json
110
+ {
111
+ "appName": "MyApp",
112
+ "bundleId": "com.example.myapp",
113
+ "version": "1.0.0",
114
+ "buildNumber": "1",
115
+ "darkMode": true,
116
+ "backgroundColor": "#1a1a2e",
117
+ "enableSpeechRecognition": true,
118
+ "enableHaptics": true,
119
+ "enableShare": true,
120
+ "enableCamera": true,
121
+ "enableBiometric": true,
122
+ "enablePushNotifications": false,
123
+ "enableSecureStorage": true,
124
+ "enableGeolocation": true,
125
+ "enableClipboard": true,
126
+ "enableNetworkStatus": true,
127
+ "enableAppReview": true,
128
+ "enableFlashlight": true,
129
+ "enableQRScanner": true,
130
+ "enableFilePicker": true,
131
+ "enableFileDownload": true,
132
+ "enableSocialAuth": true,
133
+ "enableAudioRecording": true,
134
+ "enableVideoRecording": true,
135
+ "enableMotionSensors": true,
136
+ "enableLocalDatabase": true,
137
+ "enableBluetooth": true,
138
+ "enableNFC": true,
139
+ "enableHealthKit": false,
140
+ "enableScreenCapture": true,
141
+ "iosVersion": "15.0",
142
+ "teamId": ""
143
+ }
144
+ ```
145
+
146
+ ## JavaScript Bridge
147
+
148
+ Once Craft is initialized, the `window.craft` object is available:
149
+
150
+ ```javascript
151
+ // Wait for Craft to be ready
152
+ window.addEventListener('craftReady', (e) => {
153
+ console.log('Platform:', e.detail.platform); // 'ios'
154
+ console.log('Capabilities:', e.detail.capabilities);
155
+ });
156
+
157
+ // Haptic feedback
158
+ window.craft.haptic('light'); // light, medium, heavy
159
+ window.craft.haptic('success'); // success, warning, error
160
+ window.craft.haptic('selection');
161
+
162
+ // Speech recognition
163
+ window.craft.startListening();
164
+ window.craft.stopListening();
165
+
166
+ // Listen for speech events
167
+ window.addEventListener('craftSpeechStart', () => { /* recording started */ });
168
+ window.addEventListener('craftSpeechResult', (e) => {
169
+ console.log(e.detail.transcript); // "hello world"
170
+ console.log(e.detail.isFinal); // true/false
171
+ });
172
+ window.addEventListener('craftSpeechEnd', () => { /* recording stopped */ });
173
+ window.addEventListener('craftSpeechError', (e) => {
174
+ console.error(e.detail.error);
175
+ });
176
+
177
+ // Share
178
+ window.craft.share('Check out this app!', 'My App');
179
+
180
+ // Camera & Gallery
181
+ const imageBase64 = await window.craft.openCamera();
182
+ const selectedImage = await window.craft.pickImage();
183
+
184
+ // Barcode/QR Scanner (iOS 16+)
185
+ const scannedCode = await window.craft.scanQRCode();
186
+ console.log(scannedCode); // "https://example.com"
187
+
188
+ // File Picker
189
+ const file = await window.craft.pickFile(['public.image', 'public.pdf']);
190
+ console.log(file.name, file.data); // base64 encoded
191
+
192
+ // File Download
193
+ await window.craft.downloadFile('https://example.com/file.pdf', 'document.pdf');
194
+ await window.craft.saveFile(base64Data, 'image.png', 'image/png');
195
+
196
+ // Social Auth (Apple Sign In)
197
+ const user = await window.craft.signInWithApple();
198
+ console.log(user.userId, user.email, user.fullName, user.identityToken);
199
+
200
+ // Audio Recording
201
+ await window.craft.startAudioRecording();
202
+ const audioBase64 = await window.craft.stopAudioRecording();
203
+
204
+ // Video Recording
205
+ const videoBase64 = await window.craft.startVideoRecording();
206
+
207
+ // Motion Sensors
208
+ window.craft.startMotionUpdates();
209
+ window.addEventListener('craftMotionUpdate', (e) => {
210
+ console.log('Accelerometer:', e.detail.accelerometer); // {x, y, z}
211
+ console.log('Gyroscope:', e.detail.gyroscope); // {x, y, z}
212
+ });
213
+ window.craft.stopMotionUpdates();
214
+
215
+ // Local Database (SQLite)
216
+ await window.craft.db.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
217
+ await window.craft.db.execute('INSERT INTO users (name) VALUES (?)', ['Alice']);
218
+ const users = await window.craft.db.query('SELECT * FROM users');
219
+
220
+ // Bluetooth LE Scanning
221
+ window.craft.startBluetoothScan();
222
+ window.addEventListener('craftBluetoothDevice', (e) => {
223
+ console.log(e.detail.name, e.detail.uuid, e.detail.rssi);
224
+ });
225
+ window.craft.stopBluetoothScan();
226
+
227
+ // NFC Tag Reading
228
+ const nfcData = await window.craft.scanNFC();
229
+ console.log(nfcData); // Tag content
230
+
231
+ // Health/Fitness (requires HealthKit entitlement)
232
+ await window.craft.requestHealthAuthorization(['stepCount', 'heartRate']);
233
+ const steps = await window.craft.getHealthData('stepCount', startDate, endDate);
234
+
235
+ // Screen Capture
236
+ const screenshotBase64 = await window.craft.takeScreenshot();
237
+
238
+ // Biometric authentication
239
+ try {
240
+ const authenticated = await window.craft.authenticate('Confirm your identity');
241
+ if (authenticated) {
242
+ console.log('User authenticated!');
243
+ }
244
+ } catch (error) {
245
+ console.log('Authentication failed:', error.message);
246
+ }
247
+
248
+ // Push notifications
249
+ const token = await window.craft.registerPush();
250
+ console.log('Push token:', token);
251
+
252
+ // Secure storage (Keychain)
253
+ await window.craft.secureStore.set('api*key', 'secret123');
254
+ const apiKey = await window.craft.secureStore.get('api*key');
255
+ await window.craft.secureStore.remove('api*key');
256
+
257
+ // Geolocation
258
+ const position = await window.craft.getCurrentPosition();
259
+ console.log(position.latitude, position.longitude);
260
+
261
+ // Watch position (continuous updates)
262
+ const watchId = window.craft.watchPosition((position) => {
263
+ console.log('New position:', position);
264
+ });
265
+ window.craft.clearWatch(watchId);
266
+
267
+ // Clipboard
268
+ await window.craft.clipboard.write('Hello World');
269
+ const text = await window.craft.clipboard.read();
270
+
271
+ // Device Info
272
+ const device = window.craft.getDeviceInfo();
273
+ console.log(device.model, device.systemVersion, device.screenWidth);
274
+
275
+ // App Badge
276
+ window.craft.setBadge(5);
277
+ window.craft.clearBadge();
278
+
279
+ // Network Status
280
+ const network = window.craft.getNetworkStatus();
281
+ console.log(network.isConnected, network.type); // true, 'wifi'
282
+
283
+ // Network change listener
284
+ window.craft.onNetworkChange((status) => {
285
+ console.log('Network changed:', status);
286
+ });
287
+ window.craft.offNetworkChange();
288
+
289
+ // App Review (StoreKit)
290
+ await window.craft.requestReview();
291
+
292
+ // Flashlight
293
+ window.craft.setFlashlight(true); // turn on
294
+ window.craft.setFlashlight(false); // turn off
295
+ window.craft.toggleFlashlight(); // toggle
296
+
297
+ // Open URL (external browser)
298
+ window.craft.openURL('https://example.com');
299
+
300
+ // Vibration Pattern (custom durations in ms)
301
+ window.craft.vibrate([100, 50, 100, 50, 200]);
302
+
303
+ // App State
304
+ const state = window.craft.getAppState(); // 'active', 'inactive', 'background'
305
+
306
+ // App state change listener
307
+ window.craft.onAppStateChange((state) => {
308
+ console.log('App state:', state);
309
+ });
310
+ window.craft.offAppStateChange();
311
+
312
+ // Logging (appears in Xcode console)
313
+ window.craft.log('Debug message');
314
+
315
+ // ==================== High Value Bridges ====================
316
+
317
+ // Contacts
318
+ const contacts = await window.craft.getContacts();
319
+ console.log(contacts); // [{id, givenName, familyName, displayName, phoneNumbers, emailAddresses}]
320
+
321
+ const newContactId = await window.craft.addContact({
322
+ givenName: 'John',
323
+ familyName: 'Doe',
324
+ phone: '+1234567890',
325
+ email: 'john@example.com'
326
+ });
327
+
328
+ // Calendar Events
329
+ const events = await window.craft.getCalendarEvents(startDateMs, endDateMs);
330
+ console.log(events); // [{id, title, location, notes, startDate, endDate, isAllDay}]
331
+
332
+ const newEventId = await window.craft.createCalendarEvent({
333
+ title: 'Meeting',
334
+ location: 'Office',
335
+ notes: 'Discuss project',
336
+ startDate: Date.now(),
337
+ endDate: Date.now() + 3600000,
338
+ isAllDay: false
339
+ });
340
+
341
+ await window.craft.deleteCalendarEvent(eventId);
342
+
343
+ // Local Notifications
344
+ const notificationId = await window.craft.scheduleNotification({
345
+ id: 'reminder-1',
346
+ title: 'Reminder',
347
+ body: 'Don\'t forget!',
348
+ badge: 1,
349
+ timestamp: Date.now() + 60000, // 1 minute from now
350
+ // or use delay: 60000 // delay in ms
351
+ });
352
+
353
+ await window.craft.cancelNotification('reminder-1');
354
+ await window.craft.cancelAllNotifications();
355
+ const pending = await window.craft.getPendingNotifications();
356
+
357
+ // In-App Purchase
358
+ const products = await window.craft.getProducts(['product*id*1', 'product*id*2']);
359
+ console.log(products); // [{id, title, description, price, priceLocale}]
360
+
361
+ const purchaseResult = await window.craft.purchase('product*id*1');
362
+ console.log(purchaseResult); // {transactionId, productId, ...}
363
+
364
+ await window.craft.restorePurchases();
365
+
366
+ // Keep Screen Awake
367
+ window.craft.setKeepAwake(true); // Prevent screen dimming
368
+ window.craft.setKeepAwake(false); // Allow screen dimming
369
+
370
+ // Orientation Lock
371
+ window.craft.lockOrientation('portrait'); // Lock to portrait
372
+ window.craft.lockOrientation('landscape'); // Lock to landscape
373
+ window.craft.unlockOrientation(); // Allow all orientations
374
+
375
+ // ==================== Medium Value Bridges ====================
376
+
377
+ // Background Tasks (iOS 13+)
378
+ await window.craft.backgroundTask.register('sync-data');
379
+ await window.craft.backgroundTask.schedule('sync-data', {
380
+ delay: 900, // 15 minutes minimum
381
+ requiresNetwork: true,
382
+ requiresCharging: false
383
+ });
384
+ await window.craft.backgroundTask.cancel('sync-data');
385
+ await window.craft.backgroundTask.cancelAll();
386
+
387
+ // PDF Viewer
388
+ await window.craft.openPDF('https://example.com/document.pdf');
389
+ await window.craft.openPDF(base64PdfData, 5); // Open at page 5
390
+ await window.craft.closePDF();
391
+
392
+ // Contacts Picker (shows native picker UI)
393
+ const contact = await window.craft.pickContact();
394
+ console.log(contact); // {id, givenName, familyName, displayName, phoneNumbers, emailAddresses}
395
+
396
+ const contacts = await window.craft.pickContact({multiple: true}); // Select multiple
397
+
398
+ // App Shortcuts (3D Touch / long press)
399
+ await window.craft.shortcuts.set([
400
+ {type: 'new-message', title: 'New Message', subtitle: 'Start composing', iconName: 'square.and.pencil'},
401
+ {type: 'search', title: 'Search', iconName: 'magnifyingglass'}
402
+ ]);
403
+ window.craft.shortcuts.onShortcut((shortcut) => {
404
+ console.log('Shortcut activated:', shortcut.type);
405
+ });
406
+ await window.craft.shortcuts.clear();
407
+
408
+ // Keychain Sharing (cross-app data with access groups)
409
+ await window.craft.sharedKeychain.set('user*token', 'abc123', 'com.example.shared'); // group optional
410
+ const result = await window.craft.sharedKeychain.get('user*token', 'com.example.shared');
411
+ console.log(result.value); // 'abc123'
412
+ await window.craft.sharedKeychain.remove('user*token');
413
+
414
+ // Local Auth Persistence (skip re-auth for a duration)
415
+ await window.craft.authPersistence.enable(300); // 5 minutes
416
+ const status = await window.craft.authPersistence.check();
417
+ if (status.isValid) {
418
+ console.log('Session valid for', status.remainingSeconds, 'more seconds');
419
+ }
420
+ await window.craft.authPersistence.clear();
421
+
422
+ // ==================== Nice to Have Bridges ====================
423
+
424
+ // AR (ARKit) - Requires iOS device with A9+ chip
425
+ await window.craft.ar.start({planeDetection: true});
426
+ window.craft.ar.onPlaneDetected((plane) => {
427
+ console.log('Plane detected:', plane.id, plane.alignment);
428
+ });
429
+
430
+ // Place 3D objects (built-in shapes or .usdz/.scn files)
431
+ const obj = await window.craft.ar.placeObject('box', {x: 0, y: 0, z: -0.5});
432
+ console.log('Object placed:', obj.objectId);
433
+ // Built-in shapes: 'box', 'sphere', 'cylinder', 'cone'
434
+ // Or provide URL to .usdz or .scn file
435
+
436
+ // Get detected planes
437
+ const planes = await window.craft.ar.getPlanes();
438
+ console.log(planes); // [{id, alignment, center, extent}]
439
+
440
+ // Remove object
441
+ await window.craft.ar.removeObject(obj.objectId);
442
+
443
+ // Stop AR session
444
+ await window.craft.ar.stop();
445
+
446
+ // ML (Vision Framework)
447
+ // First capture an image
448
+ const image = await window.craft.openCamera();
449
+
450
+ // Image Classification - Identify what's in the image
451
+ const labels = await window.craft.ml.classifyImage(image);
452
+ console.log(labels); // [{label: 'cat', confidence: 0.95}, ...]
453
+
454
+ // Object Detection - Detect and locate objects
455
+ const objects = await window.craft.ml.detectObjects(image);
456
+ console.log(objects); // [{labels: [...], boundingBox: {x, y, width, height}}]
457
+
458
+ // Text Recognition (OCR) - Extract text from image
459
+ const textResults = await window.craft.ml.recognizeText(image);
460
+ console.log(textResults); // [{text: 'Hello World', confidence: 0.98, boundingBox: {...}}]
461
+
462
+ // ==================== Widgets (WidgetKit) ====================
463
+
464
+ // Update widget data - displayed on home screen widget
465
+ await window.craft.widget.update({
466
+ title: 'My App',
467
+ subtitle: 'Latest update',
468
+ value: '42',
469
+ icon: 'star.fill' // SF Symbol name
470
+ });
471
+
472
+ // Reload all widgets
473
+ await window.craft.widget.reload();
474
+
475
+ // ==================== Siri Shortcuts ====================
476
+
477
+ // Register a Siri shortcut
478
+ await window.craft.siri.register('Open my app', 'open*app');
479
+
480
+ // Remove a Siri shortcut
481
+ await window.craft.siri.remove('open*app');
482
+
483
+ // Listen for Siri shortcut invocations
484
+ window.craft.siri.onInvoke((detail) => {
485
+ console.log('Siri invoked:', detail.action);
486
+ });
487
+
488
+ // ==================== Watch Connectivity ====================
489
+
490
+ // Check if watch is reachable
491
+ const status = await window.craft.watch.isReachable();
492
+ console.log(status.reachable); // true/false
493
+
494
+ // Send message to watch
495
+ const reply = await window.craft.watch.send({
496
+ action: 'ping',
497
+ data: { timestamp: Date.now() }
498
+ });
499
+
500
+ // Update application context (synced to watch)
501
+ await window.craft.watch.updateContext({
502
+ lastUpdate: Date.now(),
503
+ status: 'active'
504
+ });
505
+
506
+ // Listen for messages from watch
507
+ window.craft.watch.onMessage((message) => {
508
+ console.log('Watch message:', message);
509
+ });
510
+
511
+ // Listen for watch reachability changes
512
+ window.craft.watch.onReachabilityChange((status) => {
513
+ console.log('Watch reachable:', status.reachable);
514
+ });
515
+ ```
516
+
517
+ ## CLI Reference
518
+
519
+ ```bash
520
+ craft ios init <name> # Initialize new iOS project
521
+ craft ios build # Build and generate Xcode project
522
+ craft ios open # Open Xcode project
523
+ craft ios run --simulator # Run on iOS Simulator
524
+
525
+ Options:
526
+ --bundle-id <id> Bundle identifier
527
+ --team-id <id> Apple Developer Team ID
528
+ --html-path <path> Path to HTML file
529
+ -d, --dev-server <url> Development server URL
530
+ -o, --output <dir> Output directory (default: ./ios)
531
+ -s, --simulator Run on simulator
532
+ ```
533
+
534
+ ## Requirements
535
+
536
+ - macOS with Xcode 15+
537
+ - [xcodegen](https://github.com/yonaskolb/XcodeGen): `brew install xcodegen`
538
+ - Apple Developer account (free or paid)
539
+ - iPhone running iOS 15+ (for device deployment)
540
+
541
+ ## Permissions
542
+
543
+ Add to your Info.plist as needed:
544
+
545
+ ```xml
546
+ <!-- Speech Recognition -->
547
+ <key>NSSpeechRecognitionUsageDescription</key>
548
+ <string>This app uses speech recognition for voice commands.</string>
549
+ <key>NSMicrophoneUsageDescription</key>
550
+ <string>This app needs microphone access for speech recognition.</string>
551
+
552
+ <!-- Camera -->
553
+ <key>NSCameraUsageDescription</key>
554
+ <string>This app needs camera access to take photos.</string>
555
+ <key>NSPhotoLibraryUsageDescription</key>
556
+ <string>This app needs photo library access.</string>
557
+
558
+ <!-- Location -->
559
+ <key>NSLocationWhenInUseUsageDescription</key>
560
+ <string>This app needs your location.</string>
561
+
562
+ <!-- Face ID -->
563
+ <key>NSFaceIDUsageDescription</key>
564
+ <string>This app uses Face ID for authentication.</string>
565
+
566
+ <!-- NFC -->
567
+ <key>NFCReaderUsageDescription</key>
568
+ <string>This app reads NFC tags.</string>
569
+
570
+ <!-- Bluetooth -->
571
+ <key>NSBluetoothAlwaysUsageDescription</key>
572
+ <string>This app uses Bluetooth.</string>
573
+
574
+ <!-- Health -->
575
+ <key>NSHealthShareUsageDescription</key>
576
+ <string>This app reads health data.</string>
577
+ <key>NSHealthUpdateUsageDescription</key>
578
+ <string>This app writes health data.</string>
579
+
580
+ <!-- Contacts -->
581
+ <key>NSContactsUsageDescription</key>
582
+ <string>This app accesses your contacts.</string>
583
+
584
+ <!-- Calendar -->
585
+ <key>NSCalendarsUsageDescription</key>
586
+ <string>This app accesses your calendar.</string>
587
+ ```
588
+
589
+ ## Development Mode
590
+
591
+ For hot-reload during development:
592
+
593
+ ```bash
594
+ # Terminal 1: Start your dev server
595
+ bun run dev # e.g., http://localhost:3456
596
+
597
+ # Terminal 2: Build iOS with dev server
598
+ craft ios build --dev-server http://localhost:3456
599
+ craft ios open
600
+ ```
601
+
602
+ The app will load from your dev server instead of bundled HTML.
603
+
604
+ ## Publishing
605
+
606
+ Build for App Store:
607
+
608
+ ```bash
609
+ craft publish --ios
610
+ ```
611
+
612
+ This will:
613
+
614
+ 1. Build a release archive
615
+ 2. Output path for manual upload to App Store Connect
616
+
617
+ ## License
618
+
619
+ MIT