Merge remote-tracking branch 'origin/main' into refactor/ui

This commit is contained in:
liyasthomas
2021-07-08 13:44:00 +05:30
18 changed files with 346 additions and 52 deletions

View File

@@ -13,6 +13,7 @@ STORAGE_BUCKET=postwoman-api.appspot.com
MESSAGING_SENDER_ID=421993993223 MESSAGING_SENDER_ID=421993993223
APP_ID=1:421993993223:web:ec0baa8ee8c02ffa1fc6a2 APP_ID=1:421993993223:web:ec0baa8ee8c02ffa1fc6a2
MEASUREMENT_ID=G-ERJ6025CEB MEASUREMENT_ID=G-ERJ6025CEB
FB_MEASUREMENT_ID=G-BBJ3R80PJT
# Base URL # Base URL
BASE_URL=https://hoppscotch.io BASE_URL=https://hoppscotch.io

View File

@@ -131,6 +131,7 @@
import { Splitpanes, Pane } from "splitpanes" import { Splitpanes, Pane } from "splitpanes"
import Paho from "paho-mqtt" import Paho from "paho-mqtt"
import debounce from "~/helpers/utils/debounce" import debounce from "~/helpers/utils/debounce"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
export default { export default {
components: { Splitpanes, Pane }, components: { Splitpanes, Pane },
@@ -202,6 +203,10 @@ export default {
}) })
this.client.onConnectionLost = this.onConnectionLost this.client.onConnectionLost = this.onConnectionLost
this.client.onMessageArrived = this.onMessageArrived this.client.onMessageArrived = this.onMessageArrived
logHoppRequestRunToAnalytics({
platform: "mqtt",
})
}, },
onConnectionFailure() { onConnectionFailure() {
this.connectionState = false this.connectionState = false

View File

@@ -153,6 +153,7 @@ import { Splitpanes, Pane } from "splitpanes"
import { io as Client } from "socket.io-client" import { io as Client } from "socket.io-client"
import wildcard from "socketio-wildcard" import wildcard from "socketio-wildcard"
import debounce from "~/helpers/utils/debounce" import debounce from "~/helpers/utils/debounce"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
export default { export default {
components: { Splitpanes, Pane }, components: { Splitpanes, Pane },
@@ -275,6 +276,10 @@ export default {
icon: "error", icon: "error",
}) })
} }
logHoppRequestRunToAnalytics({
platform: "socketio",
})
}, },
disconnect() { disconnect() {
this.io.close() this.io.close()

View File

@@ -53,6 +53,7 @@
<script> <script>
import { Splitpanes, Pane } from "splitpanes" import { Splitpanes, Pane } from "splitpanes"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
import debounce from "~/helpers/utils/debounce" import debounce from "~/helpers/utils/debounce"
export default { export default {
@@ -164,6 +165,10 @@ export default {
}, },
] ]
} }
logHoppRequestRunToAnalytics({
platform: "mqtt",
})
}, },
handleSSEError(error) { handleSSEError(error) {
this.stop() this.stop()

View File

@@ -169,6 +169,7 @@
<script> <script>
import { Splitpanes, Pane } from "splitpanes" import { Splitpanes, Pane } from "splitpanes"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
import debounce from "~/helpers/utils/debounce" import debounce from "~/helpers/utils/debounce"
import "splitpanes/dist/splitpanes.css" import "splitpanes/dist/splitpanes.css"
@@ -285,6 +286,10 @@ export default {
icon: "error", icon: "error",
}) })
} }
logHoppRequestRunToAnalytics({
platform: "wss",
})
}, },
disconnect() { disconnect() {
if (this.socket) { if (this.socket) {

View File

@@ -21,36 +21,34 @@
</div> </div>
</template> </template>
<script> <script lang="ts">
import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence" import Vue from "vue"
import {
HoppAccentColors,
HoppAccentColor,
getSettingSubject,
settingsStore,
applySetting,
} from "~/newstore/settings"
export default { export default Vue.extend({
data() { data() {
return { return {
active: getLocalConfig("THEME_COLOR") || "green", accentColors: HoppAccentColors,
accentColors: [ active: settingsStore.value.THEME_COLOR,
"blue",
"green",
"teal",
"indigo",
"purple",
"orange",
"pink",
"red",
"yellow",
],
} }
}, },
watch: { subscriptions() {
active(color) { return {
setLocalConfig("THEME_COLOR", color) active: getSettingSubject("THEME_COLOR"),
}, }
}, },
methods: { methods: {
setActiveColor(color) { setActiveColor(color: HoppAccentColor) {
document.documentElement.setAttribute("data-accent", color) document.documentElement.setAttribute("data-accent", color)
this.active = color
applySetting("THEME_COLOR", color)
}, },
}, },
} })
</script> </script>

View File

@@ -15,15 +15,31 @@
</div> </div>
</template> </template>
<script> <script lang="ts">
export default { import Vue from "vue"
import {
applySetting,
getSettingSubject,
HoppBgColor,
HoppBgColors,
} from "~/newstore/settings"
export default Vue.extend({
data() { data() {
return { return {
colors: ["system", "light", "dark", "black"], colors: HoppBgColors,
}
},
subscriptions() {
return {
activeColor: getSettingSubject("BG_COLOR"),
} }
}, },
methods: { methods: {
getIcon(color) { setBGMode(color: HoppBgColor) {
applySetting("BG_COLOR", color)
},
getIcon(color: HoppBgColor) {
switch (color) { switch (color) {
case "system": case "system":
return "desktop_windows" return "desktop_windows"
@@ -38,5 +54,5 @@ export default {
} }
}, },
}, },
} })
</script> </script>

93
helpers/fb/analytics.ts Normal file
View File

@@ -0,0 +1,93 @@
import firebase from "firebase"
import { authEvents$ } from "./auth"
import {
HoppAccentColor,
HoppBgColor,
settings$,
settingsStore,
} from "~/newstore/settings"
let analytics: firebase.analytics.Analytics
type SettingsCustomDimensions = {
usesProxy: boolean
usesExtension: boolean
usesScrollInto: boolean
syncCollections: boolean
syncEnvironments: boolean
syncHistory: boolean
usesBg: HoppBgColor
usesAccent: HoppAccentColor
usesTelemetry: boolean
}
type HoppRequestEvent =
| {
platform: "rest" | "graphql-query" | "graphql-schema"
strategy: "normal" | "proxy" | "extension"
}
| { platform: "wss" | "sse" | "socketio" | "mqtt" }
export function initAnalytics() {
analytics = firebase.app().analytics()
initLoginListeners()
initSettingsListeners()
}
function initLoginListeners() {
authEvents$.subscribe((ev) => {
if (ev.event === "login") {
if (settingsStore.value.TELEMETRY_ENABLED) {
analytics.setUserId(ev.user.uid)
analytics.logEvent("login", {
method: ev.user.providerData[0]?.providerId, // Assume the first provider is the login provider
})
}
} else if (ev.event === "logout") {
if (settingsStore.value.TELEMETRY_ENABLED) {
analytics.logEvent("logout")
}
}
})
}
function initSettingsListeners() {
// Keep track of the telemetry status
let telemetryStatus = settingsStore.value.TELEMETRY_ENABLED
settings$.subscribe((settings) => {
const conf: SettingsCustomDimensions = {
usesProxy: settings.PROXY_ENABLED,
usesExtension: settings.EXTENSIONS_ENABLED,
usesScrollInto: settings.SCROLL_INTO_ENABLED,
syncCollections: settings.syncCollections,
syncEnvironments: settings.syncEnvironments,
syncHistory: settings.syncHistory,
usesAccent: settings.THEME_COLOR,
usesBg: settings.BG_COLOR,
usesTelemetry: settings.TELEMETRY_ENABLED,
}
// User toggled telemetry mode to off or to on
if (
(telemetryStatus && !settings.TELEMETRY_ENABLED) ||
settings.TELEMETRY_ENABLED
) {
analytics.setUserProperties(conf)
}
telemetryStatus = settings.TELEMETRY_ENABLED
analytics.setAnalyticsCollectionEnabled(telemetryStatus)
})
analytics.setAnalyticsCollectionEnabled(telemetryStatus)
}
export function logHoppRequestRunToAnalytics(ev: HoppRequestEvent) {
if (settingsStore.value.TELEMETRY_ENABLED) {
analytics.logEvent("hopp-request", ev)
}
}

View File

@@ -1,11 +1,16 @@
import firebase from "firebase" import firebase from "firebase"
import { BehaviorSubject } from "rxjs" import { BehaviorSubject, Subject } from "rxjs"
export type HoppUser = firebase.User & { export type HoppUser = firebase.User & {
provider?: string provider?: string
accessToken?: string accessToken?: string
} }
type AuthEvents =
| { event: "login"; user: HoppUser }
| { event: "logout" }
| { event: "authTokenUpdate"; user: HoppUser; newToken: string | null }
/** /**
* A BehaviorSubject emitting the currently logged in user (or null if not logged in) * A BehaviorSubject emitting the currently logged in user (or null if not logged in)
*/ */
@@ -15,6 +20,11 @@ export const currentUser$ = new BehaviorSubject<HoppUser | null>(null)
*/ */
export const authIdToken$ = new BehaviorSubject<string | null>(null) export const authIdToken$ = new BehaviorSubject<string | null>(null)
/**
* A subject that emits events related to authentication flows
*/
export const authEvents$ = new Subject<AuthEvents>()
/** /**
* Initializes the firebase authentication related subjects * Initializes the firebase authentication related subjects
*/ */
@@ -22,6 +32,9 @@ export function initAuth() {
let extraSnapshotStop: (() => void) | null = null let extraSnapshotStop: (() => void) | null = null
firebase.auth().onAuthStateChanged((user) => { firebase.auth().onAuthStateChanged((user) => {
/** Whether the user was logged in before */
const wasLoggedIn = currentUser$.value !== null
if (!user && extraSnapshotStop) { if (!user && extraSnapshotStop) {
extraSnapshotStop() extraSnapshotStop()
extraSnapshotStop = null extraSnapshotStop = null
@@ -61,14 +74,35 @@ export function initAuth() {
userUpdate.provider = data.provider userUpdate.provider = data.provider
userUpdate.accessToken = data.accessToken userUpdate.accessToken = data.accessToken
} }
currentUser$.next(userUpdate)
}) })
} }
currentUser$.next(user) currentUser$.next(user)
// User wasn't found before, but now is there (login happened)
if (!wasLoggedIn && user) {
authEvents$.next({
event: "login",
user: currentUser$.value!!,
})
} else if (wasLoggedIn && !user) {
// User was found before, but now is not there (logout happened)
authEvents$.next({
event: "logout",
})
}
}) })
firebase.auth().onIdTokenChanged(async (user) => { firebase.auth().onIdTokenChanged(async (user) => {
if (user) { if (user) {
authIdToken$.next(await user.getIdToken()) authIdToken$.next(await user.getIdToken())
authEvents$.next({
event: "authTokenUpdate",
newToken: authIdToken$.value,
user: currentUser$.value!!, // Force not-null because user is defined
})
} else { } else {
authIdToken$.next(null) authIdToken$.next(null)
} }

View File

@@ -1,4 +1,5 @@
import firebase from "firebase" import firebase from "firebase"
import { initAnalytics } from "./analytics"
import { initAuth } from "./auth" import { initAuth } from "./auth"
import { initCollections } from "./collections" import { initCollections } from "./collections"
import { initEnvironments } from "./environments" import { initEnvironments } from "./environments"
@@ -13,13 +14,14 @@ const firebaseConfig = {
storageBucket: process.env.STORAGE_BUCKET, storageBucket: process.env.STORAGE_BUCKET,
messagingSenderId: process.env.MESSAGING_SENDER_ID, messagingSenderId: process.env.MESSAGING_SENDER_ID,
appId: process.env.APP_ID, appId: process.env.APP_ID,
measurementId: process.env.MEASUREMENT_ID, measurementId: process.env.FB_MEASUREMENT_ID,
} }
let initialized = false let initialized = false
export function initializeFirebase() { export function initializeFirebase() {
if (!initialized) { if (!initialized) {
try {
firebase.initializeApp(firebaseConfig) firebase.initializeApp(firebaseConfig)
initAuth() initAuth()
@@ -27,7 +29,12 @@ export function initializeFirebase() {
initCollections() initCollections()
initHistory() initHistory()
initEnvironments() initEnvironments()
initAnalytics()
initialized = true initialized = true
} catch (e) {
// initializeApp throws exception if we reinitialize
initialized = true
}
} }
} }

View File

@@ -25,5 +25,21 @@ const runAppropriateStrategy = (req) => {
return AxiosStrategy(req) return AxiosStrategy(req)
} }
/**
* Returns an identifier for how a request will be ran
* if the system is asked to fire a request
*
* @returns {"normal" | "extension" | "proxy"}
*/
export function getCurrentStrategyID() {
if (isExtensionsAllowed() && hasExtensionInstalled()) {
return "extension"
} else if (settingsStore.value.PROXY_ENABLED) {
return "proxy"
} else {
return "normal"
}
}
export const sendNetworkRequest = (req) => export const sendNetworkRequest = (req) =>
runAppropriateStrategy(req).finally(() => window.$nuxt.$loading.finish()) runAppropriateStrategy(req).finally(() => window.$nuxt.$loading.finish())

View File

@@ -286,6 +286,8 @@
"are_you_sure_remove_folder": "Are you sure you want to remove this folder?", "are_you_sure_remove_folder": "Are you sure you want to remove this folder?",
"are_you_sure_remove_request": "Are you sure you want to remove this request?", "are_you_sure_remove_request": "Are you sure you want to remove this request?",
"are_you_sure_remove_environment": "Are you sure you want to remove this environment?", "are_you_sure_remove_environment": "Are you sure you want to remove this environment?",
"are_you_sure_remove_telemetry": "Are you sure you want to opt-out of Telemetry?",
"telemetry_helps_us": "Telemetry helps us to personalize our operations and deliver the best experience to you.",
"select_next_method": "Select Next method", "select_next_method": "Select Next method",
"select_previous_method": "Select Previous method", "select_previous_method": "Select Previous method",
"select_get_method": "Select GET method", "select_get_method": "Select GET method",
@@ -345,5 +347,6 @@
"share": "Share", "share": "Share",
"interceptor": "Interceptor", "interceptor": "Interceptor",
"profile": "Profile", "profile": "Profile",
"are_you_sure_logout": "Are you sure you want to logout?" "are_you_sure_logout": "Are you sure you want to logout?",
"telemetry": "Telemetry"
} }

View File

@@ -66,15 +66,12 @@
<script> <script>
import { Splitpanes, Pane } from "splitpanes" import { Splitpanes, Pane } from "splitpanes"
import "splitpanes/dist/splitpanes.css" import "splitpanes/dist/splitpanes.css"
import { setupLocalPersistence } from "~/newstore/localpersistence"
import {
setupLocalPersistence,
getLocalConfig,
} from "~/newstore/localpersistence"
import { performMigrations } from "~/helpers/migrations" import { performMigrations } from "~/helpers/migrations"
import { initUserInfo } from "~/helpers/teams/BackendUserInfo" import { initUserInfo } from "~/helpers/teams/BackendUserInfo"
import { registerApolloAuthUpdate } from "~/helpers/apollo" import { registerApolloAuthUpdate } from "~/helpers/apollo"
import { initializeFirebase } from "~/helpers/fb" import { initializeFirebase } from "~/helpers/fb"
import { getSettingSubject } from "~/newstore/settings"
export default { export default {
components: { Splitpanes, Pane }, components: { Splitpanes, Pane },
@@ -93,8 +90,13 @@ export default {
beforeMount() { beforeMount() {
registerApolloAuthUpdate() registerApolloAuthUpdate()
const color = getLocalConfig("THEME_COLOR") || "green" this.$subscribeTo(getSettingSubject("THEME_COLOR"), (color) => {
document.documentElement.setAttribute("data-accent", color) document.documentElement.setAttribute("data-accent", color)
})
this.$subscribeTo(getSettingSubject("BG_COLOR"), (color) => {
this.$colorMode.preference = color
})
}, },
async mounted() { async mounted() {
performMigrations() performMigrations()
@@ -112,13 +114,13 @@ export default {
if (workbox) { if (workbox) {
workbox.addEventListener("installed", (event) => { workbox.addEventListener("installed", (event) => {
if (event.isUpdate) { if (event.isUpdate) {
this.$toast.show(this.$t("new_version_found"), { this.$toast.show(this.$t("new_version_found").toString(), {
icon: "info", icon: "info",
duration: 0, duration: 0,
theme: "toasted-primary", theme: "toasted-primary",
action: [ action: [
{ {
text: this.$t("reload"), text: this.$t("reload").toString(),
onClick: (_, toastObject) => { onClick: (_, toastObject) => {
toastObject.goAway(0) toastObject.goAway(0)
window.location.reload() window.location.reload()

View File

@@ -3,7 +3,14 @@
import clone from "lodash/clone" import clone from "lodash/clone"
import assign from "lodash/assign" import assign from "lodash/assign"
import eq from "lodash/eq" import eq from "lodash/eq"
import { settingsStore, bulkApplySettings, defaultSettings } from "./settings" import {
settingsStore,
bulkApplySettings,
defaultSettings,
applySetting,
HoppAccentColor,
HoppBgColor,
} from "./settings"
import { import {
restHistoryStore, restHistoryStore,
graphqlHistoryStore, graphqlHistoryStore,
@@ -55,6 +62,20 @@ function checkAndMigrateOldSettings() {
delete vuexData.postwoman.environments delete vuexData.postwoman.environments
window.localStorage.setItem("vuex", JSON.stringify(vuexData)) window.localStorage.setItem("vuex", JSON.stringify(vuexData))
} }
if (window.localStorage.getItem("THEME_COLOR")) {
const themeColor = window.localStorage.getItem("THEME_COLOR")
applySetting("THEME_COLOR", themeColor as HoppAccentColor)
window.localStorage.removeItem("THEME_COLOR")
}
if (window.localStorage.getItem("nuxt-color-mode")) {
const color = window.localStorage.getItem("nuxt-color-mode") as HoppBgColor
applySetting("BG_COLOR", color)
window.localStorage.removeItem("BG_COLOR")
}
} }
function setupSettingsPersistence() { function setupSettingsPersistence() {

View File

@@ -4,7 +4,47 @@ import { Observable } from "rxjs"
import DispatchingStore, { defineDispatchers } from "./DispatchingStore" import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
import type { KeysMatching } from "~/types/ts-utils" import type { KeysMatching } from "~/types/ts-utils"
export const defaultSettings = { export const HoppBgColors = ["system", "light", "dark", "black"] as const
export type HoppBgColor = typeof HoppBgColors[number]
export const HoppAccentColors = [
"blue",
"green",
"teal",
"indigo",
"purple",
"orange",
"pink",
"red",
"yellow",
] as const
export type HoppAccentColor = typeof HoppAccentColors[number]
export type SettingsType = {
syncCollections: boolean
syncHistory: boolean
syncEnvironments: boolean
SCROLL_INTO_ENABLED: boolean
PROXY_ENABLED: boolean
PROXY_URL: string
PROXY_KEY: string
EXTENSIONS_ENABLED: boolean
EXPERIMENTAL_URL_BAR_ENABLED: boolean
URL_EXCLUDES: {
auth: boolean
httpUser: boolean
httpPassword: boolean
bearerToken: boolean
}
THEME_COLOR: HoppAccentColor
BG_COLOR: HoppBgColor
TELEMETRY_ENABLED: boolean
}
export const defaultSettings: SettingsType = {
syncCollections: true, syncCollections: true,
syncHistory: true, syncHistory: true,
syncEnvironments: true, syncEnvironments: true,
@@ -21,10 +61,11 @@ export const defaultSettings = {
httpPassword: true, httpPassword: true,
bearerToken: true, bearerToken: true,
}, },
THEME_COLOR: "green",
BG_COLOR: "system",
TELEMETRY_ENABLED: true,
} }
export type SettingsType = typeof defaultSettings
const validKeys = Object.keys(defaultSettings) const validKeys = Object.keys(defaultSettings)
const dispatchers = defineDispatchers({ const dispatchers = defineDispatchers({

View File

@@ -455,9 +455,10 @@ import { Splitpanes, Pane } from "splitpanes"
import * as gql from "graphql" import * as gql from "graphql"
import { commonHeaders } from "~/helpers/headers" import { commonHeaders } from "~/helpers/headers"
import { getPlatformSpecialKey } from "~/helpers/platformutils" import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { sendNetworkRequest } from "~/helpers/network" import { getCurrentStrategyID, sendNetworkRequest } from "~/helpers/network"
import { getSettingSubject } from "~/newstore/settings" import { getSettingSubject } from "~/newstore/settings"
import { addGraphqlHistoryEntry } from "~/newstore/history" import { addGraphqlHistoryEntry } from "~/newstore/history"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
export default { export default {
components: { Splitpanes, Pane }, components: { Splitpanes, Pane },
@@ -818,6 +819,11 @@ export default {
}) })
console.log("Error", error) console.log("Error", error)
} }
logHoppRequestRunToAnalytics({
platform: "graphql-query",
strategy: getCurrentStrategyID(),
})
}, },
// NOTE : schema required here is the GQL Schema document object, not the schema string // NOTE : schema required here is the GQL Schema document object, not the schema string
@@ -887,6 +893,11 @@ export default {
await this.getSchema() await this.getSchema()
this.pollSchema() this.pollSchema()
logHoppRequestRunToAnalytics({
platform: "graphql-schema",
strategy: getCurrentStrategyID(),
})
} }
}, },
async pollSchema() { async pollSchema() {

View File

@@ -167,6 +167,12 @@
{{ $t("use_experimental_url_bar") }} {{ $t("use_experimental_url_bar") }}
</SmartToggle> </SmartToggle>
</div> </div>
<div class="flex items-center">
<SmartToggle :on="TELEMETRY_ENABLED" @change="showConfirmModal">
{{ $t("telemetry") }}
{{ TELEMETRY_ENABLED ? $t("enabled") : $t("disabled") }}
</SmartToggle>
</div>
</div> </div>
</fieldset> </fieldset>
</div> </div>
@@ -275,8 +281,19 @@
</div> </div>
</div> </div>
</div> </div>
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" /> <FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
<FirebaseEmail :show="showEmail" @hide-modal="showEmail = false" />
<SmartConfirmModal
:show="confirmRemove"
:title="`${$t('are_you_sure_remove_telemetry')} ${$t(
'telemetry_helps_us'
)}`"
@hide-modal="confirmRemove = false"
@resolve="
toggleSetting('TELEMETRY_ENABLED')
confirmRemove = false
"
/>
</div> </div>
</template> </template>
@@ -319,6 +336,9 @@ export default Vue.extend({
showLogin: false, showLogin: false,
active: getLocalConfig("THEME_COLOR") || "green", active: getLocalConfig("THEME_COLOR") || "green",
confirmRemove: false,
TELEMETRY_ENABLED: null,
} }
}, },
subscriptions() { subscriptions() {
@@ -339,6 +359,8 @@ export default Vue.extend({
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"), SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"),
SYNC_HISTORY: getSettingSubject("syncHistory"), SYNC_HISTORY: getSettingSubject("syncHistory"),
TELEMETRY_ENABLED: getSettingSubject("TELEMETRY_ENABLED"),
currentUser: currentUser$, currentUser: currentUser$,
} }
}, },
@@ -365,6 +387,10 @@ export default Vue.extend({
}, },
}, },
methods: { methods: {
showConfirmModal() {
if (this.TELEMETRY_ENABLED) this.confirmRemove = true
else toggleSetting("TELEMETRY_ENABLED")
},
applySetting<K extends keyof SettingsType>(key: K, value: SettingsType[K]) { applySetting<K extends keyof SettingsType>(key: K, value: SettingsType[K]) {
applySetting(key, value) applySetting(key, value)
}, },

View File

@@ -34,6 +34,11 @@ export const SETTINGS_KEYS = [
*/ */
"EXTENSIONS_ENABLED", "EXTENSIONS_ENABLED",
/**
* A boolean value indicating whether Telemetry is enabled.
*/
"TELEMETRY_ENABLED",
/** /**
* A boolean value indicating whether to use the URL bar experiments * A boolean value indicating whether to use the URL bar experiments
*/ */