feat(chrome-ext): managed policy for Onyx URL and new-tab toggle; strip trailing slashes (#15027)

This commit is contained in:
roshan authored and GitHub committed 2026-09-23 19:34:16 +00:00
1 parent 436209ee5d
commit b2b7172634
13 files changed
+485 -229

No files matched your search

+52
View File
@@ -19,12 +19,64 @@ Onyx connects with dozens of popular workplace apps like Google Drive, Jira, Con
For Onyx Cloud Users, please visit the Chrome Plugin Store (pending approval still) For Onyx Cloud Users, please visit the Chrome Plugin Store (pending approval still)
## Enterprise configuration (managed policy)
Admins can pre-set and lock settings with Chrome's extension policy
(`ExtensionSettings` / "Managed storage" in Google Admin, or the
`3rdparty.extensions.<id>` policy on Windows/macOS/Linux). Settings set by
policy are read-only in the extension UI. Supported keys (see
`managed_schema.json`):
| Key | Type | Description |
| ---------------------------- | ------- | ----------------------------------------------- |
| `onyxExtensionDomain` | string | Root URL of your Onyx instance |
| `onyxExtensionDefaultNewTab` | boolean | Force the "Use Onyx as new tab page" toggle |
Example (Linux, `/etc/opt/chrome/policies/managed/onyx.json`; the Chrome Web
Store extension ID is `dacfbnglakogghooelgjflkhjcdbemia`):
```json
{
"3rdparty": {
"extensions": {
"dacfbnglakogghooelgjflkhjcdbemia": {
"onyxExtensionDomain": "https://onyx.example.com",
"onyxExtensionDefaultNewTab": false
}
}
}
}
```
Trailing slashes on `onyxExtensionDomain` are stripped automatically.
## Development ## Development
- Load unpacked extension in your browser - Load unpacked extension in your browser
- Modify files in `src` directory - Modify files in `src` directory
- Refresh extension in Chrome - Refresh extension in Chrome
### Testing managed policy locally
Use the ID shown on the unpacked extension's card in `chrome://extensions`
(it differs from the Web Store ID).
Linux: write the JSON above to `/etc/opt/chrome/policies/managed/onyx.json`.
macOS: Chrome only reads extension policy from managed preferences, so
`defaults write` is not enough. Write the plist with `plutil`:
```sh
P="/Library/Managed Preferences/$USER/com.google.Chrome.extensions.<id>.plist"
sudo plutil -create xml1 "$P"
sudo plutil -insert onyxExtensionDomain -string "https://onyx.example.com" "$P"
sudo plutil -insert onyxExtensionDefaultNewTab -bool false "$P"
sudo killall cfprefsd
```
Then quit and relaunch Chrome, click "Reload policies" on `chrome://policy`,
and reload the extension. Remove the file and restart Chrome to undo.
## Contributing ## Contributing
Submit issues or pull requests for improvements Submit issues or pull requests for improvements
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/draft-03/schema#",
"type": "object",
"properties": {
"onyxExtensionDomain": {
"title": "Onyx root domain",
"description": "Root URL of the Onyx instance (for example https://cloud.onyx.app). When set by policy, users cannot change it in the extension.",
"type": "string"
},
"onyxExtensionDefaultNewTab": {
"title": "Use Onyx as new tab page",
"description": "Force the new tab override on (true) or off (false). When set by policy, users cannot change the toggle in the extension.",
"type": "boolean"
}
}
}
+5 -7
View File
@@ -1,14 +1,9 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Onyx", "name": "Onyx",
"version": "1.1", "version": "1.2",
"description": "Onyx lets you research, create, and automate with LLMs powered by your team's unique knowledge", "description": "Onyx lets you research, create, and automate with LLMs powered by your team's unique knowledge",
"permissions": [ "permissions": ["sidePanel", "storage", "activeTab", "tabs"],
"sidePanel",
"storage",
"activeTab",
"tabs"
],
"host_permissions": ["<all_urls>"], "host_permissions": ["<all_urls>"],
"background": { "background": {
"service_worker": "service_worker.js", "service_worker": "service_worker.js",
@@ -28,6 +23,9 @@
"128": "public/icon128.png" "128": "public/icon128.png"
}, },
"options_page": "src/pages/options.html", "options_page": "src/pages/options.html",
"storage": {
"managed_schema": "managed_schema.json"
},
"chrome_url_overrides": { "chrome_url_overrides": {
"newtab": "src/pages/onyx_home.html" "newtab": "src/pages/onyx_home.html"
}, },
+47 -71
View File
@@ -1,9 +1,14 @@
import { import {
DEFAULT_ONYX_DOMAIN,
CHROME_SPECIFIC_STORAGE_KEYS, CHROME_SPECIFIC_STORAGE_KEYS,
ACTIONS, ACTIONS,
SIDE_PANEL_PATH, SIDE_PANEL_PATH,
} from "./src/utils/constants.js"; } from "./src/utils/constants.js";
import {
getOnyxDomain,
getUseOnyxAsDefaultNewTab,
setUseOnyxAsDefaultNewTab,
isNewTabOverrideChange,
} from "./src/utils/storage.js";
// Track side panel state per window // Track side panel state per window
const sidePanelOpenState = new Map(); const sidePanelOpenState = new Map();
@@ -52,12 +57,8 @@ async function sendToOnyx(info, tab) {
const currentUrl = encodeURIComponent(tab.url); const currentUrl = encodeURIComponent(tab.url);
try { try {
const result = await chrome.storage.local.get({ const domain = await getOnyxDomain();
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN, const url = `${domain}${SIDE_PANEL_PATH}?user-prompt=${selectedText}`;
});
const url = `${
result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]
}${SIDE_PANEL_PATH}?user-prompt=${selectedText}`;
await openSidePanel(tab.id); await openSidePanel(tab.id);
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
@@ -72,14 +73,12 @@ async function sendToOnyx(info, tab) {
async function toggleNewTabOverride() { async function toggleNewTabOverride() {
try { try {
const result = await chrome.storage.local.get( const newValue = !(await getUseOnyxAsDefaultNewTab());
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB const updated = await setUseOnyxAsDefaultNewTab(newValue);
); if (!updated) {
const newValue = console.warn("New tab override is managed by policy; ignoring toggle");
!result[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]; return;
await chrome.storage.local.set({ }
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: newValue,
});
chrome.notifications.create({ chrome.notifications.create({
type: "basic", type: "basic",
@@ -176,28 +175,16 @@ async function sendActiveTabUrlToPanel() {
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === ACTIONS.GET_CURRENT_ONYX_DOMAIN) { if (request.action === ACTIONS.GET_CURRENT_ONYX_DOMAIN) {
chrome.storage.local.get( getOnyxDomain().then((domain) => {
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN }, sendResponse({ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: domain });
(result) => { });
sendResponse({
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]:
result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN],
});
}
);
return true; return true;
} }
if (request.action === ACTIONS.CLOSE_SIDE_PANEL) { if (request.action === ACTIONS.CLOSE_SIDE_PANEL) {
closeSidePanel(); closeSidePanel();
chrome.storage.local.get( getOnyxDomain().then((domain) => {
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN }, chrome.tabs.create({ url: `${domain}/auth/login`, active: true });
(result) => { });
chrome.tabs.create({
url: `${result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]}/auth/login`,
active: true,
});
}
);
return true; return true;
} }
if (request.action === ACTIONS.OPEN_SIDE_PANEL_WITH_INPUT) { if (request.action === ACTIONS.OPEN_SIDE_PANEL_WITH_INPUT) {
@@ -206,38 +193,34 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const windowId = sender.tab?.windowId; const windowId = sender.tab?.windowId;
if (tabId && windowId) { if (tabId && windowId) {
chrome.storage.local.get( getOnyxDomain().then((onyxDomain) => {
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN }, const encodedText = encodeUserPrompt(selectedText);
(result) => { const url = `${onyxDomain}${SIDE_PANEL_PATH}?user-prompt=${encodedText}`;
const encodedText = encodeUserPrompt(selectedText);
const onyxDomain = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
const url = `${onyxDomain}${SIDE_PANEL_PATH}?user-prompt=${encodedText}`;
chrome.storage.session.set({ chrome.storage.session.set({
pendingInput: { pendingInput: {
url: url,
pageUrl: pageUrl,
timestamp: Date.now(),
},
});
chrome.sidePanel
.open({ windowId })
.then(() => {
chrome.runtime.sendMessage({
action: ACTIONS.OPEN_ONYX_WITH_INPUT,
url: url, url: url,
pageUrl: pageUrl, pageUrl: pageUrl,
timestamp: Date.now(),
},
});
chrome.sidePanel
.open({ windowId })
.then(() => {
chrome.runtime.sendMessage({
action: ACTIONS.OPEN_ONYX_WITH_INPUT,
url: url,
pageUrl: pageUrl,
});
})
.catch((error) => {
console.error(
"[Onyx SW] Error opening side panel with text:",
error
);
}); });
} })
); .catch((error) => {
console.error(
"[Onyx SW] Error opening side panel with text:",
error
);
});
});
} else { } else {
console.error("[Onyx SW] Missing tabId or windowId"); console.error("[Onyx SW] Missing tabId or windowId");
} }
@@ -255,15 +238,12 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
}); });
chrome.storage.onChanged.addListener((changes, namespace) => { chrome.storage.onChanged.addListener((changes, namespace) => {
if ( if (isNewTabOverrideChange(changes, namespace)) {
namespace === "local" &&
changes[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]
) {
const newValue = const newValue =
changes[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB] changes[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]
.newValue; .newValue;
if (newValue === false) { if (namespace === "local" && newValue === false) {
chrome.runtime.openOptionsPage(); chrome.runtime.openOptionsPage();
} }
} }
@@ -279,11 +259,7 @@ chrome.omnibox.setDefaultSuggestion({
chrome.omnibox.onInputEntered.addListener(async (text) => { chrome.omnibox.onInputEntered.addListener(async (text) => {
try { try {
const result = await chrome.storage.local.get({ const domain = await getOnyxDomain();
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN,
});
const domain = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
const searchUrl = `${domain}/chat?user-prompt=${encodeURIComponent(text)}`; const searchUrl = `${domain}/chat?user-prompt=${encodeURIComponent(text)}`;
chrome.tabs.update({ url: searchUrl }); chrome.tabs.update({ url: searchUrl });
+38 -35
View File
@@ -8,7 +8,12 @@ import {
hideErrorModal, hideErrorModal,
initErrorModal, initErrorModal,
} from "../utils/error-modal.js"; } from "../utils/error-modal.js";
import { getOnyxDomain } from "../utils/storage.js"; import {
getOnyxDomain,
getUseOnyxAsDefaultNewTab,
setUseOnyxAsDefaultNewTab,
isNewTabOverrideChange,
} from "../utils/storage.js";
(function () { (function () {
let mainIframe = document.getElementById("onyx-iframe"); let mainIframe = document.getElementById("onyx-iframe");
@@ -109,38 +114,26 @@ import { getOnyxDomain } from "../utils/storage.js";
}); });
} }
function checkOnyxPreference() { async function checkOnyxPreference() {
chrome.storage.local.get( let useOnyxAsDefaultNewTab = await getUseOnyxAsDefaultNewTab();
[
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB,
CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN,
],
(items) => {
let useOnyxAsDefaultNewTab =
items[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
if (useOnyxAsDefaultNewTab === undefined) { if (useOnyxAsDefaultNewTab === undefined) {
useOnyxAsDefaultNewTab = !!( useOnyxAsDefaultNewTab =
localStorage.getItem( localStorage.getItem(
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB
) === "1" ) === "1";
); await setUseOnyxAsDefaultNewTab(useOnyxAsDefaultNewTab);
chrome.storage.local.set({ }
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]:
useOnyxAsDefaultNewTab,
});
}
if (!useOnyxAsDefaultNewTab) { if (!useOnyxAsDefaultNewTab) {
chrome.tabs.update({ chrome.tabs.update({
url: "chrome://new-tab-page", url: "chrome://new-tab-page",
}); });
return; return;
} }
setIframeSrc(items[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN] + "/nrf"); const domain = await getOnyxDomain();
} setIframeSrc(domain + "/nrf");
);
} }
function loadThemeAndBackground() { function loadThemeAndBackground() {
@@ -209,19 +202,29 @@ import { getOnyxDomain } from "../utils/storage.js";
} }
chrome.storage.onChanged.addListener(function (changes, namespace) { chrome.storage.onChanged.addListener(function (changes, namespace) {
if (namespace === "local" && changes.useOnyxAsDefaultNewTab) { if (isNewTabOverrideChange(changes, namespace)) {
checkOnyxPreference(); checkOnyxPreference();
} }
}); });
window.addEventListener("message", function (event) { // Only accept messages from one of our own Onyx iframes, and only when
// the sender origin matches that iframe's src (fails closed).
function isTrustedIframeMessage(event) {
const frame = [mainIframe, preloadedIframe].find(
(f) => f && f.contentWindow === event.source
);
if (!frame) return false;
try { try {
if (event.origin !== getOnyxOrigin(mainIframe)) return; return getOnyxOrigin(frame) === event.origin;
} catch { } catch {
return; return false;
} }
}
window.addEventListener("message", function (event) {
if (!isTrustedIframeMessage(event)) return;
if (event.data.type === CHROME_MESSAGE.SET_DEFAULT_NEW_TAB) { if (event.data.type === CHROME_MESSAGE.SET_DEFAULT_NEW_TAB) {
chrome.storage.local.set({ useOnyxAsDefaultNewTab: event.data.value }); setUseOnyxAsDefaultNewTab(event.data.value);
} else if (event.data.type === CHROME_MESSAGE.ONYX_APP_LOADED) { } else if (event.data.type === CHROME_MESSAGE.ONYX_APP_LOADED) {
clearTimeout(iframeLoadTimeout); clearTimeout(iframeLoadTimeout);
hideErrorModal(); hideErrorModal();
+19
View File
@@ -259,6 +259,19 @@
color: var(--text-light-03); color: var(--text-light-03);
} }
.managed-notice {
font-size: 12px;
color: var(--text-light-03);
padding: 0 12px 12px;
display: none;
}
.input-field:disabled,
.toggle-switch input:disabled + .toggle-slider {
opacity: 0.5;
cursor: not-allowed;
}
.setting-divider { .setting-divider {
height: 1px; height: 1px;
background: var(--white-10); background: var(--white-10);
@@ -460,6 +473,9 @@
placeholder="https://cloud.onyx.app" placeholder="https://cloud.onyx.app"
/> />
</div> </div>
<div id="domainManagedNotice" class="managed-notice">
This setting is managed by your organization.
</div>
<div class="setting-divider"></div> <div class="setting-divider"></div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-row-content"> <div class="setting-row-content">
@@ -472,6 +488,9 @@
<span class="toggle-slider"></span> <span class="toggle-slider"></span>
</label> </label>
</div> </div>
<div id="newTabManagedNotice" class="managed-notice">
This setting is managed by your organization.
</div>
</div> </div>
</section> </section>
+71 -36
View File
@@ -1,11 +1,16 @@
import { CHROME_SPECIFIC_STORAGE_KEYS } from "../utils/constants.js";
import { import {
CHROME_SPECIFIC_STORAGE_KEYS, getOnyxDomain,
DEFAULT_ONYX_DOMAIN, getUseOnyxAsDefaultNewTab,
} from "../utils/constants.js"; getManagedSettings,
normalizeOnyxDomain,
} from "../utils/storage.js";
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const domainInput = document.getElementById("onyxDomain"); const domainInput = document.getElementById("onyxDomain");
const useOnyxAsDefaultToggle = document.getElementById("useOnyxAsDefault"); const useOnyxAsDefaultToggle = document.getElementById("useOnyxAsDefault");
const domainManagedNotice = document.getElementById("domainManagedNotice");
const newTabManagedNotice = document.getElementById("newTabManagedNotice");
const statusContainer = document.getElementById("statusContainer"); const statusContainer = document.getElementById("statusContainer");
const statusElement = document.getElementById("status"); const statusElement = document.getElementById("status");
const newTabButton = document.getElementById("newTab"); const newTabButton = document.getElementById("newTab");
@@ -29,49 +34,70 @@ document.addEventListener("DOMContentLoaded", function () {
} }
} }
function loadStoredValues() { let domainManaged = false;
chrome.storage.local.get( let newTabManaged = false;
{
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN, async function loadStoredValues() {
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: false, const managed = await getManagedSettings();
domainManaged =
typeof managed[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN] === "string";
newTabManaged =
typeof managed[
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB
] === "boolean";
const [domain, useOnyxAsDefault, themeResult] = await Promise.all([
getOnyxDomain(),
getUseOnyxAsDefaultNewTab(),
chrome.storage.local.get({
[CHROME_SPECIFIC_STORAGE_KEYS.THEME]: "dark", [CHROME_SPECIFIC_STORAGE_KEYS.THEME]: "dark",
}, }),
(result) => { ]);
if (domainInput)
domainInput.value = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
if (useOnyxAsDefaultToggle)
useOnyxAsDefaultToggle.checked =
result[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
currentTheme = result[CHROME_SPECIFIC_STORAGE_KEYS.THEME] || "dark"; if (domainInput) {
updateThemeIcon(currentTheme); domainInput.value = domain;
domainInput.disabled = domainManaged;
}
if (domainManagedNotice) {
domainManagedNotice.style.display = domainManaged ? "block" : "none";
}
if (useOnyxAsDefaultToggle) {
useOnyxAsDefaultToggle.checked = !!useOnyxAsDefault;
useOnyxAsDefaultToggle.disabled = newTabManaged;
}
if (newTabManagedNotice) {
newTabManagedNotice.style.display = newTabManaged ? "block" : "none";
}
document.body.className = currentTheme === "light" ? "light-theme" : ""; currentTheme = themeResult[CHROME_SPECIFIC_STORAGE_KEYS.THEME] || "dark";
} updateThemeIcon(currentTheme);
);
document.body.className = currentTheme === "light" ? "light-theme" : "";
} }
function saveSettings() { function saveSettings() {
const domain = domainInput.value.trim();
const useOnyxAsDefault = useOnyxAsDefaultToggle const useOnyxAsDefault = useOnyxAsDefaultToggle
? useOnyxAsDefaultToggle.checked ? useOnyxAsDefaultToggle.checked
: false; : false;
chrome.storage.local.set( const values = { [CHROME_SPECIFIC_STORAGE_KEYS.THEME]: currentTheme };
{ if (!domainManaged) {
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: domain, values[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN] = normalizeOnyxDomain(
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: domainInput.value
useOnyxAsDefault, );
[CHROME_SPECIFIC_STORAGE_KEYS.THEME]: currentTheme, }
}, if (!newTabManaged) {
() => { values[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB] =
showStatusMessage( useOnyxAsDefault;
useOnyxAsDefault }
? "Settings updated. Open a new tab to test it out. Click on the extension icon to bring up Onyx from any page."
: "Settings updated." chrome.storage.local.set(values, () => {
); showStatusMessage(
} useOnyxAsDefault
); ? "Settings updated. Open a new tab to test it out. Click on the extension icon to bring up Onyx from any page."
: "Settings updated."
);
});
} }
function showStatusMessage(message) { function showStatusMessage(message) {
@@ -124,8 +150,17 @@ document.addEventListener("DOMContentLoaded", function () {
clearTimeout(domainInput.saveTimeout); clearTimeout(domainInput.saveTimeout);
domainInput.saveTimeout = setTimeout(saveSettings, 1000); domainInput.saveTimeout = setTimeout(saveSettings, 1000);
}); });
domainInput.addEventListener("blur", () => {
domainInput.value = normalizeOnyxDomain(domainInput.value);
});
} }
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === "managed") {
loadStoredValues();
}
});
if (useOnyxAsDefaultToggle) { if (useOnyxAsDefaultToggle) {
useOnyxAsDefaultToggle.addEventListener("change", saveSettings); useOnyxAsDefaultToggle.addEventListener("change", saveSettings);
} }
+15
View File
@@ -197,6 +197,18 @@
input:checked + .toggle-slider:before { input:checked + .toggle-slider:before {
transform: translateX(20px); transform: translateX(20px);
} }
.toggle-switch input:disabled + .toggle-slider {
opacity: 0.5;
cursor: not-allowed;
}
.managed-notice {
font-size: 12px;
color: var(--text-light-03);
padding: 0 12px 12px;
display: none;
}
</style> </style>
</head> </head>
<body> <body>
@@ -218,6 +230,9 @@
<span class="toggle-slider"></span> <span class="toggle-slider"></span>
</label> </label>
</div> </div>
<div id="newTabManagedNotice" class="managed-notice">
Managed by your organization.
</div>
</div> </div>
<div class="button-group"> <div class="button-group">
+19 -10
View File
@@ -1,25 +1,34 @@
import { CHROME_SPECIFIC_STORAGE_KEYS } from "../utils/constants.js"; import {
getUseOnyxAsDefaultNewTab,
setUseOnyxAsDefaultNewTab,
isNewTabOverrideManaged,
} from "../utils/storage.js";
document.addEventListener("DOMContentLoaded", async function () { document.addEventListener("DOMContentLoaded", async function () {
const defaultNewTabToggle = document.getElementById("defaultNewTabToggle"); const defaultNewTabToggle = document.getElementById("defaultNewTabToggle");
const openSidePanelButton = document.getElementById("openSidePanel"); const openSidePanelButton = document.getElementById("openSidePanel");
const openOptionsButton = document.getElementById("openOptions"); const openOptionsButton = document.getElementById("openOptions");
const newTabManagedNotice = document.getElementById("newTabManagedNotice");
async function loadSetting() { async function loadSetting() {
const result = await chrome.storage.local.get({ const [value, managed] = await Promise.all([
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: false, getUseOnyxAsDefaultNewTab(),
}); isNewTabOverrideManaged(),
]);
if (defaultNewTabToggle) { if (defaultNewTabToggle) {
defaultNewTabToggle.checked = defaultNewTabToggle.checked = !!value;
result[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]; defaultNewTabToggle.disabled = managed;
}
if (newTabManagedNotice) {
newTabManagedNotice.style.display = managed ? "block" : "none";
} }
} }
async function toggleSetting() { async function toggleSetting() {
const currentValue = defaultNewTabToggle.checked; const updated = await setUseOnyxAsDefaultNewTab(
await chrome.storage.local.set({ defaultNewTabToggle.checked
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: currentValue, );
}); if (!updated) await loadSetting();
} }
async function openSidePanel() { async function openSidePanel() {
+19
View File
@@ -376,6 +376,19 @@
line-height: 1.4; line-height: 1.4;
} }
.managed-notice {
font-size: 13px;
color: var(--text-light-03);
margin-top: 8px;
display: none;
}
.input-field:disabled,
.toggle-switch input:disabled + .toggle-slider {
opacity: 0.5;
cursor: not-allowed;
}
.setting-divider { .setting-divider {
height: 1px; height: 1px;
background: var(--white-10); background: var(--white-10);
@@ -573,6 +586,9 @@
class="input-field" class="input-field"
placeholder="https://cloud.onyx.app" placeholder="https://cloud.onyx.app"
/> />
<div id="domainManagedNotice" class="managed-notice">
This setting is managed by your organization.
</div>
</div> </div>
<div class="button-group"> <div class="button-group">
@@ -602,6 +618,9 @@
</label> </label>
</div> </div>
</div> </div>
<div id="newTabManagedNotice" class="managed-notice">
This setting is managed by your organization.
</div>
<div class="button-group"> <div class="button-group">
<button class="btn btn-secondary" id="backBtn">Back</button> <button class="btn btn-secondary" id="backBtn">Back</button>
+75 -36
View File
@@ -1,11 +1,11 @@
import { import { CHROME_SPECIFIC_STORAGE_KEYS } from "../utils/constants.js";
CHROME_SPECIFIC_STORAGE_KEYS, import { getManagedSettings, normalizeOnyxDomain } from "../utils/storage.js";
DEFAULT_ONYX_DOMAIN,
} from "../utils/constants.js";
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const domainInput = document.getElementById("onyxDomain"); const domainInput = document.getElementById("onyxDomain");
const useOnyxAsDefaultToggle = document.getElementById("useOnyxAsDefault"); const useOnyxAsDefaultToggle = document.getElementById("useOnyxAsDefault");
const domainManagedNotice = document.getElementById("domainManagedNotice");
const newTabManagedNotice = document.getElementById("newTabManagedNotice");
const continueBtn = document.getElementById("continueBtn"); const continueBtn = document.getElementById("continueBtn");
const backBtn = document.getElementById("backBtn"); const backBtn = document.getElementById("backBtn");
const finishBtn = document.getElementById("finishBtn"); const finishBtn = document.getElementById("finishBtn");
@@ -18,6 +18,8 @@ document.addEventListener("DOMContentLoaded", function () {
let currentStep = 1; let currentStep = 1;
let currentTheme = "dark"; let currentTheme = "dark";
let domainManaged = false;
let newTabManaged = false;
// Initialize theme based on system preference or stored value // Initialize theme based on system preference or stored value
function initTheme() { function initTheme() {
@@ -121,47 +123,79 @@ document.addEventListener("DOMContentLoaded", function () {
} }
function handleFinish() { function handleFinish() {
const domain = domainInput.value.trim() || DEFAULT_ONYX_DOMAIN;
const useOnyxAsDefault = useOnyxAsDefaultToggle.checked; const useOnyxAsDefault = useOnyxAsDefaultToggle.checked;
chrome.storage.local.set( const values = {
{ [CHROME_SPECIFIC_STORAGE_KEYS.THEME]: currentTheme,
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: domain, [CHROME_SPECIFIC_STORAGE_KEYS.ONBOARDING_COMPLETE]: true,
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: };
useOnyxAsDefault, if (!domainManaged) {
[CHROME_SPECIFIC_STORAGE_KEYS.THEME]: currentTheme, values[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN] = normalizeOnyxDomain(
[CHROME_SPECIFIC_STORAGE_KEYS.ONBOARDING_COMPLETE]: true, domainInput.value
}, );
() => { }
// Open a new tab if they enabled the new tab feature, otherwise just close if (!newTabManaged) {
if (useOnyxAsDefault) { values[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB] =
chrome.tabs.create({}, () => { useOnyxAsDefault;
window.close(); }
});
} else { chrome.storage.local.set(values, () => {
// Open a new tab if they enabled the new tab feature, otherwise just close
if (useOnyxAsDefault) {
chrome.tabs.create({}, () => {
window.close(); window.close();
} });
} else {
window.close();
} }
); });
}
// Lock controls that are set by policy. Unmanaged controls keep whatever
// the user has entered so far.
async function applyManagedSettings() {
const managed = await getManagedSettings();
const managedDomain = managed[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
const managedNewTab =
managed[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
domainManaged = typeof managedDomain === "string";
newTabManaged = typeof managedNewTab === "boolean";
domainInput.disabled = domainManaged;
domainManagedNotice.style.display = domainManaged ? "block" : "none";
if (domainManaged) {
domainInput.value = normalizeOnyxDomain(managedDomain);
}
useOnyxAsDefaultToggle.disabled = newTabManaged;
newTabManagedNotice.style.display = newTabManaged ? "block" : "none";
if (newTabManaged) {
useOnyxAsDefaultToggle.checked = managedNewTab;
}
} }
// Load any existing values (in case user returns to this page) // Load any existing values (in case user returns to this page)
function loadStoredValues() { async function loadStoredValues() {
chrome.storage.local.get( const result = await chrome.storage.local.get({
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: "",
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: "", [CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: true,
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: true, });
},
(result) => { if (result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]) {
if (result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]) { domainInput.value = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
domainInput.value = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]; }
} useOnyxAsDefaultToggle.checked =
useOnyxAsDefaultToggle.checked = result[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
result[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
} await applyManagedSettings();
);
} }
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === "managed") {
applyManagedSettings();
}
});
if (themeToggle) { if (themeToggle) {
themeToggle.addEventListener("click", toggleTheme); themeToggle.addEventListener("click", toggleTheme);
} }
@@ -185,6 +219,11 @@ document.addEventListener("DOMContentLoaded", function () {
handleContinue(); handleContinue();
} }
}); });
domainInput.addEventListener("blur", () => {
if (domainInput.value.trim()) {
domainInput.value = normalizeOnyxDomain(domainInput.value);
}
});
} }
initTheme(); initTheme();
+31 -29
View File
@@ -1,8 +1,9 @@
import { ACTIONS } from "./constants.js";
import { import {
CHROME_SPECIFIC_STORAGE_KEYS, getOnyxDomain,
DEFAULT_ONYX_DOMAIN, isNewTabOverrideManaged,
ACTIONS, setUseOnyxAsDefaultNewTab,
} from "./constants.js"; } from "./storage.js";
const errorModalHTML = ` const errorModalHTML = `
<div id="error-modal"> <div id="error-modal">
@@ -277,8 +278,16 @@ export function initErrorModal() {
chrome.runtime.openOptionsPage(); chrome.runtime.openOptionsPage();
}); });
isNewTabOverrideManaged().then((managed) => {
if (managed) disableOverrideButton.style.display = "none";
});
disableOverrideButton.addEventListener("click", () => { disableOverrideButton.addEventListener("click", () => {
chrome.storage.local.set({ useOnyxAsDefaultNewTab: false }, () => { setUseOnyxAsDefaultNewTab(false).then((updated) => {
if (!updated) {
console.warn("New tab override is managed by policy; not disabled.");
return;
}
chrome.tabs.update({ url: "chrome://new-tab-page" }); chrome.tabs.update({ url: "chrome://new-tab-page" });
}); });
}); });
@@ -325,37 +334,30 @@ export function initAuthModal() {
openAuthButton.addEventListener("click", (e) => { openAuthButton.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
chrome.storage.local.get( getOnyxDomain().then((onyxDomain) => {
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN }, chrome.runtime.sendMessage({ action: ACTIONS.CLOSE_SIDE_PANEL }, () => {
(result) => { if (chrome.runtime.lastError) {
const onyxDomain = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]; console.error(
chrome.runtime.sendMessage( "Error closing side panel:",
{ action: ACTIONS.CLOSE_SIDE_PANEL }, chrome.runtime.lastError
() => { );
}
chrome.tabs.create(
{
url: `${onyxDomain}/auth/login`,
active: true,
},
(_) => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error( console.error(
"Error closing side panel:", "Error opening auth tab:",
chrome.runtime.lastError chrome.runtime.lastError
); );
} }
chrome.tabs.create(
{
url: `${onyxDomain}/auth/login`,
active: true,
},
(_) => {
if (chrome.runtime.lastError) {
console.error(
"Error opening auth tab:",
chrome.runtime.lastError
);
}
}
);
} }
); );
} });
); });
}); });
} }
} }
+78 -5
View File
@@ -3,22 +3,95 @@ import {
CHROME_SPECIFIC_STORAGE_KEYS, CHROME_SPECIFIC_STORAGE_KEYS,
} from "./constants.js"; } from "./constants.js";
export function normalizeOnyxDomain(domain) {
if (typeof domain !== "string") return DEFAULT_ONYX_DOMAIN;
const trimmed = domain.trim().replace(/\/+$/, "");
return trimmed || DEFAULT_ONYX_DOMAIN;
}
// Managed storage is populated by enterprise policy (see managed_schema.json).
// It is read-only and takes precedence over anything the user sets locally.
async function getManaged(keys) {
if (!chrome.storage.managed) return {};
try {
return await chrome.storage.managed.get(keys);
} catch (error) {
console.error(
"Failed to read managed storage; using local settings",
error
);
return {};
}
}
export async function getManagedSettings() {
return getManaged([
CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN,
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB,
]);
}
export async function isDomainManaged() {
const managed = await getManaged(CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN);
return typeof managed[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN] === "string";
}
export async function isNewTabOverrideManaged() {
const managed = await getManaged(
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB
);
return (
typeof managed[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB] ===
"boolean"
);
}
export async function getOnyxDomain() { export async function getOnyxDomain() {
const managed = await getManaged(CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN);
if (typeof managed[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN] === "string") {
return normalizeOnyxDomain(
managed[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]
);
}
const result = await chrome.storage.local.get({ const result = await chrome.storage.local.get({
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN, [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN,
}); });
return result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]; return normalizeOnyxDomain(result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]);
} }
export function setOnyxDomain(domain, callback) { export function setOnyxDomain(domain, callback) {
chrome.storage.local.set( chrome.storage.local.set(
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: domain }, { [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: normalizeOnyxDomain(domain) },
callback callback
); );
} }
export function getOnyxDomainSync() { // Returns undefined when the user has never chosen, so callers can fall back
return new Promise((resolve) => { // to their own default.
getOnyxDomain(resolve); export async function getUseOnyxAsDefaultNewTab() {
const managed = await getManaged(
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB
);
const managedValue =
managed[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
if (typeof managedValue === "boolean") return managedValue;
const result = await chrome.storage.local.get(
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB
);
return result[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
}
export async function setUseOnyxAsDefaultNewTab(value) {
if (await isNewTabOverrideManaged()) return false;
await chrome.storage.local.set({
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: !!value,
}); });
return true;
}
export function isNewTabOverrideChange(changes, namespace) {
return (
(namespace === "local" || namespace === "managed") &&
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB in changes
);
} }