@onairos/react-native 3.7.2 → 3.7.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.
Files changed (237) hide show
  1. package/lib/commonjs/api/index.js +219 -9
  2. package/lib/commonjs/components/BodyText.js +27 -9
  3. package/lib/commonjs/components/BrandMark.js +111 -10
  4. package/lib/commonjs/components/CodeInput.js +116 -9
  5. package/lib/commonjs/components/EmailInput.js +30 -8
  6. package/lib/commonjs/components/GoogleButton.js +56 -9
  7. package/lib/commonjs/components/HeadingGroup.js +43 -9
  8. package/lib/commonjs/components/LLMDataInputModal.js +664 -14
  9. package/lib/commonjs/components/ModalHeader.js +99 -9
  10. package/lib/commonjs/components/ModalSheet.js +47 -9
  11. package/lib/commonjs/components/Onairos.js +380 -14
  12. package/lib/commonjs/components/OnairosButton.js +313 -13
  13. package/lib/commonjs/components/OnairosSignInButton.js +130 -12
  14. package/lib/commonjs/components/Overlay.js +465 -13
  15. package/lib/commonjs/components/PersonaImage.js +137 -10
  16. package/lib/commonjs/components/PersonaLoadingScreen.js +318 -12
  17. package/lib/commonjs/components/PersonalizationConsentScreen.js +467 -13
  18. package/lib/commonjs/components/PinCreationScreen.js +403 -12
  19. package/lib/commonjs/components/PinInput.js +464 -9
  20. package/lib/commonjs/components/PlatformConnectorsStep.js +1311 -23
  21. package/lib/commonjs/components/PlatformList.js +137 -10
  22. package/lib/commonjs/components/PlatformToggle.js +180 -9
  23. package/lib/commonjs/components/PrimaryButton.js +180 -10
  24. package/lib/commonjs/components/SignInMatchAnimation.js +197 -9
  25. package/lib/commonjs/components/SignInStep.js +345 -12
  26. package/lib/commonjs/components/UniversalOnboarding.js +2780 -30
  27. package/lib/commonjs/components/VerificationStep.js +176 -11
  28. package/lib/commonjs/components/WelcomeScreen.js +461 -22
  29. package/lib/commonjs/components/icons/Basicproficon.js +37 -8
  30. package/lib/commonjs/components/icons/Basicprofile.js +21 -8
  31. package/lib/commonjs/components/icons/Checkbox.js +21 -8
  32. package/lib/commonjs/components/icons/Checkmark.js +27 -8
  33. package/lib/commonjs/components/icons/Contentanalysis.js +21 -8
  34. package/lib/commonjs/components/icons/Contenticon.js +39 -8
  35. package/lib/commonjs/components/icons/EnochE.js +41 -8
  36. package/lib/commonjs/components/icons/Personalityicon.js +30 -8
  37. package/lib/commonjs/components/icons/Personalityprofile.js +21 -8
  38. package/lib/commonjs/components/icons/Personalitytraits.js +21 -8
  39. package/lib/commonjs/components/icons/Userpreferences.js +21 -8
  40. package/lib/commonjs/components/icons/index.js +84 -17
  41. package/lib/commonjs/components/onboarding/OAuthWebView.js +1754 -18
  42. package/lib/commonjs/components/onboarding/OnboardingHeader.js +74 -10
  43. package/lib/commonjs/components/onboarding/PinInput.js +283 -10
  44. package/lib/commonjs/components/onboarding/PlatformConnector.js +249 -11
  45. package/lib/commonjs/config/PLATFORM_APIS.md +849 -0
  46. package/lib/commonjs/config/api.js +56 -7
  47. package/lib/commonjs/constants/index.js +120 -7
  48. package/lib/commonjs/context/AuthContext.js +345 -10
  49. package/lib/commonjs/hooks/useConnectedAccounts.js +111 -9
  50. package/lib/commonjs/hooks/useConnections.js +102 -8
  51. package/lib/commonjs/hooks/useCredentials.js +178 -10
  52. package/lib/commonjs/hooks/useUserConnections.js +148 -10
  53. package/lib/commonjs/index.js +439 -34
  54. package/lib/commonjs/services/apiClient.js +298 -8
  55. package/lib/commonjs/services/biometricPinService.js +180 -8
  56. package/lib/commonjs/services/chatGPTConversationExtractor.js +155 -8
  57. package/lib/commonjs/services/chatGPTConversationService.js +275 -9
  58. package/lib/commonjs/services/claudeConversationExtractor.js +103 -8
  59. package/lib/commonjs/services/claudeConversationService.js +158 -9
  60. package/lib/commonjs/services/connectedAccountsService.js +310 -10
  61. package/lib/commonjs/services/googleAuthService.js +252 -11
  62. package/lib/commonjs/services/hingeDataExtractor.js +105 -8
  63. package/lib/commonjs/services/hingeDataService.js +150 -9
  64. package/lib/commonjs/services/imageCompressionService.js +260 -7
  65. package/lib/commonjs/services/instagramDataExtractor.js +126 -8
  66. package/lib/commonjs/services/instagramDataService.js +163 -9
  67. package/lib/commonjs/services/jwtStorageService.js +276 -7
  68. package/lib/commonjs/services/linkedinDOMExtractor.js +245 -7
  69. package/lib/commonjs/services/linkedinProfileService.js +222 -9
  70. package/lib/commonjs/services/linkedinScrapingService.js +230 -8
  71. package/lib/commonjs/services/llmDataStorage.js +294 -8
  72. package/lib/commonjs/services/mobileTrainingService.js +186 -8
  73. package/lib/commonjs/services/netflixDataExtractor.js +120 -8
  74. package/lib/commonjs/services/netflixDataService.js +198 -9
  75. package/lib/commonjs/services/pinEncryptionService.js +84 -8
  76. package/lib/commonjs/services/pinStorageUtils.js +105 -7
  77. package/lib/commonjs/services/platformAuthService.js +1484 -12
  78. package/lib/commonjs/services/sephoraDataExtractor.js +140 -8
  79. package/lib/commonjs/services/sephoraDataService.js +200 -9
  80. package/lib/commonjs/services/spotifyDataExtractor.js +148 -8
  81. package/lib/commonjs/services/spotifyDataService.js +241 -9
  82. package/lib/commonjs/services/storageService.js +404 -8
  83. package/lib/commonjs/services/telegramDataExtractor.js +115 -8
  84. package/lib/commonjs/services/telegramDataService.js +499 -9
  85. package/lib/commonjs/services/trainingApiHelpers.js +73 -7
  86. package/lib/commonjs/services/userConnectionsService.js +340 -10
  87. package/lib/commonjs/services/youtubeMigrationService.js +416 -10
  88. package/lib/commonjs/theme/index.js +250 -7
  89. package/lib/commonjs/types/ambient.d.js +2 -1
  90. package/lib/commonjs/types/declarations.d.js +2 -1
  91. package/lib/commonjs/types/index.js +6 -1
  92. package/lib/commonjs/types/node-fix.d.js +2 -1
  93. package/lib/commonjs/types/node-override.d.js +2 -1
  94. package/lib/commonjs/types/opacity.d.js +2 -1
  95. package/lib/commonjs/types.js +14 -1
  96. package/lib/commonjs/utils/Portal.js +98 -8
  97. package/lib/commonjs/utils/api.js +130 -9
  98. package/lib/commonjs/utils/assetRegistry.js +210 -35
  99. package/lib/commonjs/utils/auth.js +112 -9
  100. package/lib/commonjs/utils/connectorTests.js +613 -29
  101. package/lib/commonjs/utils/crypto.js +62 -8
  102. package/lib/commonjs/utils/debugHelper.js +64 -1
  103. package/lib/commonjs/utils/encryption.js +76 -7
  104. package/lib/commonjs/utils/eventUtils.js +288 -1
  105. package/lib/commonjs/utils/haptics.js +66 -9
  106. package/lib/commonjs/utils/imagePreloader.js +6 -1
  107. package/lib/commonjs/utils/networkDiagnostics.js +226 -8
  108. package/lib/commonjs/utils/onairosApi.js +350 -9
  109. package/lib/commonjs/utils/programmaticFlow.js +117 -9
  110. package/lib/commonjs/utils/retryHelper.js +220 -1
  111. package/lib/commonjs/utils/secureStorage.js +349 -10
  112. package/lib/commonjs/utils/webviewScripts/chatgpt.js +551 -1
  113. package/lib/commonjs/utils/webviewScripts/claude.js +376 -1
  114. package/lib/commonjs/utils/webviewScripts/hinge.js +411 -1
  115. package/lib/commonjs/utils/webviewScripts/index.js +698 -15
  116. package/lib/commonjs/utils/webviewScripts/instagram.js +454 -1
  117. package/lib/commonjs/utils/webviewScripts/linkedin.js +880 -1
  118. package/lib/commonjs/utils/webviewScripts/netflix.js +382 -1
  119. package/lib/commonjs/utils/webviewScripts/sephora.js +516 -1
  120. package/lib/commonjs/utils/webviewScripts/spotify.js +419 -1
  121. package/lib/commonjs/utils/webviewScripts/telegram.js +678 -1
  122. package/lib/module/api/index.js +211 -1
  123. package/lib/module/components/BodyText.js +20 -1
  124. package/lib/module/components/BrandMark.js +104 -1
  125. package/lib/module/components/CodeInput.js +109 -1
  126. package/lib/module/components/EmailInput.js +23 -1
  127. package/lib/module/components/GoogleButton.js +49 -1
  128. package/lib/module/components/HeadingGroup.js +36 -1
  129. package/lib/module/components/LLMDataInputModal.js +656 -7
  130. package/lib/module/components/ModalHeader.js +92 -1
  131. package/lib/module/components/ModalSheet.js +39 -1
  132. package/lib/module/components/Onairos.js +373 -1
  133. package/lib/module/components/OnairosButton.js +305 -1
  134. package/lib/module/components/OnairosSignInButton.js +121 -1
  135. package/lib/module/components/Overlay.js +456 -1
  136. package/lib/module/components/PersonaImage.js +129 -1
  137. package/lib/module/components/PersonaLoadingScreen.js +310 -1
  138. package/lib/module/components/PersonalizationConsentScreen.js +460 -1
  139. package/lib/module/components/PinCreationScreen.js +396 -1
  140. package/lib/module/components/PinInput.js +456 -1
  141. package/lib/module/components/PlatformConnectorsStep.js +1302 -6
  142. package/lib/module/components/PlatformList.js +129 -1
  143. package/lib/module/components/PlatformToggle.js +173 -1
  144. package/lib/module/components/PrimaryButton.js +172 -1
  145. package/lib/module/components/SignInMatchAnimation.js +189 -1
  146. package/lib/module/components/SignInStep.js +338 -1
  147. package/lib/module/components/UniversalOnboarding.js +2770 -1
  148. package/lib/module/components/VerificationStep.js +168 -1
  149. package/lib/module/components/WelcomeScreen.js +453 -1
  150. package/lib/module/components/icons/Basicproficon.js +30 -1
  151. package/lib/module/components/icons/Basicprofile.js +14 -1
  152. package/lib/module/components/icons/Checkbox.js +14 -1
  153. package/lib/module/components/icons/Checkmark.js +20 -1
  154. package/lib/module/components/icons/Contentanalysis.js +14 -1
  155. package/lib/module/components/icons/Contenticon.js +32 -1
  156. package/lib/module/components/icons/EnochE.js +34 -1
  157. package/lib/module/components/icons/Personalityicon.js +23 -1
  158. package/lib/module/components/icons/Personalityprofile.js +14 -1
  159. package/lib/module/components/icons/Personalitytraits.js +14 -1
  160. package/lib/module/components/icons/Userpreferences.js +14 -1
  161. package/lib/module/components/icons/index.js +13 -1
  162. package/lib/module/components/onboarding/OAuthWebView.js +1746 -1
  163. package/lib/module/components/onboarding/OnboardingHeader.js +66 -1
  164. package/lib/module/components/onboarding/PinInput.js +274 -1
  165. package/lib/module/components/onboarding/PlatformConnector.js +240 -1
  166. package/lib/module/config/PLATFORM_APIS.md +849 -0
  167. package/lib/module/config/api.js +47 -1
  168. package/lib/module/constants/index.js +114 -1
  169. package/lib/module/context/AuthContext.js +335 -1
  170. package/lib/module/hooks/useConnectedAccounts.js +106 -1
  171. package/lib/module/hooks/useConnections.js +95 -1
  172. package/lib/module/hooks/useCredentials.js +171 -6
  173. package/lib/module/hooks/useUserConnections.js +140 -1
  174. package/lib/module/index.js +172 -1
  175. package/lib/module/services/apiClient.js +295 -1
  176. package/lib/module/services/biometricPinService.js +169 -1
  177. package/lib/module/services/chatGPTConversationExtractor.js +149 -1
  178. package/lib/module/services/chatGPTConversationService.js +268 -1
  179. package/lib/module/services/claudeConversationExtractor.js +97 -1
  180. package/lib/module/services/claudeConversationService.js +151 -1
  181. package/lib/module/services/connectedAccountsService.js +293 -1
  182. package/lib/module/services/googleAuthService.js +241 -1
  183. package/lib/module/services/hingeDataExtractor.js +99 -1
  184. package/lib/module/services/hingeDataService.js +143 -1
  185. package/lib/module/services/imageCompressionService.js +250 -1
  186. package/lib/module/services/instagramDataExtractor.js +120 -1
  187. package/lib/module/services/instagramDataService.js +156 -1
  188. package/lib/module/services/jwtStorageService.js +257 -1
  189. package/lib/module/services/linkedinDOMExtractor.js +234 -1
  190. package/lib/module/services/linkedinProfileService.js +210 -1
  191. package/lib/module/services/linkedinScrapingService.js +219 -1
  192. package/lib/module/services/llmDataStorage.js +277 -1
  193. package/lib/module/services/mobileTrainingService.js +173 -1
  194. package/lib/module/services/netflixDataExtractor.js +114 -1
  195. package/lib/module/services/netflixDataService.js +191 -1
  196. package/lib/module/services/pinEncryptionService.js +74 -6
  197. package/lib/module/services/pinStorageUtils.js +93 -1
  198. package/lib/module/services/platformAuthService.js +1461 -1
  199. package/lib/module/services/sephoraDataExtractor.js +134 -1
  200. package/lib/module/services/sephoraDataService.js +193 -1
  201. package/lib/module/services/spotifyDataExtractor.js +142 -1
  202. package/lib/module/services/spotifyDataService.js +234 -1
  203. package/lib/module/services/storageService.js +383 -1
  204. package/lib/module/services/telegramDataExtractor.js +109 -1
  205. package/lib/module/services/telegramDataService.js +493 -1
  206. package/lib/module/services/trainingApiHelpers.js +67 -1
  207. package/lib/module/services/userConnectionsService.js +329 -1
  208. package/lib/module/services/youtubeMigrationService.js +405 -1
  209. package/lib/module/theme/index.js +245 -1
  210. package/lib/module/types.js +10 -1
  211. package/lib/module/utils/Portal.js +90 -1
  212. package/lib/module/utils/api.js +118 -1
  213. package/lib/module/utils/assetRegistry.js +200 -34
  214. package/lib/module/utils/auth.js +100 -1
  215. package/lib/module/utils/connectorTests.js +600 -27
  216. package/lib/module/utils/crypto.js +54 -1
  217. package/lib/module/utils/debugHelper.js +54 -1
  218. package/lib/module/utils/encryption.js +67 -1
  219. package/lib/module/utils/eventUtils.js +270 -1
  220. package/lib/module/utils/haptics.js +59 -8
  221. package/lib/module/utils/imagePreloader.js +3 -1
  222. package/lib/module/utils/networkDiagnostics.js +217 -1
  223. package/lib/module/utils/onairosApi.js +333 -1
  224. package/lib/module/utils/programmaticFlow.js +111 -1
  225. package/lib/module/utils/retryHelper.js +211 -1
  226. package/lib/module/utils/secureStorage.js +330 -6
  227. package/lib/module/utils/webviewScripts/chatgpt.js +545 -1
  228. package/lib/module/utils/webviewScripts/claude.js +370 -1
  229. package/lib/module/utils/webviewScripts/hinge.js +405 -1
  230. package/lib/module/utils/webviewScripts/index.js +434 -1
  231. package/lib/module/utils/webviewScripts/instagram.js +448 -1
  232. package/lib/module/utils/webviewScripts/linkedin.js +874 -1
  233. package/lib/module/utils/webviewScripts/netflix.js +376 -1
  234. package/lib/module/utils/webviewScripts/sephora.js +510 -1
  235. package/lib/module/utils/webviewScripts/spotify.js +413 -1
  236. package/lib/module/utils/webviewScripts/telegram.js +672 -1
  237. package/package.json +2 -2
@@ -0,0 +1,849 @@
1
+ # Platform Connector APIs Reference
2
+
3
+ Quick reference for all platform data sources, APIs, authentication, and token management.
4
+
5
+ ---
6
+
7
+ ## Sephora
8
+
9
+ **Platform ID:** `mobile-sephora`
10
+ **Data Type:** E-commerce / Beauty
11
+
12
+ ### Authentication
13
+
14
+ | Key | Storage | Format |
15
+ |-----|---------|--------|
16
+ | `seph-access-token` | localStorage | JSON: `{ data: "JWT...", expiry: "timestamp" }` |
17
+ | `x-api-key` | Header | `nQc7BFt78yJBvfYDKtle9APd5RrX984i` |
18
+
19
+ **Token Expiry:** ~150 days (expiry is Unix timestamp in seconds)
20
+
21
+ ### APIs
22
+
23
+ | API | Endpoint | Method | Data Retrieved |
24
+ |-----|----------|--------|----------------|
25
+ | **Profile** | `/gapi/users/profiles/{profileId}/current/full` | GET | Name, email, VIB status, Beauty Insider points, skin/hair/makeup preferences, preferred store |
26
+ | **Basket** | `/api/shopping-cart/basket` | GET | Cart items, subtotal, BI points, shipping info |
27
+ | **Loves** | `/gway/v1/dotcom/users/profiles/{profileId}/lists/skus/all` | GET | Wishlist products with ratings, categories, prices, images |
28
+ | **Purchases** | `/api/bi/profiles/{profileId}/purchases` | GET | Purchase history with products, dates, order IDs |
29
+
30
+ ### Profile API Params
31
+ ```
32
+ ?skipApis=targetersResult
33
+ &includeApis=profile,basket,loves,shoppingList,segments
34
+ ```
35
+
36
+ ### Loves API Params
37
+ ```
38
+ ?itemsPerPage=100
39
+ &currentPage=1
40
+ &listShortNameLength=20
41
+ &skipProductDetails=false
42
+ &includeInactiveSkus=true
43
+ &fetchAllLovesList=true
44
+ &sortBy=recently
45
+ &includeCategories=true
46
+ ```
47
+
48
+ ### Purchases API Params
49
+ ```
50
+ ?sortBy=recently
51
+ &itemsPerPage=100
52
+ &groupBy=none
53
+ &excludeSamples=true
54
+ &excludeRewards=true
55
+ ```
56
+
57
+ ### Data Extracted
58
+
59
+ **Profile:**
60
+ - `profileId`, `firstName`, `lastName`, `email`
61
+ - `vibStatus` (BI, VIB, Rouge)
62
+ - Beauty Insider: points, segment, spending, birthday gift eligibility
63
+ - Preferences: skin concerns, hair concerns, makeup preferences
64
+ - Preferred store info
65
+
66
+ **Basket:**
67
+ - Items with SKU ID, product ID, name, brand, price, quantity, size, image
68
+
69
+ **Loves:**
70
+ - Products with ratings, reviews, categories, stock status, sale info
71
+
72
+ **Purchases:**
73
+ - Order history with product details, dates, quantities
74
+
75
+ ### Token Extraction
76
+
77
+ ```javascript
78
+ function getSephoraToken() {
79
+ var tokenKeys = ['seph-access-token', 'sephoraAccessToken', 'accessToken'];
80
+
81
+ // Try localStorage
82
+ for (var i = 0; i < tokenKeys.length; i++) {
83
+ var token = localStorage.getItem(tokenKeys[i]);
84
+ if (token) {
85
+ try {
86
+ var parsed = JSON.parse(token);
87
+ if (parsed && parsed.data) return parsed.data;
88
+ } catch (e) {
89
+ return token; // Not JSON, use as-is
90
+ }
91
+ }
92
+ }
93
+
94
+ // Try cookies
95
+ var cookieMatch = document.cookie.match(/seph-access-token=([^;]+)/);
96
+ if (cookieMatch) return cookieMatch[1];
97
+
98
+ // Try window state
99
+ if (window.__PRELOADED_STATE__ && window.__PRELOADED_STATE__.user) {
100
+ return window.__PRELOADED_STATE__.user.accessToken;
101
+ }
102
+
103
+ return null;
104
+ }
105
+ ```
106
+
107
+ ### Profile ID Extraction
108
+
109
+ The `profileId` is needed for most Sephora APIs. Extract from:
110
+
111
+ ```javascript
112
+ function getProfileId() {
113
+ // Method 1: From token (JWT payload)
114
+ var token = getSephoraToken();
115
+ if (token) {
116
+ try {
117
+ var payload = JSON.parse(atob(token.split('.')[1]));
118
+ if (payload.sub) return payload.sub;
119
+ } catch (e) {}
120
+ }
121
+
122
+ // Method 2: From localStorage
123
+ var profileId = localStorage.getItem('biAccountId') ||
124
+ localStorage.getItem('profileId');
125
+ if (profileId) return profileId;
126
+
127
+ // Method 3: From window state
128
+ if (window.__PRELOADED_STATE__ && window.__PRELOADED_STATE__.user) {
129
+ return window.__PRELOADED_STATE__.user.profileId;
130
+ }
131
+
132
+ // Method 4: From basket API (doesn't require profileId)
133
+ // Call /api/shopping-cart/basket first, extract profileId from response
134
+
135
+ return null;
136
+ }
137
+ ```
138
+
139
+ ### Token Expiry Check
140
+
141
+ ```javascript
142
+ function isTokenExpired() {
143
+ var tokenData = localStorage.getItem('seph-access-token');
144
+ if (!tokenData) return true;
145
+
146
+ try {
147
+ var parsed = JSON.parse(tokenData);
148
+ var expiry = parseInt(parsed.expiry, 10);
149
+ var now = Math.floor(Date.now() / 1000);
150
+ return now >= expiry;
151
+ } catch (e) {
152
+ return true;
153
+ }
154
+ }
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Instagram
160
+
161
+ **Platform ID:** `mobile-instagram` (data connector) / `instagram` (OAuth)
162
+ **Data Type:** Social
163
+
164
+ ### Authentication
165
+
166
+ | Key | Storage | Notes |
167
+ |-----|---------|-------|
168
+ | `csrftoken` | Cookie | CSRF token for POST requests |
169
+ | `sessionid` | Cookie | Session authentication |
170
+ | `X-IG-App-ID` | Header | `936619743392459` |
171
+ | `__bkv` | URL param | Bloks version: `cc4d2103131ee3bbc02c20a86f633b7fb7a031cbf515d12d81e0c8ae7af305dd` |
172
+
173
+ **Token Expiry:** Session-based (tied to browser session cookie)
174
+
175
+ ### APIs
176
+
177
+ | API | Endpoint | Method | Data Retrieved |
178
+ |-----|----------|--------|----------------|
179
+ | **Liked Media (Bloks)** | `/async/wbloks/fetch/` | POST | Liked posts via Bloks framework |
180
+ | **Saved Posts** | `/api/v1/feed/saved/posts/` | GET | Saved posts with media, captions, owner info |
181
+ | **Timeline (GraphQL)** | `/graphql/query` | POST | Feed timeline with media, captions, engagement |
182
+
183
+ ### Bloks API (Liked Media)
184
+
185
+ **Endpoint:**
186
+ ```
187
+ POST https://www.instagram.com/async/wbloks/fetch/?appid=com.instagram.privacy.activity_center.liked_media_screen&type=app&__bkv={bloksVersionId}
188
+ ```
189
+
190
+ **Request Body:** `application/x-www-form-urlencoded`
191
+ ```
192
+ params=%7B%7D
193
+ ```
194
+
195
+ **Response Format:**
196
+ ```javascript
197
+ for (;;);{"__ar":1,"rid":"...","payload":{"layout":{"bloks_payload":{"data":[
198
+ {"id":"440463611_0","type":"gs","data":{...}},
199
+ ...
200
+ ]}}}}
201
+ ```
202
+
203
+ **Note:** Response has `for (;;);` prefix (anti-JSON hijacking) that must be stripped before JSON.parse()
204
+
205
+ **Parsing the Bloks payload:**
206
+ ```javascript
207
+ function parseLikedMedia(data) {
208
+ var likes = [];
209
+ if (data && data.payload && data.payload.layout && data.payload.layout.bloks_payload) {
210
+ var bloksData = data.payload.layout.bloks_payload.data || [];
211
+ bloksData.forEach(function(item, idx) {
212
+ // Bloks items have varying structures
213
+ if (item.type === 'media' || item.type === 'ig' || item.data) {
214
+ likes.push({
215
+ id: item.id || 'like_' + idx,
216
+ type: 'like',
217
+ rawData: item.data,
218
+ bloksType: item.type
219
+ });
220
+ }
221
+ });
222
+ }
223
+ return likes;
224
+ }
225
+ ```
226
+
227
+ **⚠️ Bloks Caveat:** The Bloks payload structure is complex and may vary. The `data` array contains UI component definitions, not clean media objects. You may need to recursively parse to extract actual media IDs.
228
+
229
+ ### Saved Posts API
230
+
231
+ **Endpoint:**
232
+ ```
233
+ GET https://www.instagram.com/api/v1/feed/saved/posts/
234
+ ```
235
+
236
+ **Response Format:**
237
+ ```javascript
238
+ {
239
+ "items": [
240
+ {
241
+ "media": {
242
+ "id": "...",
243
+ "pk": "...",
244
+ "code": "...",
245
+ "media_type": 1,
246
+ "caption": { "text": "..." },
247
+ "user": { "pk": "...", "username": "..." },
248
+ "like_count": 123,
249
+ "comment_count": 45,
250
+ "taken_at": 1706123456,
251
+ "image_versions2": { "candidates": [{ "url": "..." }] }
252
+ }
253
+ }
254
+ ],
255
+ "more_available": true,
256
+ "next_max_id": "..."
257
+ }
258
+ ```
259
+
260
+ ### GraphQL API (Timeline)
261
+
262
+ **Endpoint:**
263
+ ```
264
+ POST https://www.instagram.com/graphql/query
265
+ ```
266
+
267
+ **Request Body:** `application/x-www-form-urlencoded`
268
+ ```
269
+ doc_id=8845758582119845&variables={"first":12,"after":null}
270
+ ```
271
+
272
+ **Response Format:**
273
+ ```javascript
274
+ {
275
+ "data": {
276
+ "xdt_api__v1__feed__timeline__connection": {
277
+ "pagination_source": null,
278
+ "edges": [
279
+ {
280
+ "node": {
281
+ "media": {
282
+ "id": "3820598654428293407_10139962",
283
+ "pk": "3820598654428293407",
284
+ "code": "...",
285
+ "media_type": 1,
286
+ "caption": { "text": "..." },
287
+ "user": { "pk": "...", "username": "..." },
288
+ "like_count": 123,
289
+ "comment_count": 45,
290
+ "taken_at": 1706123456,
291
+ "image_versions2": { "candidates": [{ "url": "..." }] }
292
+ }
293
+ }
294
+ }
295
+ ]
296
+ }
297
+ }
298
+ }
299
+ ```
300
+
301
+ ### Required Headers
302
+
303
+ ```javascript
304
+ {
305
+ 'Accept': '*/*',
306
+ 'Accept-Language': 'en-US,en;q=0.9',
307
+ 'X-CSRFToken': csrfToken,
308
+ 'X-IG-App-ID': '936619743392459',
309
+ 'X-Requested-With': 'XMLHttpRequest',
310
+ 'X-ASBD-ID': '129477',
311
+ 'Content-Type': 'application/x-www-form-urlencoded'
312
+ }
313
+ ```
314
+
315
+ ### Data Extracted
316
+
317
+ **Liked Media:**
318
+ - Post IDs
319
+ - Bloks payload data (varies by content type)
320
+
321
+ **Saved Posts:**
322
+ - `id`, `pk`, `code` (shortcode for URL)
323
+ - `mediaType` (1=image, 2=video, 8=carousel)
324
+ - `caption` text
325
+ - `owner` (user info: id, username, full name)
326
+ - `likeCount`, `commentCount`
327
+ - `takenAt` (Unix timestamp)
328
+ - `imageUrl` (best quality candidate)
329
+
330
+ **Timeline:**
331
+ - `id`, `pk`, `code` (shortcode for URL)
332
+ - `mediaType` (1=image, 2=video, 8=carousel)
333
+ - `caption` text
334
+ - `owner` (user info: id, username, full name)
335
+ - `likeCount`, `commentCount`
336
+ - `takenAt` (Unix timestamp)
337
+ - `imageUrl` (best quality candidate)
338
+
339
+ ### User ID Extraction
340
+
341
+ Instagram requires the user's ID for some operations. Extract from page state:
342
+
343
+ ```javascript
344
+ function getUserId() {
345
+ // Method 1: window._sharedData (older pages)
346
+ if (window._sharedData && window._sharedData.config) {
347
+ return window._sharedData.config.viewerId;
348
+ }
349
+
350
+ // Method 2: window.__initialData (newer pages)
351
+ if (window.__initialData && window.__initialData.data && window.__initialData.data.user) {
352
+ return window.__initialData.data.user.id;
353
+ }
354
+
355
+ // Method 3: Script tag parsing (fallback)
356
+ var scripts = document.querySelectorAll('script');
357
+ for (var i = 0; i < scripts.length; i++) {
358
+ var text = scripts[i].textContent || '';
359
+ var match = text.match(/"userId":"(\d+)"/);
360
+ if (match) return match[1];
361
+ match = text.match(/"viewerId":"(\d+)"/);
362
+ if (match) return match[1];
363
+ }
364
+
365
+ return null;
366
+ }
367
+ ```
368
+
369
+ ### Bloks Version ID
370
+
371
+ The `__bkv` parameter is a hash that may change with Instagram updates.
372
+
373
+ **Current value:** `cc4d2103131ee3bbc02c20a86f633b7fb7a031cbf515d12d81e0c8ae7af305dd`
374
+
375
+ **To find current value:**
376
+ 1. Open Instagram DevTools → Network tab
377
+ 2. Navigate to Your Activity → Likes
378
+ 3. Look for requests to `/async/wbloks/fetch/`
379
+ 4. Copy `__bkv` value from URL
380
+
381
+ ### Pagination
382
+
383
+ **Saved Posts:**
384
+ ```javascript
385
+ // First request
386
+ GET /api/v1/feed/saved/posts/
387
+
388
+ // Subsequent requests (if more_available: true)
389
+ GET /api/v1/feed/saved/posts/?max_id={next_max_id}
390
+ ```
391
+
392
+ **GraphQL Timeline:**
393
+ ```javascript
394
+ // First request
395
+ doc_id=8845758582119845&variables={"first":12,"after":null}
396
+
397
+ // Subsequent requests
398
+ doc_id=8845758582119845&variables={"first":12,"after":"{end_cursor}"}
399
+ ```
400
+
401
+ The `end_cursor` comes from `pageInfo.end_cursor` in the response.
402
+
403
+ ### Cookie Extraction Helper
404
+
405
+ ```javascript
406
+ function getCookie(name) {
407
+ var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
408
+ return match ? match[2] : null;
409
+ }
410
+
411
+ // Usage
412
+ var csrfToken = getCookie('csrftoken');
413
+ var sessionId = getCookie('sessionid');
414
+ ```
415
+
416
+ ---
417
+
418
+ ## Hinge
419
+
420
+ **Platform ID:** `mobile-hinge`
421
+ **Data Type:** Dating
422
+
423
+ ### Authentication
424
+
425
+ | Key | Storage | Notes |
426
+ |-----|---------|-------|
427
+ | `auth_token` | Cookie/localStorage | TBD - need to confirm |
428
+ | `hinge_auth_token` | Cookie | Fallback |
429
+
430
+ ### APIs
431
+
432
+ | API | Endpoint | Data Retrieved |
433
+ |-----|----------|----------------|
434
+ | TBD | TBD | Matches, messages |
435
+
436
+ ### Data Extracted
437
+ - Matches (profiles)
438
+ - Messages/chat history
439
+
440
+ ---
441
+
442
+ ---
443
+
444
+ ## Netflix
445
+
446
+ **Platform ID:** `mobile-netflix`
447
+ **Data Type:** Streaming / Entertainment
448
+
449
+ ### Authentication
450
+
451
+ | Key | Storage | Format | Notes |
452
+ |-----|---------|--------|-------|
453
+ | `NetflixId` | Cookie | Session cookie | Main authentication |
454
+ | `SecureNetflixId` | Cookie | Secure session cookie | HTTPS only |
455
+ | `profilesToken` | localStorage/Cookie | JWT-like | Multi-profile auth |
456
+
457
+ **Token Expiry:** Session-based (~30 days active session, refreshes on activity)
458
+
459
+ > ⚠️ **DISCOVERY REQUIRED**: Use `tests/netflix-webview-test.html` to discover exact token locations and API endpoints.
460
+
461
+ ### APIs (To Be Discovered)
462
+
463
+ | API | Expected Endpoint | Method | Data Retrieved |
464
+ |-----|-------------------|--------|----------------|
465
+ | **Viewing History** | `/api/viewing-history` or `/shakti/*` | GET | Watch history with timestamps |
466
+ | **My List** | `/api/mylist` or `/lolomo/*` | GET | Saved titles queue |
467
+ | **Continue Watching** | `/api/continueWatching` | GET | Partially watched content |
468
+ | **Ratings** | `/api/ratings` or `/thumbs` | GET | Thumbs up/down ratings |
469
+ | **Profile** | `/api/profiles` | GET | Profile preferences |
470
+
471
+ ### Known Netflix Patterns
472
+
473
+ 1. **Shakti API** - Netflix's internal API framework
474
+ - Base: `https://www.netflix.com/nq/website/memberapi/{version}/`
475
+ - Requires `BUILD_IDENTIFIER` from page source
476
+
477
+ 2. **pathEvaluator** - GraphQL-like data fetching
478
+ - Used for homepage/browse data
479
+ - May contain viewing activity
480
+
481
+ 3. **Multi-Profile** - Netflix supports multiple profiles per account
482
+ - Current profile context needed for API calls
483
+
484
+ ### Token Extraction (Placeholder)
485
+
486
+ ```javascript
487
+ function getNetflixToken() {
488
+ // Method 1: Cookies
489
+ var netflixId = document.cookie.match(/NetflixId=([^;]+)/);
490
+ if (netflixId) return { token: netflixId[1], type: 'cookie' };
491
+
492
+ // Method 2: localStorage
493
+ var tokenKeys = ['netflix_token', 'profilesToken', 'authToken'];
494
+ for (var i = 0; i < tokenKeys.length; i++) {
495
+ var token = localStorage.getItem(tokenKeys[i]);
496
+ if (token) return { token: token, type: 'localStorage' };
497
+ }
498
+
499
+ // Method 3: Window state (TODO: discover Netflix state object)
500
+ // if (window.__NETFLIX_STATE__) return ...
501
+
502
+ return null;
503
+ }
504
+ ```
505
+
506
+ ### Data Extracted
507
+
508
+ **Viewing History:**
509
+ - `videoId` - Netflix content ID
510
+ - `title` - Show/movie name
511
+ - `type` - movie/series/episode
512
+ - `seriesTitle`, `seasonNumber`, `episodeNumber` (for series)
513
+ - `watchedAt` - Timestamp
514
+ - `watchDuration`, `totalDuration`, `percentWatched`
515
+
516
+ **My List:**
517
+ - `videoId`, `title`, `type`
518
+ - `addedAt` - When added to list
519
+ - `genres`, `maturityRating`, `year`
520
+
521
+ **Ratings:**
522
+ - `videoId`, `title`
523
+ - `rating` - thumbs_up/thumbs_down/love
524
+ - `ratedAt` - When rated
525
+
526
+ ---
527
+
528
+ ## Spotify
529
+
530
+ **Platform ID:** `mobile-spotify`
531
+ **Data Type:** Streaming / Music
532
+
533
+ ### Authentication
534
+
535
+ | Key | Storage | Format | Notes |
536
+ |-----|---------|--------|-------|
537
+ | `sp_dc` | Cookie | Device credentials | Long-lived session cookie |
538
+ | `sp_t` | Cookie | Spotify token | Short-lived token |
539
+ | `accessToken` | Memory/localStorage | Bearer token | ~1 hour expiry |
540
+
541
+ **Token Expiry:** Access token ~1 hour, sp_dc cookie ~1 year
542
+
543
+ > ⚠️ **DISCOVERY REQUIRED**: Use `tests/spotify-webview-test.html` to discover exact token locations.
544
+
545
+ ### APIs (Standard Spotify Web API)
546
+
547
+ | API | Endpoint | Method | Data Retrieved |
548
+ |-----|----------|--------|----------------|
549
+ | **Profile** | `/v1/me` | GET | User profile, subscription, country |
550
+ | **Recently Played** | `/v1/me/player/recently-played` | GET | Last 50 played tracks |
551
+ | **Top Tracks** | `/v1/me/top/tracks` | GET | User's most played tracks |
552
+ | **Top Artists** | `/v1/me/top/artists` | GET | User's most played artists |
553
+ | **Playlists** | `/v1/me/playlists` | GET | User's playlists |
554
+ | **Saved Tracks** | `/v1/me/tracks` | GET | Liked Songs library |
555
+ | **Saved Albums** | `/v1/me/albums` | GET | Saved albums |
556
+
557
+ **API Base URL:** `https://api.spotify.com`
558
+
559
+ **Alternative (Web Player Internal):**
560
+ - `https://spclient.wg.spotify.com/` - Spotify internal client
561
+ - `https://api-partner.spotify.com/` - Partner API
562
+
563
+ ### Request Headers
564
+
565
+ ```javascript
566
+ {
567
+ 'Authorization': 'Bearer ' + accessToken,
568
+ 'Accept': 'application/json',
569
+ 'Content-Type': 'application/json'
570
+ }
571
+ ```
572
+
573
+ ### API Query Parameters
574
+
575
+ **Top Items:**
576
+ ```
577
+ ?time_range=medium_term&limit=50
578
+ ```
579
+ - `time_range`: `short_term` (4 weeks), `medium_term` (6 months), `long_term` (years)
580
+ - `limit`: 1-50
581
+
582
+ **Recently Played:**
583
+ ```
584
+ ?limit=50&before={unix_timestamp_ms}
585
+ ```
586
+
587
+ **Playlists/Saved:**
588
+ ```
589
+ ?limit=50&offset=0
590
+ ```
591
+
592
+ ### Token Extraction (Placeholder)
593
+
594
+ ```javascript
595
+ function getSpotifyToken() {
596
+ // Method 1: localStorage (most likely)
597
+ var tokenKeys = ['spotify_access_token', 'accessToken', 'sp_access_token'];
598
+ for (var i = 0; i < tokenKeys.length; i++) {
599
+ var token = localStorage.getItem(tokenKeys[i]);
600
+ if (token) {
601
+ try {
602
+ var parsed = JSON.parse(token);
603
+ return { token: parsed.accessToken || parsed.token, expiry: parsed.expiresAt };
604
+ } catch (e) {
605
+ if (token.startsWith('BQ')) return { token: token, expiry: null };
606
+ }
607
+ }
608
+ }
609
+
610
+ // Method 2: Intercept from network requests
611
+ // The web player makes API calls with Authorization header
612
+
613
+ // Method 3: Window state
614
+ // Spotify web player may store token in React state
615
+
616
+ return null;
617
+ }
618
+ ```
619
+
620
+ ### Data Extracted
621
+
622
+ **Recently Played:**
623
+ - `trackId`, `name`, `artists[]`, `album`
624
+ - `playedAt` - ISO timestamp
625
+ - `duration` - Track length in ms
626
+ - `imageUrl`, `previewUrl`
627
+
628
+ **Top Items:**
629
+ - Tracks: `trackId`, `name`, `artists[]`, `popularity`
630
+ - Artists: `artistId`, `name`, `genres[]`, `followers`
631
+
632
+ **Playlists:**
633
+ - `playlistId`, `name`, `description`
634
+ - `isPublic`, `isCollaborative`
635
+ - `trackCount`, `owner`
636
+
637
+ **Saved Tracks/Albums:**
638
+ - Track/Album details
639
+ - `savedAt` - When saved
640
+
641
+ ### Rate Limits
642
+
643
+ | Endpoint | Rate Limit | Notes |
644
+ |----------|------------|-------|
645
+ | Most endpoints | ~100 req/min | Per user token |
646
+ | Search | ~30 req/min | Stricter |
647
+
648
+ ### Pagination
649
+
650
+ ```javascript
651
+ // Response includes
652
+ {
653
+ "items": [...],
654
+ "next": "https://api.spotify.com/v1/me/tracks?offset=50&limit=50",
655
+ "total": 500,
656
+ "limit": 50,
657
+ "offset": 0
658
+ }
659
+
660
+ // Use next URL or manually paginate with offset
661
+ ```
662
+
663
+ ---
664
+
665
+ ## Token Expiry Reference
666
+
667
+ | Platform | Token Lifetime | Storage Location | Refresh Mechanism | How to Check Expiry |
668
+ |----------|---------------|------------------|-------------------|---------------------|
669
+ | **Sephora** | ~150 days | localStorage `seph-access-token` | Auto-refresh on site visit | Check `expiry` field in JSON (Unix timestamp) |
670
+ | **Instagram** | Session-based (~24h-7d) | Cookie `sessionid` | New session on login | No direct field; 401 = expired |
671
+ | **ChatGPT** | ~14 days | Cookie `__Secure-next-auth.session-token` | Auto-refresh on activity | JWT `exp` claim |
672
+ | **Claude** | Session-based | Cookie/localStorage | Session refresh | Check for 401 response |
673
+ | **Telegram** | Session-based | localStorage various keys | WebSocket reconnect | Check connection state |
674
+ | **Hinge** | TBD | TBD | TBD | TBD |
675
+ | **Netflix** | ~30 days session | Cookie `NetflixId` | Session cookie refresh | 401/403 response |
676
+ | **Spotify** | ~1 hour (access token) | Memory/localStorage | Refresh token endpoint | `expires_in` field or 401 |
677
+
678
+ ### Token Refresh Patterns
679
+
680
+ | Platform | Refresh Pattern | Implementation |
681
+ |----------|-----------------|----------------|
682
+ | **Sephora** | Passive (site visit) | Token auto-updates in localStorage on any page load |
683
+ | **Instagram** | Re-authentication | User must log in again when session expires |
684
+ | **ChatGPT** | Background refresh | Next.js session auto-refreshes; re-login if fully expired |
685
+ | **Spotify** | Active refresh | Call `/api/token` with refresh_token (OAuth 2.0 standard) |
686
+ | **Netflix** | Cookie refresh | Session cookies refresh on any Netflix activity |
687
+
688
+ ### Detecting Expired Tokens
689
+
690
+ ```javascript
691
+ // Universal expiry detection
692
+ async function checkTokenValidity(platform, token) {
693
+ try {
694
+ // Make a lightweight API call
695
+ var response = await fetch(getTestEndpoint(platform), {
696
+ headers: { 'Authorization': 'Bearer ' + token }
697
+ });
698
+
699
+ if (response.status === 401 || response.status === 403) {
700
+ return { valid: false, reason: 'Token expired or revoked' };
701
+ }
702
+
703
+ return { valid: true };
704
+ } catch (e) {
705
+ return { valid: false, reason: e.message };
706
+ }
707
+ }
708
+
709
+ function getTestEndpoint(platform) {
710
+ switch(platform) {
711
+ case 'spotify': return 'https://api.spotify.com/v1/me';
712
+ case 'instagram': return 'https://www.instagram.com/api/v1/users/web_profile_info/';
713
+ // Add others as needed
714
+ }
715
+ }
716
+ ```
717
+
718
+ ### JWT Token Expiry Check
719
+
720
+ ```javascript
721
+ function getJWTExpiry(token) {
722
+ try {
723
+ var payload = JSON.parse(atob(token.split('.')[1]));
724
+ if (payload.exp) {
725
+ var expiryDate = new Date(payload.exp * 1000);
726
+ var now = new Date();
727
+ return {
728
+ expiresAt: expiryDate.toISOString(),
729
+ isExpired: now > expiryDate,
730
+ minutesRemaining: Math.floor((expiryDate - now) / 60000)
731
+ };
732
+ }
733
+ } catch (e) {
734
+ return { error: 'Not a JWT or invalid format' };
735
+ }
736
+ return { error: 'No exp claim found' };
737
+ }
738
+ ```
739
+
740
+ ---
741
+
742
+ ## Common Request Headers
743
+
744
+ All platforms require:
745
+ ```javascript
746
+ {
747
+ 'Accept': 'application/json',
748
+ 'Content-Type': 'application/json',
749
+ 'credentials': 'include' // for cookies
750
+ }
751
+ ```
752
+
753
+ Platform-specific headers are added per connector.
754
+
755
+ ---
756
+
757
+ ## Onairos Backend Endpoint
758
+
759
+ All extracted data is sent to:
760
+ ```
761
+ POST https://api2.onairos.uk/platform-data/store
762
+ ```
763
+
764
+ With headers:
765
+ ```javascript
766
+ {
767
+ 'Content-Type': 'application/json',
768
+ 'Authorization': 'Bearer <onairos_user_token>'
769
+ }
770
+ ```
771
+
772
+ Payload structure:
773
+ ```javascript
774
+ {
775
+ platform: 'mobile-sephora', // platform ID
776
+ dataType: 'ecommerce', // data category
777
+ data: { ... }, // extracted data
778
+ summary: { ... }, // quick stats
779
+ mobileMetadata: {
780
+ platform: 'web',
781
+ source: 'bookmarklet-api',
782
+ extractedAt: 'ISO timestamp',
783
+ tokenExpiry: { ... } // token expiry info
784
+ }
785
+ }
786
+ ```
787
+
788
+ ---
789
+
790
+ ## Rate Limits & Best Practices
791
+
792
+ ### General Guidelines
793
+
794
+ | Platform | Rate Limit | Recommendation |
795
+ |----------|------------|----------------|
796
+ | **Sephora** | Unknown (generous) | Max 10 requests per extraction |
797
+ | **Instagram** | Strict (anti-bot) | Max 3-5 requests, add delays |
798
+ | **Hinge** | TBD | TBD |
799
+
800
+ ### Implementation Tips
801
+
802
+ 1. **Parallel requests** - Use `Promise.all()` for independent API calls
803
+ 2. **Error handling** - Gracefully handle 401/403 (re-auth needed), 429 (rate limited)
804
+ 3. **Credentials** - Always include `credentials: 'include'` for cookie-based auth
805
+ 4. **CORS** - These APIs work from same-origin (bookmarklet on the site), not cross-origin
806
+
807
+ ### Response Parsing
808
+
809
+ **Instagram Bloks (strip prefix):**
810
+ ```javascript
811
+ function parseBloksResponse(text) {
812
+ if (text.startsWith('for (;;);')) {
813
+ text = text.substring(9);
814
+ }
815
+ return JSON.parse(text);
816
+ }
817
+ ```
818
+
819
+ **Sephora (standard JSON):**
820
+ ```javascript
821
+ var data = await response.json();
822
+ ```
823
+
824
+ ### Error Codes
825
+
826
+ | Code | Meaning | Action |
827
+ |------|---------|--------|
828
+ | 200 | Success | Parse response |
829
+ | 401 | Unauthorized | Token expired, prompt re-login |
830
+ | 403 | Forbidden | Blocked/banned, try later |
831
+ | 429 | Rate Limited | Wait and retry |
832
+ | 500+ | Server Error | Retry with backoff |
833
+
834
+ ---
835
+
836
+ ## Quick Implementation Checklist
837
+
838
+ For each platform connector:
839
+
840
+ - [ ] Verify user is on correct domain
841
+ - [ ] Show consent popup
842
+ - [ ] Extract auth token(s)
843
+ - [ ] Check token validity/expiry
844
+ - [ ] Call APIs with proper headers
845
+ - [ ] Parse responses
846
+ - [ ] Build normalized payload
847
+ - [ ] Send to Onairos backend
848
+ - [ ] Show success/error message
849
+