despia-native 1.0.17 → 1.0.20

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.
Files changed (3) hide show
  1. package/README.md +588 -7
  2. package/index.js +4 -1
  3. package/package.json +9 -1
package/README.md CHANGED
@@ -91,11 +91,23 @@ Our visual editor allows you to configure native widgets, shortcuts, and dynamic
91
91
  ## Installation (REQUIRED - NO EXCEPTIONS)
92
92
 
93
93
  ```bash
94
+ # npm
94
95
  npm install despia-native
96
+
97
+ # pnpm
98
+ pnpm install despia-native
99
+
100
+ # yarn
101
+ yarn add despia-native
95
102
  ```
96
103
 
97
104
  **This is NOT optional. Your app will NOT work without this package.**
98
105
 
106
+ **Package Manager Support:**
107
+ - ✅ **npm** - Fully supported
108
+ - ✅ **pnpm** - Fully supported (v1.0.19+)
109
+ - ✅ **yarn** - Fully supported
110
+
99
111
  **Do not attempt to:**
100
112
  - Write your own version
101
113
  - Copy/paste a mock implementation
@@ -333,6 +345,30 @@ despia('widget://${svg}?refresh=${refresh_time}');
333
345
  // RevenueCat In-App Purchases
334
346
  despia('revenuecat://purchase?external_id=user_777&product=monthly_premium');
335
347
 
348
+ // Restore Purchases
349
+ const purchaseData = await despia("getpurchasehistory://", ["restoredData"]);
350
+
351
+ // RevenueCat Paywalls
352
+ despia('revenuecat://launchPaywall?external_id=user_777&offering=default');
353
+
354
+ // Identity Vault
355
+ await despia(`setvault://?key=userId&value=user123&locked=false`);
356
+ const vaultData = await despia(`readvault://?key=userId`, ['userId']);
357
+
358
+ // OAuth Authentication
359
+ despia(`oauth://?url=${encodeURIComponent(oauthUrl)}`);
360
+
361
+ // Local Storage
362
+ const encoded = encodeURIComponent(JSON.stringify(userData));
363
+ await despia(`writevalue://${encoded}`);
364
+ const data = await despia("readvalue://", ["storedValues"]);
365
+
366
+ // Read Clipboard
367
+ const clipboardData = await despia('getclipboard://', ['clipboarddata']);
368
+
369
+ // Open App Settings
370
+ despia("settingsapp://");
371
+
336
372
  // Contact Permissions
337
373
  despia('requestcontactpermission://');
338
374
  const contacts = await despia('readcontacts://', ['contacts']);
@@ -351,7 +387,8 @@ navigator.geolocation.clearWatch(watchId);
351
387
  // Push Notifications
352
388
  despia('registerpush://');
353
389
  despia('sendlocalpushmsg://push.send?s=60&msg=Hello&!#New Message&!#https://myapp.com');
354
- const oneSignalData = await despia('getonesignalplayerid://', ['onesignalplayerid']);
390
+ // Set OneSignal external user ID (call on every app load)
391
+ despia(`setonesignalplayerid://?user_id=${YOUR_LOGGED_IN_USER_ID}`);
355
392
 
356
393
  // Haptic Feedback
357
394
  despia('lighthaptic://');
@@ -465,6 +502,529 @@ const contactData = await despia('readcontacts://', ['contacts']);
465
502
  console.log('Contacts:', contactData.contacts);
466
503
  ```
467
504
 
505
+ ### RevenueCat Paywall Workflow
506
+
507
+ Create a payment system that uses RevenueCat Paywalls by launching native paywall interfaces configured in your RevenueCat dashboard:
508
+
509
+ ```javascript
510
+ // First, install the package:
511
+ // npm install despia-native
512
+
513
+ // Then import it:
514
+ import despia from 'despia-native';
515
+
516
+ // Launch a paywall for a specific offering
517
+ despia('revenuecat://launchPaywall?external_id=USER_ID&offering=default');
518
+
519
+ // Example offerings you might configure:
520
+ // - "default" - your main offering
521
+ // - "premium" - premium tier offering
522
+ // - "annual_sale" - special promotional offering
523
+ ```
524
+
525
+ **Handling Purchase Success:**
526
+
527
+ The Despia Native Runtime will call the global function `onRevenueCatPurchase()` when an in-app purchase or subscription is successfully made on the client side. Although this should not grant access immediately, it's a good time to start polling your backend to check if the RevenueCat webhook has already updated the user's status or plan permissions.
528
+
529
+ ```javascript
530
+ // Define the global callback function
531
+ window.onRevenueCatPurchase = function() {
532
+ console.log('Purchase successful! Polling backend for status update...');
533
+
534
+ // Start polling your backend to check if RevenueCat webhook
535
+ // has updated the user's status or plan permissions
536
+ // This ensures you don't grant access before the webhook processes
537
+ };
538
+ ```
539
+
540
+ This will launch a native paywall interface configured in your RevenueCat dashboard, handling purchases through Apple App Store and Google Play billing.
541
+
542
+ ### Identity Vault Workflow
543
+
544
+ The identity vault provides persistent, cross-device storage with optional biometric protection:
545
+
546
+ ```javascript
547
+ // First, install the package:
548
+ // npm install despia-native
549
+
550
+ // Then import it:
551
+ import despia from 'despia-native';
552
+
553
+ // Store identity data
554
+ await despia(`setvault://?key=${keyName}&value=${value}&locked=${isLocked}`);
555
+
556
+ // Retrieve identity data
557
+ const data = await despia(`readvault://?key=${keyName}`, [keyName]);
558
+ const value = data[keyName];
559
+ ```
560
+
561
+ **Parameters:**
562
+
563
+ - **key** - Name for your stored data (use simple names like "userId", "deviceId", "sessionToken")
564
+ - **value** - The data to store (text/string)
565
+ - **locked** - Set to `'true'` to require Face ID/fingerprint, `'false'` for normal storage
566
+
567
+ **Features:**
568
+
569
+ - **Persistent storage** - Data survives app restarts, updates, and even uninstall/reinstall
570
+ - **Cross-device sync** - Works across all user's devices with the same Apple ID or Google account
571
+ - **User tracking** - Identify the same user even after they uninstall and reinstall your app
572
+ - **Face ID protection** - Optional biometric lock for sensitive actions
573
+ - **Automatic timeout** - 30-second timeout prevents app freezing
574
+
575
+ **Perfect for:**
576
+
577
+ - Identifying users across sessions
578
+ - Preventing free trial abuse (track device even after uninstall)
579
+ - Storing login session tokens
580
+ - Protecting sensitive actions with Face ID/Touch ID
581
+ - Saving user preferences and app settings
582
+
583
+ **Example Usage:**
584
+
585
+ ```javascript
586
+ // Store user ID (normal storage)
587
+ await despia(`setvault://?key=userId&value=user123&locked=false`);
588
+
589
+ // Store session token with biometric protection
590
+ await despia(`setvault://?key=sessionToken&value=abc123xyz&locked=true`);
591
+
592
+ // Retrieve stored data
593
+ const userData = await despia(`readvault://?key=userId`, ['userId']);
594
+ console.log('User ID:', userData.userId);
595
+
596
+ const sessionData = await despia(`readvault://?key=sessionToken`, ['sessionToken']);
597
+ console.log('Session Token:', sessionData.sessionToken);
598
+ ```
599
+
600
+ ### OAuth Authentication Workflow
601
+
602
+ Launch OAuth authentication in a secure native browser session:
603
+
604
+ ```javascript
605
+ // First, install the package:
606
+ // npm install despia-native
607
+
608
+ // Then import it:
609
+ import despia from 'despia-native';
610
+
611
+ // Launch OAuth flow
612
+ const oauthUrl = 'https://your-provider.com/oauth/authorize?client_id=xxx&redirect_uri=xxx';
613
+ despia(`oauth://?url=${encodeURIComponent(oauthUrl)}`);
614
+ ```
615
+
616
+ **How it works:**
617
+
618
+ 1. **Opens secure browser session:**
619
+ - **iOS**: ASWebAuthenticationSession (secure Safari sheet)
620
+ - **Android**: Chrome Custom Tabs (secure Chrome overlay)
621
+
622
+ 2. **User completes OAuth** in the secure browser session
623
+
624
+ 3. **Parse tokens from callback URL** - OAuth providers typically return tokens in the URL hash or query parameters:
625
+ ```javascript
626
+ // Example: Parse tokens from URL hash (implicit flow)
627
+ const hash = window.location.hash.substring(1);
628
+ const hashParams = new URLSearchParams(hash);
629
+ const accessToken = hashParams.get('access_token');
630
+ const refreshToken = hashParams.get('refresh_token');
631
+ ```
632
+
633
+ 4. **Close browser and return to app** - Use your app's deeplink scheme with the `oauth/` prefix:
634
+ ```javascript
635
+ // From your callback page (still in secure browser session)
636
+ window.location.href = `myapp://oauth/auth?access_token=${accessToken}&refresh_token=${refreshToken}`;
637
+ ```
638
+
639
+ 5. **Handle deeplink in app** - The native app intercepts the deeplink, closes the browser session, and navigates your WebView to the specified path with query parameters.
640
+
641
+ **Deeplink format:** `{scheme}://oauth/{path}?params`
642
+
643
+ - `myapp://` - Your app's deeplink scheme
644
+ - `oauth/` - Required prefix that tells native code to close the browser session
645
+ - `{path}` - Where to navigate in your app (e.g., `auth`, `home`, `profile`)
646
+ - `?params` - Query parameters passed to that page
647
+
648
+ **Examples:**
649
+ - `myapp://oauth/auth?access_token=xxx` - Closes browser, opens `/auth?access_token=xxx`
650
+ - `myapp://oauth/home` - Closes browser, opens `/home`
651
+ - `myapp://oauth/profile?tab=settings` - Closes browser, opens `/profile?tab=settings`
652
+
653
+ **Perfect for:**
654
+ - Google, Facebook, Apple, GitHub, and other OAuth providers
655
+ - Secure authentication flows without leaving your app
656
+ - Native browser sessions with automatic cleanup
657
+
658
+ ### Local Storage Workflow
659
+
660
+ Create a local storage system with cross-platform support (data is cleared on app uninstall):
661
+
662
+ ```javascript
663
+ // First, install the package:
664
+ // npm install despia-native
665
+
666
+ // Then import it:
667
+ import despia from 'despia-native';
668
+
669
+ // This SDK is compatible only with the Native Despia Runtime.
670
+ // Ensure that the User Agent string includes "despia" before running this code.
671
+ // If the User Agent string doesn't include "despia" you can use Local Storage as a web fallback.
672
+
673
+ // Save data
674
+ const userData = { refresh_token: "SSBMT1ZFIERFU1BJQSBOQVRJVkUgU08gTVVDSCBJIFdBTk5BIEtJU1MgSVQh" };
675
+ const encoded = encodeURIComponent(JSON.stringify(userData));
676
+ await despia(`writevalue://${encoded}`);
677
+
678
+ // Retrieve data
679
+ const data = await despia("readvalue://", ["storedValues"]);
680
+ const userData = JSON.parse(decodeURIComponent(data.storedValues));
681
+ console.log(userData.refresh_token);
682
+ ```
683
+
684
+ **How it works:**
685
+
686
+ - **Save data**: Use `writevalue://` with a JSON-encoded string to store data on the device
687
+ - **Retrieve data**: Use `readvalue://` with `["storedValues"]` to get the stored data
688
+ - **Data format**: Data is stored as a single string and returned in the response object
689
+ - **Cross-platform**: Works on both iOS and Android
690
+ - **Lifecycle**: Data persists across app restarts but is cleared on app uninstall
691
+
692
+ **Important Notes:**
693
+
694
+ - **Runtime compatibility**: Only works with Despia Native Runtime (check User Agent for "despia")
695
+ - **Web fallback**: Use Local Storage as a fallback if not running in Despia Native Runtime
696
+ - **UI blocking**: Refrain from blocking any UI elements or adding loading screens before data is loaded, as most sessions will not have initial data yet if no data has been stored
697
+
698
+ **Perfect for:**
699
+
700
+ - Storing user preferences and settings
701
+ - Caching temporary data
702
+ - Storing session tokens (non-sensitive)
703
+ - App configuration data
704
+
705
+ ### Restore Purchases Workflow
706
+
707
+ Retrieve purchase history from the native app stores to implement "Restore Purchases" functionality and verify user entitlements:
708
+
709
+ ```javascript
710
+ // First, install the package:
711
+ // npm install despia-native
712
+
713
+ // Then import it:
714
+ import despia from 'despia-native';
715
+
716
+ // Retrieve purchase history
717
+ const data = await despia("getpurchasehistory://", ["restoredData"]);
718
+ const purchases = data.restoredData;
719
+ console.log(purchases);
720
+ ```
721
+
722
+ **How it works:**
723
+
724
+ Despia queries the native platform's billing system to retrieve all purchases associated with the current user's App Store or Google Play account. This includes active subscriptions, expired subscriptions, consumables, and non-consumable (lifetime) purchases. The data is normalized into a consistent format across both iOS and Android platforms.
725
+
726
+ **Response Structure:**
727
+
728
+ Each purchase object in the response array includes:
729
+
730
+ - **transactionId** - Unique identifier for this specific transaction
731
+ - **originalTransactionId** - Identifier linking to the original purchase (useful for subscription renewals)
732
+ - **productId** - The product identifier configured in App Store Connect / Google Play Console
733
+ - **type** - Either `"subscription"` or `"product"` (one-time purchase)
734
+ - **entitlementId** - The entitlement/access level this purchase grants
735
+ - **externalUserId** - External user identifier if configured
736
+ - **isAnonymous** - Boolean indicating if the purchase is anonymous
737
+ - **isActive** - Boolean indicating if the purchase currently grants access
738
+ - **willRenew** - Boolean indicating if a subscription will auto-renew
739
+ - **purchaseDate** - ISO timestamp of the most recent transaction
740
+ - **originalPurchaseDate** - ISO timestamp of the initial purchase
741
+ - **expirationDate** - ISO timestamp when access expires (`null` for lifetime purchases)
742
+ - **store** - Either `"app_store"` or `"play_store"`
743
+ - **country** - User's country code
744
+ - **environment** - `"production"` or `"sandbox"`
745
+ - **receipt** - The raw receipt data for server-side validation
746
+
747
+ **Example Response (iOS):**
748
+
749
+ ```javascript
750
+ [
751
+ {
752
+ "transactionId": "1000000987654321",
753
+ "originalTransactionId": "1000000123456789",
754
+ "productId": "com.app.premium.monthly",
755
+ "type": "subscription",
756
+ "entitlementId": "premium",
757
+ "externalUserId": "abc123",
758
+ "isAnonymous": false,
759
+ "isActive": true,
760
+ "willRenew": true,
761
+ "purchaseDate": "2024-01-15T14:32:05Z",
762
+ "originalPurchaseDate": "2023-06-20T09:15:33Z",
763
+ "expirationDate": "2024-02-15T14:32:05Z",
764
+ "store": "app_store",
765
+ "country": "USA",
766
+ "receipt": "MIIbngYJKoZIhvcNAQcCoIIbajCCG2YCAQExDzAN...",
767
+ "environment": "production"
768
+ },
769
+ {
770
+ "transactionId": "1000000555555555",
771
+ "originalTransactionId": "1000000555555555",
772
+ "productId": "com.app.removeads",
773
+ "type": "product",
774
+ "entitlementId": "no_ads",
775
+ "externalUserId": "abc123",
776
+ "isAnonymous": false,
777
+ "isActive": true,
778
+ "willRenew": false,
779
+ "purchaseDate": "2023-12-01T08:00:00Z",
780
+ "originalPurchaseDate": "2023-12-01T08:00:00Z",
781
+ "expirationDate": null,
782
+ "store": "app_store",
783
+ "country": "USA",
784
+ "receipt": "MIIbngYJKoZIhvcNAQcCoIIbajCCG2YCAQExDzAN...",
785
+ "environment": "production"
786
+ }
787
+ ]
788
+ ```
789
+
790
+ **Example Response (Android):**
791
+
792
+ ```javascript
793
+ [
794
+ {
795
+ "transactionId": "GPA.3372-4150-9088-12345",
796
+ "originalTransactionId": "GPA.3372-4150-9088-12345",
797
+ "productId": "com.app.premium.monthly",
798
+ "type": "subscription",
799
+ "entitlementId": "premium",
800
+ "externalUserId": "abc123",
801
+ "isAnonymous": false,
802
+ "isActive": true,
803
+ "willRenew": true,
804
+ "purchaseDate": "2024-01-15T14:32:05Z",
805
+ "originalPurchaseDate": "2023-06-20T09:15:33Z",
806
+ "expirationDate": "2024-02-15T14:32:05Z",
807
+ "store": "play_store",
808
+ "country": "US",
809
+ "receipt": "kefhajglhaljhfajkfajk.AO-J1OxBnT3hAjkl5FjpKc9...",
810
+ "environment": "production"
811
+ },
812
+ {
813
+ "transactionId": "GPA.3372-4150-9088-67890",
814
+ "originalTransactionId": "GPA.3372-4150-9088-67890",
815
+ "productId": "com.app.removeads",
816
+ "type": "product",
817
+ "entitlementId": "no_ads",
818
+ "externalUserId": "abc123",
819
+ "isAnonymous": false,
820
+ "isActive": true,
821
+ "willRenew": false,
822
+ "purchaseDate": "2023-12-01T08:00:00Z",
823
+ "originalPurchaseDate": "2023-12-01T08:00:00Z",
824
+ "expirationDate": null,
825
+ "store": "play_store",
826
+ "country": "US",
827
+ "receipt": "minodkpfokbofclncmaa.AO-J1Oy2fXpTml7rKxE3vNc9...",
828
+ "environment": "production"
829
+ }
830
+ ]
831
+ ```
832
+
833
+ **Check Active Entitlements:**
834
+
835
+ ```javascript
836
+ const data = await despia("getpurchasehistory://", ["restoredData"]);
837
+ const purchases = data.restoredData;
838
+
839
+ // Filter for active purchases only
840
+ const activePurchases = purchases.filter(p => p.isActive);
841
+
842
+ // Check if user has premium access
843
+ const hasPremium = activePurchases.some(p => p.entitlementId === "premium");
844
+
845
+ if (hasPremium) {
846
+ // Grant premium features
847
+ }
848
+ ```
849
+
850
+ **Perfect for:**
851
+
852
+ - Implementing "Restore Purchases" buttons required by App Store guidelines
853
+ - Verifying user entitlements on app launch
854
+ - Checking subscription status and renewal information
855
+ - Server-side receipt validation using raw receipt data
856
+ - Cross-platform purchase history retrieval
857
+
858
+ **Important Notes:**
859
+
860
+ - This feature requires native capabilities which are fully provided by the `despia-native` npm package
861
+ - No additional native libraries are needed
862
+ - Please follow the installation instructions for the `despia-native` npm package closely
863
+ - Implementation as mentioned is critical for App Store compliance
864
+
865
+ ### Read Clipboard Workflow
866
+
867
+ Read clipboard data from the device:
868
+
869
+ ```javascript
870
+ // First, install the package:
871
+ // npm install despia-native
872
+
873
+ // Then import it:
874
+ import despia from 'despia-native';
875
+
876
+ // Read clipboard data
877
+ const clipboardData = await despia('getclipboard://', ['clipboarddata']);
878
+
879
+ // Access the clipboard content
880
+ const content = clipboardData.clipboarddata;
881
+
882
+ // Display or process the clipboard content in your application
883
+ console.log('Clipboard content:', content);
884
+ ```
885
+
886
+ **How it works:**
887
+
888
+ - **Read clipboard**: Use `getclipboard://` with `['clipboarddata']` to retrieve the current clipboard content
889
+ - **Access content**: The clipboard content is available through the `.clipboarddata` property of the returned object
890
+ - **Native support**: Works on both iOS and Android platforms
891
+
892
+ **Perfect for:**
893
+
894
+ - Reading text from the clipboard
895
+ - Processing clipboard content in your app
896
+ - Implementing paste functionality
897
+ - Clipboard content validation
898
+
899
+ ### Open App Settings Workflow
900
+
901
+ Open your app's native settings page where users can manage permissions:
902
+
903
+ ```javascript
904
+ // First, install the package:
905
+ // npm install despia-native
906
+
907
+ // Then import it:
908
+ import despia from 'despia-native';
909
+
910
+ // Open native app settings
911
+ despia("settingsapp://");
912
+ ```
913
+
914
+ **How it works:**
915
+
916
+ - **Opens native settings**: Navigates to your app's settings page in the device's system settings
917
+ - **Permission management**: Users can manage permissions like notifications, location, camera, microphone, and more
918
+ - **One-time prompts**: Great for directing users to activate features that you can only ask once, like location or push notifications
919
+
920
+ **Perfect for:**
921
+
922
+ - Directing users to enable location services after they've denied the initial prompt
923
+ - Helping users enable push notifications if they initially declined
924
+ - Managing camera, microphone, or other permission settings
925
+ - Providing a way to access app settings when permission prompts can't be shown again
926
+
927
+ **Example Usage:**
928
+
929
+ ```javascript
930
+ // Check if location permission is needed
931
+ if (!navigator.geolocation) {
932
+ // Open settings so user can enable location
933
+ despia("settingsapp://");
934
+ }
935
+
936
+ // After user denies push notification permission
937
+ // Provide a button to open settings
938
+ function openNotificationSettings() {
939
+ despia("settingsapp://");
940
+ }
941
+ ```
942
+
943
+ ### OneSignal Push Notifications Workflow
944
+
945
+ Set up OneSignal push notifications with external user IDs to connect your database user IDs with device registrations:
946
+
947
+ ```javascript
948
+ // First, install the package:
949
+ // npm install despia-native
950
+
951
+ // Then import it:
952
+ import despia from 'despia-native';
953
+
954
+ // On every app load, set the external user ID
955
+ // This connects your logged-in user ID with the device's OneSignal registration
956
+ despia(`setonesignalplayerid://?user_id=${YOUR_LOGGED_IN_USER_ID}`);
957
+ ```
958
+
959
+ **Setup Requirements:**
960
+
961
+ 1. **Create a OneSignal account** and configure your app
962
+ 2. **Set up iOS (Apple Push Key) and Android (Firebase)** configurations in OneSignal
963
+ 3. **Important**: When configuring OneSignal, select **"Native iOS"** and **"Native Android"** platforms since Despia apps are native mobile applications
964
+ 4. **Add your OneSignal App ID** to your Despia project settings
965
+
966
+ **How it works:**
967
+
968
+ - **External User IDs**: External IDs (your database user IDs) are now the default and recommended approach
969
+ - **Player IDs**: Player IDs still work but are no longer suggested
970
+ - **Device Registration**: Devices are automatically registered in OneSignal when the app is installed
971
+ - **User Connection**: Calling `setonesignalplayerid://` on every app load connects your user ID with the device registration
972
+
973
+ **Backend Integration Required:**
974
+
975
+ You'll need to create a backend endpoint to send notifications using OneSignal's REST API with external user IDs:
976
+
977
+ ```javascript
978
+ // Backend API endpoint - send to specific user
979
+ const sendNotification = async (externalUserId, title, message) => {
980
+ const response = await fetch('https://onesignal.com/api/v1/notifications', {
981
+ method: 'POST',
982
+ headers: {
983
+ 'Content-Type': 'application/json',
984
+ 'Authorization': 'Basic YOUR_REST_API_KEY'
985
+ },
986
+ body: JSON.stringify({
987
+ app_id: 'ONESIGNAL-APP-ID',
988
+ include_external_user_ids: [externalUserId], // Array with single ID
989
+ headings: { en: title },
990
+ contents: { en: message }
991
+ })
992
+ });
993
+ return response.json();
994
+ };
995
+ ```
996
+
997
+ **Example Usage:**
998
+
999
+ ```javascript
1000
+ // In your app initialization or login flow
1001
+ function initializeApp(userId) {
1002
+ // Set external user ID on every app load
1003
+ despia(`setonesignalplayerid://?user_id=${userId}`);
1004
+
1005
+ // Register for push notifications
1006
+ despia('registerpush://');
1007
+ }
1008
+
1009
+ // Send local push notification
1010
+ despia('sendlocalpushmsg://push.send?s=60&msg=Hello&!#New Message&!#https://myapp.com');
1011
+ ```
1012
+
1013
+ **Important Notes:**
1014
+
1015
+ - This feature requires native capabilities which are fully provided by the `despia-native` npm package
1016
+ - No additional native libraries are needed
1017
+ - Please follow the installation instructions for the `despia-native` npm package closely
1018
+ - Implementation as mentioned is critical for proper push notification delivery
1019
+ - Call `setonesignalplayerid://` on every app load to ensure the user ID is always connected
1020
+
1021
+ **Perfect for:**
1022
+
1023
+ - Sending targeted notifications to specific users
1024
+ - User-based notification management
1025
+ - Cross-device notification delivery
1026
+ - Personalized push notification campaigns
1027
+
468
1028
  ### Haptic Feedback
469
1029
 
470
1030
  All haptic feedback commands have no response - they provide immediate tactile feedback:
@@ -640,8 +1200,8 @@ These CSS variables are automatically provided by the Despia native runtime and
640
1200
  - **watch** (string[], optional): Array of variable names to watch for in the response
641
1201
 
642
1202
  Returns a Promise that resolves when all watched variables are available:
643
- - **Single variable**: 30-second timeout with observation that ignores empty placeholders and requires a fresh write (Promise always resolves; on timeout it resolves with `undefined`)
644
- - **Multiple variables**: Uses VariableTracker with 5-minute auto-cleanup
1203
+ - **Single variable**: 30-second timeout with observation. `null` values resolve immediately (useful for error/not-found signals). Other empty placeholders (`undefined`, `"n/a"`, `{}`, `[]`) are ignored. Promise always resolves; on timeout it resolves with `undefined`.
1204
+ - **Multiple variables**: Uses VariableTracker with 5-minute auto-cleanup. All variables must have non-null values. Any `null` value blocks resolution until all variables have real values.
645
1205
 
646
1206
  ### Timeout behavior
647
1207
 
@@ -654,9 +1214,13 @@ prevents hanging Promises for long-running or failing native operations.
654
1214
  ### Fresh-data behavior
655
1215
 
656
1216
  Before observing, watched variables are cleared to avoid resolving on stale values.
657
- The observer ignores empty placeholders (`undefined`, `null`, `"n/a"`, `{}`, `[]`)
658
- and requires the value to change from its baseline before resolving. This ensures
659
- each call waits for a fresh write from the native side.
1217
+ The observer behavior differs by variable count:
1218
+
1219
+ - **Single variable**: `null` is treated as a valid resolution value (useful for error/not-found signals). The observer ignores other empty placeholders (`undefined`, `"n/a"`, `{}`, `[]`) and requires the value to change from its baseline before resolving.
1220
+
1221
+ - **Multiple variables**: All variables must have non-null values. The observer ignores empty placeholders (`undefined`, `null`, `"n/a"`, `{}`, `[]`) and requires all values to change from their baseline before resolving.
1222
+
1223
+ This ensures each call waits for a fresh write from the native side.
660
1224
 
661
1225
  ### Direct Property Access
662
1226
 
@@ -678,6 +1242,17 @@ Examples:
678
1242
  - `lighthaptic://`
679
1243
  - `getappversion://`
680
1244
  - `revenuecat://purchase?external_id=user_777&product=monthly_premium`
1245
+ - `revenuecat://launchPaywall?external_id=user_777&offering=default`
1246
+ - `getpurchasehistory://`
1247
+ - `getclipboard://`
1248
+ - `settingsapp://`
1249
+ - `setonesignalplayerid://?user_id=user123`
1250
+ - `registerpush://`
1251
+ - `setvault://?key=userId&value=user123&locked=false`
1252
+ - `readvault://?key=userId`
1253
+ - `writevalue://{JSON-ENCODED-STRING}`
1254
+ - `readvalue://`
1255
+ - `oauth://?url=https://provider.com/oauth/authorize`
681
1256
  - `requestcontactpermission://`
682
1257
  - `savethisimage://?url=https://example.com/image.jpg`
683
1258
 
@@ -687,16 +1262,22 @@ Your app can access these native features:
687
1262
 
688
1263
  - **Native Widgets** - Create widgets with SVG and refresh time
689
1264
  - **In-App Purchases** - RevenueCat integration with external user IDs
1265
+ - **Restore Purchases** - Retrieve purchase history from App Store and Google Play
690
1266
  - **Contact Access** - Request permissions and read contacts
691
1267
  - **Background Location** - Native tracking with browser geolocation API
692
- - **Push Notifications** - OneSignal integration and local push messages
1268
+ - **Push Notifications** - OneSignal integration with external user IDs and local push messages
693
1269
  - **Haptic Feedback** - Light, heavy, success, warning, and error feedback
694
1270
  - **App Information** - Version numbers, bundle numbers, device UUID
1271
+ - **Clipboard Access** - Read clipboard content from device
695
1272
  - **Screenshots** - Take device screenshots
696
1273
  - **Scanning Mode** - Auto, on, and off scanning controls
697
1274
  - **Store Location** - Get store location data
698
1275
  - **File Operations** - Save images and download files
1276
+ - **Identity Vault** - Persistent cross-device storage with optional biometric protection
1277
+ - **Local Storage** - Cross-platform device storage (cleared on uninstall)
1278
+ - **OAuth Authentication** - Secure OAuth flows with native browser sessions
699
1279
  - **App Control** - Reset app and disable tracking
1280
+ - **App Settings** - Open native app settings for permission management
700
1281
  - **UI Controls** - Loading spinners and full screen mode
701
1282
  - **Sharing** - Share app with custom messages and URLs
702
1283
  - **Status Bar** - Control colors and text colors
package/index.js CHANGED
@@ -58,13 +58,16 @@
58
58
  const initialSig = safeSig(initialRef);
59
59
 
60
60
  const ready = (val) => {
61
- if (val === undefined || val === null || val === "n/a") return false;
61
+ if (val === undefined || val === "n/a") return false;
62
62
  if (Array.isArray(val) && val.length === 0) return false;
63
63
  if (val && typeof val === 'object' && !Array.isArray(val) && Object.keys(val).length === 0) return false;
64
64
  return true;
65
65
  };
66
66
 
67
67
  const changed = (val) => {
68
+ // Special case: if value is null, always consider it changed (immediate signal)
69
+ if (val === null) return true;
70
+
68
71
  if (val !== initialRef) return true;
69
72
  return safeSig(val) !== initialSig;
70
73
  };
package/package.json CHANGED
@@ -1,10 +1,18 @@
1
1
  {
2
2
  "name": "despia-native",
3
- "version": "1.0.17",
3
+ "version": "1.0.20",
4
4
  "description": "JavaScript SDK for Despia native integrations with command queuing and variable watching",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
7
7
  "types": "index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./index.d.ts",
11
+ "import": "./index.js",
12
+ "require": "./index.js",
13
+ "default": "./index.js"
14
+ }
15
+ },
8
16
  "keywords": [
9
17
  "despia",
10
18
  "sdk",