1/** 2 * Glow Up insights + Incognito â mirrors iOS InsightsAPIService. 3 * Present batch `/insights/present/all` is not on the API; fall back to per-tile GETs 4 * (crowd_competition / conversation_heat / intent_density), same as iOS. 5 */ 6(function (global) { 7 "use strict"; 8 9 var TILE_ORDER = { 10 historic: ["hunting_session", "conversion_funnel", "pursuit_missed"], 11 present: ["crowd_competition", "conversation_heat", "intent_density"], 12 future: ["momentum_outlook", "advantage_window", "initiative_projection"], 13 }; 14 15 var TILE_TITLES = { 16 hunting_session: "Hunting Session", 17 conversion_funnel: "Conversion Funnel", 18 pursuit_missed: "Pursuit Missed", 19 crowd_competition: "Crowd Competition", 20 conversation_heat: "Conversation Heat", 21 intent_density: "Intent Density", 22 momentum_outlook: "Momentum Outlook", 23 advantage_window: "Advantage Window", 24 initiative_projection: "Initiative Projection", 25 }; 26 27 var PRESENT_PATHS = { 28 crowd_competition: "/app/insights/present/crowd_competition", 29 conversation_heat: "/app/insights/present/conversation_heat", 30 intent_density: "/app/insights/present/intent_density", 31 }; 32 33 var HISTORIC_PATHS = { 34 hunting_session: "/app/insights/historic/hunting", 35 conversion_funnel: "/app/insights/historic/funnel", 36 pursuit_missed: "/app/insights/historic/pursuit_missed", 37 }; 38 39 var FUTURE_PATHS = { 40 momentum_outlook: "/app/insights/future/momentum_outlook", 41 advantage_window: "/app/insights/future/advantage_window", 42 initiative_projection: "/app/insights/future/initiative_projection", 43 }; 44 45 function unwrapRes(res) { 46 if (!res.ok) { 47 throw Object.assign(new Error(res.message || "Request failed."), { 48 status: res.status, 49 result: res, 50 }); 51 } 52 return res.data; 53 } 54 55 function request(path, opts) { 56 return FruitiAPI.apiRequest(path, opts).then(unwrapRes); 57 } 58 59 function softGet(path, signal) { 60 return FruitiAPI.apiRequest(path, { signal: signal }).then(function (res) { 61 if (!res.ok) return null; 62 return res.data; 63 }); 64 } 65 66 function unwrap(payload) { 67 if (!payload || typeof payload !== "object") return payload; 68 if (payload.data != null && typeof payload.data === "object" && !Array.isArray(payload.data)) { 69 return payload.data; 70 } 71 return payload; 72 } 73 74 function getWhoViewedMe(signal) { 75 return request("/app/insights/who-viewed-me", { signal: signal }).then(function (payload) { 76 var data = unwrap(payload); 77 var list = 78 (data && (data.viewers || data.users || data.items || data.whoViewedMe || data.profiles)) || 79 (Array.isArray(data) ? data : []) || 80 []; 81 if (!Array.isArray(list) && data && Array.isArray(data.data)) list = data.data; 82 return Array.isArray(list) ? list : []; 83 }); 84 } 85 86 function fetchPresentParallel(signal) { 87 return Promise.all([ 88 softGet(PRESENT_PATHS.crowd_competition, signal), 89 softGet(PRESENT_PATHS.conversation_heat, signal), 90 softGet(PRESENT_PATHS.intent_density, signal), 91 ]).then(function (parts) { 92 return { 93 crowd_competition: parts[0], 94 conversation_heat: parts[1], 95 intent_density: parts[2], 96 crowdCompetition: parts[0], 97 conversationHeat: parts[1], 98 intentDensity: parts[2], 99 }; 100 }); 101 } 102 103 function getPresentAll(signal) { 104 return FruitiAPI.apiRequest("/app/insights/present/all", { signal: signal }).then(function (res) { 105 if (res.status === 404 || !res.ok) return fetchPresentParallel(signal); 106 var data = unwrap(res.data) || {}; 107 if (data.crowdCompetition || data.conversationHeat || data.intentDensity) { 108 return { 109 crowd_competition: data.crowdCompetition, 110 conversation_heat: data.conversationHeat, 111 intent_density: data.intentDensity, 112 crowdCompetition: data.crowdCompetition, 113 conversationHeat: data.conversationHeat, 114 intentDensity: data.intentDensity, 115 }; 116 } 117 return fetchPresentParallel(signal); 118 }); 119 } 120 121 function fetchMap(paths, signal) { 122 var keys = Object.keys(paths); 123 return Promise.all( 124 keys.map(function (key) { 125 return softGet(paths[key], signal).then(function (data) { 126 return { key: key, data: data }; 127 }); 128 }) 129 ).then(function (rows) { 130 var out = {}; 131 rows.forEach(function (row) { 132 out[row.key] = row.data; 133 }); 134 return out; 135 }); 136 } 137 138 function getHistoricTiles(signal) { 139 return fetchMap(HISTORIC_PATHS, signal); 140 } 141 142 function getFutureTiles(signal) { 143 return fetchMap(FUTURE_PATHS, signal); 144 } 145 146 function getIncognito(signal) { 147 return request("/app/incognito-mode", { signal: signal }).then(function (payload) { 148 var data = unwrap(payload) || {}; 149 return { 150 enabled: data.enabled === true || data.incognitoMode === true || data.isIncognito === true, 151 raw: data, 152 }; 153 }); 154 } 155 156 function setIncognito(enabled, signal) { 157 return request("/app/incognito-mode", { 158 method: "PUT", 159 body: { enabled: !!enabled, incognitoMode: !!enabled }, 160 signal: signal, 161 }).then(function (payload) { 162 var data = unwrap(payload) || {}; 163 return { 164 enabled: data.enabled === true || data.incognitoMode === true || !!enabled, 165 raw: data, 166 }; 167 }); 168 } 169 170 function firstText() { 171 for (var i = 0; i < arguments.length; i++) { 172 var v = arguments[i]; 173 if (v == null) continue; 174 var s = String(v).trim(); 175 if (s) return s; 176 } 177 return ""; 178 } 179 180 function summarizeTile(key, raw) { 181 if (!raw || typeof raw !== "object") return ""; 182 if (key === "crowd_competition") { 183 return firstText( 184 raw.explanation && raw.explanation.headline, 185 raw.explanation && raw.explanation.detail, 186 raw.competition && raw.competition.band 187 ); 188 } 189 if (key === "conversation_heat") { 190 var highlights = Array.isArray(raw.highlights) ? raw.highlights.filter(
190Boolean) : []; 191 return firstText( 192 highlights[0], 193 raw.suggestion && raw.suggestion.label, 194 raw.heat && raw.heat.band 195 ); 196 } 197 if (key === "intent_density") { 198 return firstText( 199 raw.copy && raw.copy.headline, 200 raw.copy && raw.copy.detail, 201 raw.density && raw.density.band 202 ); 203 } 204 if (key === "hunting_session") { 205 return firstText( 206 raw.summary, 207 raw.headline, 208 raw.message, 209 raw.sessionsJoinedTotal != null ? raw.sessionsJoinedTotal + " sessions" : "" 210 ); 211 } 212 if (key === "conversion_funnel") { 213 return firstText(raw.hint, raw.summary, raw.headline, raw.primary); 214 } 215 if (key === "pursuit_missed") { 216 return firstText(raw.note, raw.summary, raw.topReason, raw.headline); 217 } 218 return firstText( 219 raw.explanation && (raw.explanation.headline || raw.explanation.detail || raw.explanation), 220 raw.copy && (raw.copy.headline || raw.copy.detail), 221 raw.summary, 222 raw.headline, 223 raw.detail, 224 raw.message, 225 raw.trajectory, 226 typeof raw.explanation === "string" ? raw.explanation : "" 227 ); 228 } 229 230 function normalizeTiles(bucket, data, isPremium) { 231 var order = TILE_ORDER[bucket] || []; 232 var source = data || {}; 233 var out = []; 234 order.forEach(function (key) { 235 if (bucket === "present" && key === "crowd_competition" && !isPremium) return; 236 var raw = 237 source[key] || 238 source[camelKey(key)] || 239 (source.tiles && (source.tiles[key] || source.tiles[camelKey(key)])) || 240 null; 241 out.push({ 242 key: key, 243 title: TILE_TITLES[key] || key, 244 summary: summarizeTile(key, raw), 245 missing: !raw, 246 locked: false, 247 raw: raw, 248 }); 249 }); 250 return out; 251 } 252 253 function camelKey(snake) { 254 return snake.replace(/_([a-z])/g, function (_, c) { 255 return c.toUpperCase(); 256 }); 257 } 258 259 function relativeTime(iso) { 260 if (!iso) return ""; 261 var t = new Date(iso).getTime(); 262 if (isNaN(t)) return ""; 263 var diff = Math.max(0, Date.now() - t); 264 var mins = Math.floor(diff / 60000); 265 if (mins < 1) return "Just now"; 266 if (mins < 60) return mins + "m ago"; 267 var hrs = Math.floor(mins / 60); 268 if (hrs < 24) return hrs + "h ago"; 269 var days = Math.floor(hrs / 24); 270 if (days < 7) return days + "d ago"; 271 return new Date(t).toLocaleDateString(); 272 } 273 274 function viewerRow(item) { 275 var u = item.user || item.viewer || item.profile || item; 276 var id = String(u.id || u.userId || u.profileId || item.userId || item.profileId || "").trim(); 277 var name = 278 FruitiProfileModel.chromeDisplayName(u) || 279 String(u.displayName || u.firstName || "Someone").trim(); 280 var photo = FruitiUI.photoUrl(u) || FruitiUI.safeImageUrl(item.photoUrl || item.avatarUrl || item.avatarURL); 281 var when = item.viewedAt || item.createdAt || item.timestamp || item.at; 282 return { 283 id: id, 284 name: name, 285 photoUrl: photo, 286 whenLabel: relativeTime(when), 287 raw: item, 288 }; 289 } 290 291 global.FruitiProfileInsights = { 292 TILE_ORDER: TILE_ORDER, 293 TILE_TITLES: TILE_TITLES, 294 getWhoViewedMe: getWhoViewedMe, 295 getPresentAll: getPresentAll, 296 getHistoricTiles: getHistoricTiles, 297 getFutureTiles: getFutureTiles, 298 getIncognito: getIncognito, 299 setIncognito: setIncognito, 300 normalizeTiles: normalizeTiles, 301 viewerRow: viewerRow, 302 }; 303})(window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.