Is Captions for Live Presentations safe?
Captions for Live Presentations sends user IP address, geolocation, and speech transcripts to third-party servers.
On startup, the extension fetches the user's IP address and geolocation from ipapi.co and combines them with device fingerprint data before sending to Google Analytics via the GA4 Measurement Protocol. When the translation feature is enabled, final speech recognition results are transmitted to a developer-operated Google Cloud Run backend over a Socket.IO connection authenticated with a Firebase ID token.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
IP location added to analytics startup event
When the extension window opens, its bundle initializes Google Analytics with IP/location data from ipapi.co in analytics properties, plus a client ID and browser/platform/language fields, linking location and device to a persistent ID.
You open the extension window.
The extension initializes analytics, requests your IP-based location, adds device context, and posts the combined user properties to Google Analytics.
| Field | Value | Why it matters | |
|---|---|---|---|
Persistent analytics ID | 1f7b8a9e-4f1a-4e6a-8b33-9b4e2f4c8c2a | Lets future analytics events from the extension be tied back to the same browser profile. | |
Your IP address | 203.0.113.42 (illustrative) | Identifies the network address used for the extension session and supports approximate location lookup. | |
Approximate location | United States / California / San Francisco / America/Los_Angeles (illustrative) | Adds city, region, country, and timezone context to the analytics profile. | |
Browser and device context | Google Chrome 126, Chrome OS, en-US, 1440x900, mobile=false | Adds details about the browser, platform, language, screen size, and whether the browser appears mobile. | |
Startup event name | app_initialized | Marks the analytics upload as part of the extension startup flow. |
| Content-Type | application/json |
Analytics initialization path that combines location and device fields
var Gd = function() {
function e(t) {
var n = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {};
Rd(this, e);
var r = e,
s = r.DEFAULT_ENGAGEMENT_TIME_IN_MSEC;
"start_button_click" === t ? r.engagement_start_time = Date.now() : r.engagement_start_time && (s = Date.now() - r.engagement_start_time, "stop_button_click" === t && (r.engagement_start_time = 0)), this.name = t, this.params = Sd(Sd({}, n), {}, {
session_id: Date.now().toString(),
timestamp: (new Date).toISOString(),
engagement_time_msec: s,
debug_mode: !0
}), e.eventData.events.push(this)
}
return Nd(e, [{
key: "postToServer",
value: (n = Od(Cd().mark((function t() {
var n, r;
return Cd().wrap((function(t) {
for (;;) switch (t.prev = t.next) {
case 0:
return n = e, t.prev = 1, t.next = 4, fetch("".concat(n.GA_ENDPOINT, "?measurement_id=").concat(n.MEASUREMENT_ID, "&api_secret=").concat(n.API_SECRET), {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(n.eventData)
});
case 4:
if ((r = t.sent).ok) {
t.next = 7;
break
}
throw new Error("GA request failed with status ".concat(r.status));
case 7:
n.eventData.events = [], t.next = 15;
break;
case 11:
return t.prev = 11, t.t0 = t.catch(1), console.error("Failed to send event to GA:", t.t0), t.abrupt("return", !1);
case 15:
case 16:
case "end":
return t.stop()
}
}), t, null, [
[1, 11]
])
}))), function() {
return n.apply(this, arguments)
})
}], [{
key: "init",
value: (t = Od(Cd().mark((function t() {
var n = this;
return Cd().wrap((function(t) {
for (;;) switch (t.prev = t.next) {
case 0:
if (!Id(e, this, np)._) {
t.next = 2;
break
}
return t.abrupt("return", Id(e, this, np)._);
case 2:
np._ = Id(e, this, Promise.all([Id(e, this, Wd).call(this), Id(e, this, Qd).call(this), Id(e, this, Yd).call(this)]).then((function() {
return Id(e, n, ep).call(n), new e("app_initialized").postToServer(), !0
})).catch((function(t) {
throw console.error("Failed to initialize GoogleAnalyticsEvent:", t), np._ = Id(e, n, null), t
})));
case 4:
case "end":
return t.stop()
}
}), t, this)
}))), function() {
return t.apply(this, arguments)
})
}]);
var t, n
}();
function Wd() {
return Jd.apply(this, arguments)
}
function Jd() {
return (Jd = Od(Cd().mark((function e() {
var t;
return Cd().wrap((function(e) {
for (;;) switch (e.prev = e.next) {
case 0:
return e.next = 2, chrome.storage.local.get("clientId");
case 2:
if (t = (t = e.sent).clientId) {
e.next = 8;
break
}
return t = self.crypto.randomUUID(), e.next = 8, chrome.storage.local.set({
clientId: t
});
case 8:
this.eventData.client_id = t;
case 10:
case "end":
return e.stop()
}
}), e, this)
})))).apply(this, arguments)
}
function Qd() {
return Xd.apply(this, arguments)
}
function Xd() {
return (Xd = Od(Cd().mark((function e() {
var t, n;
return Cd().wrap((function(e) {
for (;;) switch (e.prev = e.next) {
case 0:
return e.prev = 0, e.next = 3, fetch("https://ipapi.co/json/");
case 3:
return t = e.sent, e.next = 6, t.json();
case 6:
n = e.sent, this.eventData.user_properties = Sd(Sd({}, this.eventData.user_properties), {}, {
country: {
value: n.country_name
},
region: {
value: n.region
},
city: {
value: n.city
},
timezone: {
value: n.timezone
},
ip: {
value: n.ip
}
}), e.next = 13;
break;
case 10:
e.prev = 10, e.t0 = e.catch(0), console.error("failed to retrieve user data");
case 13:
case 14:
case "end":
return e.stop()
}
}), e, this, [
[0, 10]
])
})))).apply(this, arguments)
}
function Yd() {
return Zd.apply(this, arguments)
}
function Zd() {
return Zd = Od(Cd().mark((function e() {
var t, n, r;
return Cd().wrap((function(e) {
for (;;) switch (e.prev = e.next) {
case 0:
return n = function() {
return n = Od(Cd().mark((function e() {
return Cd().wrap((function(e) {
for (;;) switch (e.prev = e.next) {
case 0:
return e.abrupt("return", navigator.userAgentData.getHighEntropyValues(["brands"]).then((function(e) {
var t = e.brands.find((function(e) {
return !e.brand.includes("Chromium") && !e.brand.includes("Not A")
}));
return t ? "".concat(t.brand, " ").concat(t.version) : "Unknown Browser"
})));
case 1:
case "end":
return e.stop()
}
}), e)
}))), n.apply(this, arguments)
}, t = function() {
return n.apply(this, arguments)
}, e.next = 5, t();
case 5:
r = e.sent, this.eventData.user_properties = Sd(Sd({}, this.eventData.user_properties), {}, {
browser: {
value: r
},
platform: {
value: navigator.userAgentData.platform
},
language: {
value: navigator.language
},
screenWidth: {
value: window.screen.width
},
screenHeight: {
value: window.screen.height
},
isMobile: {
value: /Mobi|Android/i.test(navigator.userAgent)
}
});
case 7:
case "end":
return e.stop()
}
}), e, this)
}))), Zd.apply(this, arguments)
}
function ep() {
return tp.apply(this, arguments)
}
function tp() {
return tp = Od(Cd().mark((function e() {
var t;
return Cd().wrap((function(e) {
for (;;) switch (e.prev = e.next) {
case 0:
t = function() {
var e = Od(Cd().mark((function e(t, n) {
var r, s, i, o, a;
return Cd().wrap((function(e) {
for (;;) switch (e.prev = e.next) {
case 0:
if (!(Ed.errorHandlingInProgress || null !== (r = n.reason) && void 0 !== r && null !== (r = r.stack) && void 0 !== r && r.includes("GoogleAnalyticsEvent"))) {
e.next = 2;
break
}
return e.abrupt("return");
case 2:
return Ed.errorHandlingInProgress = !0, a = {
type: t,
message: (null === (s = n.reason) || void 0 === s ? void 0 : s.message) || n.message || "Unknown error",
stack: (null === (i = n.reason) || void 0 === i ? void 0 : i.stack) || (null === (o = n.error) || void 0 === o ? void 0 : o.stack) || "No stack available",
source: n.filename || "N/A",
line: n.lineno || 0,
column: n.colno || 0
}, e.next = 6, new Ed("extension_error", a).postToServer();
case 6:
Ed.errorHandlingInProgress = !1;
case 7:
case "end":
return e.stop()
}
}), e)
})));
return function(t, n) {
return e.apply(this, arguments)
}
}(), addEventListener("unhandledrejection", (function(e) {
return t("promise_rejection", e)
})), addEventListener("error", (function(e) {
return t("runtime_error", e)
}));
case 3:
case "end":
return e.stop()
}
}), e)
}))), tp.apply(this, arguments)
}
Ed = Gd, Ad(Gd, "GA_ENDPOINT", "https://www.google-analytics.com/mp/collect"), Ad(Gd, "GA_DEBUG_ENDPOINT", "https://www.google-analytics.com/debug/mp/collect"), Ad(Gd, "DEFAULT_ENGAGEMENT_TIME_IN_MSEC", 0), Ad(Gd, "engagement_start_time", 0), Ad(Gd, "eventData", {
client_id: "",
events: [],
user_properties: {}
}), Ad(Gd, "errorHandlingInProgress", !1);
var np = {
_: null
};
Ad(Gd, "MEASUREMENT_ID", "G-YLS3JCT978"), Ad(Gd, "API_SECRET", "<redacted>");- ipapi.co
Receives the location lookup request and returns IP-derived country, region, city, timezone, and IP fields.
- www.google-analytics.com
Receives the app_initialized analytics event with client ID, location fields, and browser/device properties.