@onairos/react-native 3.7.2 → 3.7.4
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/lib/commonjs/api/index.js +219 -9
- package/lib/commonjs/components/BodyText.js +27 -9
- package/lib/commonjs/components/BrandMark.js +111 -10
- package/lib/commonjs/components/CodeInput.js +120 -9
- package/lib/commonjs/components/EmailInput.js +30 -8
- package/lib/commonjs/components/GoogleButton.js +56 -9
- package/lib/commonjs/components/HeadingGroup.js +43 -9
- package/lib/commonjs/components/LLMDataInputModal.js +664 -14
- package/lib/commonjs/components/ModalHeader.js +99 -9
- package/lib/commonjs/components/ModalSheet.js +47 -9
- package/lib/commonjs/components/Onairos.js +380 -14
- package/lib/commonjs/components/OnairosButton.js +313 -13
- package/lib/commonjs/components/OnairosSignInButton.js +130 -12
- package/lib/commonjs/components/Overlay.js +465 -13
- package/lib/commonjs/components/PersonaImage.js +137 -10
- package/lib/commonjs/components/PersonaLoadingScreen.js +318 -12
- package/lib/commonjs/components/PersonalizationConsentScreen.js +467 -13
- package/lib/commonjs/components/PinCreationScreen.js +403 -12
- package/lib/commonjs/components/PinInput.js +464 -9
- package/lib/commonjs/components/PlatformConnectorsStep.js +1311 -23
- package/lib/commonjs/components/PlatformList.js +137 -10
- package/lib/commonjs/components/PlatformToggle.js +180 -9
- package/lib/commonjs/components/PrimaryButton.js +180 -10
- package/lib/commonjs/components/SignInMatchAnimation.js +197 -9
- package/lib/commonjs/components/SignInStep.js +345 -12
- package/lib/commonjs/components/UniversalOnboarding.js +2780 -30
- package/lib/commonjs/components/VerificationStep.js +176 -11
- package/lib/commonjs/components/WelcomeScreen.js +461 -22
- package/lib/commonjs/components/icons/Basicproficon.js +37 -8
- package/lib/commonjs/components/icons/Basicprofile.js +21 -8
- package/lib/commonjs/components/icons/Checkbox.js +21 -8
- package/lib/commonjs/components/icons/Checkmark.js +27 -8
- package/lib/commonjs/components/icons/Contentanalysis.js +21 -8
- package/lib/commonjs/components/icons/Contenticon.js +39 -8
- package/lib/commonjs/components/icons/EnochE.js +41 -8
- package/lib/commonjs/components/icons/Personalityicon.js +30 -8
- package/lib/commonjs/components/icons/Personalityprofile.js +21 -8
- package/lib/commonjs/components/icons/Personalitytraits.js +21 -8
- package/lib/commonjs/components/icons/Userpreferences.js +21 -8
- package/lib/commonjs/components/icons/index.js +84 -17
- package/lib/commonjs/components/onboarding/OAuthWebView.js +1754 -18
- package/lib/commonjs/components/onboarding/OnboardingHeader.js +74 -10
- package/lib/commonjs/components/onboarding/PinInput.js +283 -10
- package/lib/commonjs/components/onboarding/PlatformConnector.js +249 -11
- package/lib/commonjs/config/PLATFORM_APIS.md +849 -0
- package/lib/commonjs/config/api.js +56 -7
- package/lib/commonjs/constants/index.js +120 -7
- package/lib/commonjs/context/AuthContext.js +345 -10
- package/lib/commonjs/hooks/useConnectedAccounts.js +111 -9
- package/lib/commonjs/hooks/useConnections.js +102 -8
- package/lib/commonjs/hooks/useCredentials.js +178 -10
- package/lib/commonjs/hooks/useUserConnections.js +148 -10
- package/lib/commonjs/index.js +439 -34
- package/lib/commonjs/services/apiClient.js +298 -8
- package/lib/commonjs/services/biometricPinService.js +180 -8
- package/lib/commonjs/services/chatGPTConversationExtractor.js +155 -8
- package/lib/commonjs/services/chatGPTConversationService.js +275 -9
- package/lib/commonjs/services/claudeConversationExtractor.js +103 -8
- package/lib/commonjs/services/claudeConversationService.js +158 -9
- package/lib/commonjs/services/connectedAccountsService.js +310 -10
- package/lib/commonjs/services/googleAuthService.js +252 -11
- package/lib/commonjs/services/hingeDataExtractor.js +105 -8
- package/lib/commonjs/services/hingeDataService.js +150 -9
- package/lib/commonjs/services/imageCompressionService.js +260 -7
- package/lib/commonjs/services/instagramDataExtractor.js +126 -8
- package/lib/commonjs/services/instagramDataService.js +163 -9
- package/lib/commonjs/services/jwtStorageService.js +276 -7
- package/lib/commonjs/services/linkedinDOMExtractor.js +245 -7
- package/lib/commonjs/services/linkedinProfileService.js +222 -9
- package/lib/commonjs/services/linkedinScrapingService.js +230 -8
- package/lib/commonjs/services/llmDataStorage.js +294 -8
- package/lib/commonjs/services/mobileTrainingService.js +186 -8
- package/lib/commonjs/services/netflixDataExtractor.js +120 -8
- package/lib/commonjs/services/netflixDataService.js +198 -9
- package/lib/commonjs/services/pinEncryptionService.js +84 -8
- package/lib/commonjs/services/pinStorageUtils.js +105 -7
- package/lib/commonjs/services/platformAuthService.js +1484 -12
- package/lib/commonjs/services/sephoraDataExtractor.js +140 -8
- package/lib/commonjs/services/sephoraDataService.js +200 -9
- package/lib/commonjs/services/spotifyDataExtractor.js +148 -8
- package/lib/commonjs/services/spotifyDataService.js +241 -9
- package/lib/commonjs/services/storageService.js +404 -8
- package/lib/commonjs/services/telegramDataExtractor.js +115 -8
- package/lib/commonjs/services/telegramDataService.js +499 -9
- package/lib/commonjs/services/trainingApiHelpers.js +73 -7
- package/lib/commonjs/services/userConnectionsService.js +340 -10
- package/lib/commonjs/services/youtubeMigrationService.js +416 -10
- package/lib/commonjs/theme/index.js +250 -7
- package/lib/commonjs/types/ambient.d.js +2 -1
- package/lib/commonjs/types/declarations.d.js +2 -1
- package/lib/commonjs/types/index.js +6 -1
- package/lib/commonjs/types/node-fix.d.js +2 -1
- package/lib/commonjs/types/node-override.d.js +2 -1
- package/lib/commonjs/types/opacity.d.js +2 -1
- package/lib/commonjs/types.js +14 -1
- package/lib/commonjs/utils/Portal.js +98 -8
- package/lib/commonjs/utils/api.js +130 -9
- package/lib/commonjs/utils/assetRegistry.js +210 -35
- package/lib/commonjs/utils/auth.js +112 -9
- package/lib/commonjs/utils/connectorTests.js +613 -29
- package/lib/commonjs/utils/crypto.js +62 -8
- package/lib/commonjs/utils/debugHelper.js +64 -1
- package/lib/commonjs/utils/encryption.js +76 -7
- package/lib/commonjs/utils/eventUtils.js +288 -1
- package/lib/commonjs/utils/haptics.js +66 -9
- package/lib/commonjs/utils/imagePreloader.js +6 -1
- package/lib/commonjs/utils/networkDiagnostics.js +226 -8
- package/lib/commonjs/utils/onairosApi.js +350 -9
- package/lib/commonjs/utils/programmaticFlow.js +117 -9
- package/lib/commonjs/utils/retryHelper.js +220 -1
- package/lib/commonjs/utils/secureStorage.js +349 -10
- package/lib/commonjs/utils/webviewScripts/chatgpt.js +551 -1
- package/lib/commonjs/utils/webviewScripts/claude.js +376 -1
- package/lib/commonjs/utils/webviewScripts/hinge.js +411 -1
- package/lib/commonjs/utils/webviewScripts/index.js +698 -15
- package/lib/commonjs/utils/webviewScripts/instagram.js +454 -1
- package/lib/commonjs/utils/webviewScripts/linkedin.js +880 -1
- package/lib/commonjs/utils/webviewScripts/netflix.js +382 -1
- package/lib/commonjs/utils/webviewScripts/sephora.js +516 -1
- package/lib/commonjs/utils/webviewScripts/spotify.js +419 -1
- package/lib/commonjs/utils/webviewScripts/telegram.js +678 -1
- package/lib/module/api/index.js +211 -1
- package/lib/module/components/BodyText.js +20 -1
- package/lib/module/components/BrandMark.js +104 -1
- package/lib/module/components/CodeInput.js +113 -1
- package/lib/module/components/EmailInput.js +23 -1
- package/lib/module/components/GoogleButton.js +49 -1
- package/lib/module/components/HeadingGroup.js +36 -1
- package/lib/module/components/LLMDataInputModal.js +656 -7
- package/lib/module/components/ModalHeader.js +92 -1
- package/lib/module/components/ModalSheet.js +39 -1
- package/lib/module/components/Onairos.js +373 -1
- package/lib/module/components/OnairosButton.js +305 -1
- package/lib/module/components/OnairosSignInButton.js +121 -1
- package/lib/module/components/Overlay.js +456 -1
- package/lib/module/components/PersonaImage.js +129 -1
- package/lib/module/components/PersonaLoadingScreen.js +310 -1
- package/lib/module/components/PersonalizationConsentScreen.js +460 -1
- package/lib/module/components/PinCreationScreen.js +396 -1
- package/lib/module/components/PinInput.js +456 -1
- package/lib/module/components/PlatformConnectorsStep.js +1302 -6
- package/lib/module/components/PlatformList.js +129 -1
- package/lib/module/components/PlatformToggle.js +173 -1
- package/lib/module/components/PrimaryButton.js +172 -1
- package/lib/module/components/SignInMatchAnimation.js +189 -1
- package/lib/module/components/SignInStep.js +338 -1
- package/lib/module/components/UniversalOnboarding.js +2770 -1
- package/lib/module/components/VerificationStep.js +168 -1
- package/lib/module/components/WelcomeScreen.js +453 -1
- package/lib/module/components/icons/Basicproficon.js +30 -1
- package/lib/module/components/icons/Basicprofile.js +14 -1
- package/lib/module/components/icons/Checkbox.js +14 -1
- package/lib/module/components/icons/Checkmark.js +20 -1
- package/lib/module/components/icons/Contentanalysis.js +14 -1
- package/lib/module/components/icons/Contenticon.js +32 -1
- package/lib/module/components/icons/EnochE.js +34 -1
- package/lib/module/components/icons/Personalityicon.js +23 -1
- package/lib/module/components/icons/Personalityprofile.js +14 -1
- package/lib/module/components/icons/Personalitytraits.js +14 -1
- package/lib/module/components/icons/Userpreferences.js +14 -1
- package/lib/module/components/icons/index.js +13 -1
- package/lib/module/components/onboarding/OAuthWebView.js +1746 -1
- package/lib/module/components/onboarding/OnboardingHeader.js +66 -1
- package/lib/module/components/onboarding/PinInput.js +274 -1
- package/lib/module/components/onboarding/PlatformConnector.js +240 -1
- package/lib/module/config/PLATFORM_APIS.md +849 -0
- package/lib/module/config/api.js +47 -1
- package/lib/module/constants/index.js +114 -1
- package/lib/module/context/AuthContext.js +335 -1
- package/lib/module/hooks/useConnectedAccounts.js +106 -1
- package/lib/module/hooks/useConnections.js +95 -1
- package/lib/module/hooks/useCredentials.js +171 -6
- package/lib/module/hooks/useUserConnections.js +140 -1
- package/lib/module/index.js +172 -1
- package/lib/module/services/apiClient.js +295 -1
- package/lib/module/services/biometricPinService.js +169 -1
- package/lib/module/services/chatGPTConversationExtractor.js +149 -1
- package/lib/module/services/chatGPTConversationService.js +268 -1
- package/lib/module/services/claudeConversationExtractor.js +97 -1
- package/lib/module/services/claudeConversationService.js +151 -1
- package/lib/module/services/connectedAccountsService.js +293 -1
- package/lib/module/services/googleAuthService.js +241 -1
- package/lib/module/services/hingeDataExtractor.js +99 -1
- package/lib/module/services/hingeDataService.js +143 -1
- package/lib/module/services/imageCompressionService.js +250 -1
- package/lib/module/services/instagramDataExtractor.js +120 -1
- package/lib/module/services/instagramDataService.js +156 -1
- package/lib/module/services/jwtStorageService.js +257 -1
- package/lib/module/services/linkedinDOMExtractor.js +234 -1
- package/lib/module/services/linkedinProfileService.js +210 -1
- package/lib/module/services/linkedinScrapingService.js +219 -1
- package/lib/module/services/llmDataStorage.js +277 -1
- package/lib/module/services/mobileTrainingService.js +173 -1
- package/lib/module/services/netflixDataExtractor.js +114 -1
- package/lib/module/services/netflixDataService.js +191 -1
- package/lib/module/services/pinEncryptionService.js +74 -6
- package/lib/module/services/pinStorageUtils.js +93 -1
- package/lib/module/services/platformAuthService.js +1461 -1
- package/lib/module/services/sephoraDataExtractor.js +134 -1
- package/lib/module/services/sephoraDataService.js +193 -1
- package/lib/module/services/spotifyDataExtractor.js +142 -1
- package/lib/module/services/spotifyDataService.js +234 -1
- package/lib/module/services/storageService.js +383 -1
- package/lib/module/services/telegramDataExtractor.js +109 -1
- package/lib/module/services/telegramDataService.js +493 -1
- package/lib/module/services/trainingApiHelpers.js +67 -1
- package/lib/module/services/userConnectionsService.js +329 -1
- package/lib/module/services/youtubeMigrationService.js +405 -1
- package/lib/module/theme/index.js +245 -1
- package/lib/module/types.js +10 -1
- package/lib/module/utils/Portal.js +90 -1
- package/lib/module/utils/api.js +118 -1
- package/lib/module/utils/assetRegistry.js +200 -34
- package/lib/module/utils/auth.js +100 -1
- package/lib/module/utils/connectorTests.js +600 -27
- package/lib/module/utils/crypto.js +54 -1
- package/lib/module/utils/debugHelper.js +54 -1
- package/lib/module/utils/encryption.js +67 -1
- package/lib/module/utils/eventUtils.js +270 -1
- package/lib/module/utils/haptics.js +59 -8
- package/lib/module/utils/imagePreloader.js +3 -1
- package/lib/module/utils/networkDiagnostics.js +217 -1
- package/lib/module/utils/onairosApi.js +333 -1
- package/lib/module/utils/programmaticFlow.js +111 -1
- package/lib/module/utils/retryHelper.js +211 -1
- package/lib/module/utils/secureStorage.js +330 -6
- package/lib/module/utils/webviewScripts/chatgpt.js +545 -1
- package/lib/module/utils/webviewScripts/claude.js +370 -1
- package/lib/module/utils/webviewScripts/hinge.js +405 -1
- package/lib/module/utils/webviewScripts/index.js +434 -1
- package/lib/module/utils/webviewScripts/instagram.js +448 -1
- package/lib/module/utils/webviewScripts/linkedin.js +874 -1
- package/lib/module/utils/webviewScripts/netflix.js +376 -1
- package/lib/module/utils/webviewScripts/sephora.js +510 -1
- package/lib/module/utils/webviewScripts/spotify.js +413 -1
- package/lib/module/utils/webviewScripts/telegram.js +672 -1
- 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
|
+
¤tPage=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
|
+
|