PageSourceSearch

https://fruiti.org/js/app/services/profile-insights.js?v=20260729-webint01

js fruiti.org collected 2026-10-02 15:45:36 UTC 9,486 bytes, 303 lines download raw bytes

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.