diff --git a/entrypoints/utils/jira.ts b/entrypoints/utils/jira.ts index 11ae1b4..5496f11 100644 --- a/entrypoints/utils/jira.ts +++ b/entrypoints/utils/jira.ts @@ -5,7 +5,7 @@ import { apiClient } from "./api"; import { getCurrentTimeEntry } from "./timeEntries"; import type { CreateTimeEntryBody } from "@solidtime/api"; -import { accessToken } from "./oauth"; +import { accessToken, ready } from "./oauth"; import { dayjs } from "./dayjs"; export interface JiraIssueInfo { @@ -317,6 +317,10 @@ async function handleJiraTrackingClick( button.style.cursor = "not-allowed"; try { + // Ensure instance config + tokens are loaded from shared storage before + // building the API client (content scripts load these asynchronously). + await ready; + if (!accessToken.value) { alert("Please log in to Solidtime first by clicking the extension icon"); return; diff --git a/entrypoints/utils/linear.ts b/entrypoints/utils/linear.ts index eaa18b3..cc63ca8 100644 --- a/entrypoints/utils/linear.ts +++ b/entrypoints/utils/linear.ts @@ -5,7 +5,7 @@ import { apiClient } from "./api"; import { getCurrentTimeEntry } from "./timeEntries"; import type { CreateTimeEntryBody } from "@solidtime/api"; -import { accessToken } from "./oauth"; +import { accessToken, ready } from "./oauth"; import { dayjs } from "./dayjs"; export interface LinearIssueInfo { @@ -401,6 +401,10 @@ async function handleTrackingClick( button.style.cursor = "not-allowed"; try { + // Ensure instance config + tokens are loaded from shared storage before + // building the API client (content scripts load these asynchronously). + await ready; + if (!accessToken.value) { alert("Please log in to Solidtime first by clicking the extension icon"); return; diff --git a/entrypoints/utils/oauth.ts b/entrypoints/utils/oauth.ts index 9a3e7d0..6bbb876 100644 --- a/entrypoints/utils/oauth.ts +++ b/entrypoints/utils/oauth.ts @@ -1,44 +1,60 @@ -import { computed, ref } from "vue"; -import { useStorage } from "@vueuse/core"; - -export const endpoint = useStorage( - "instance_endpoint", - "https://app.solidtime.io", -); -export const clientId = useStorage( - "instance_client_id", - "019b27e8-a52a-71d8-8d67-071cff97f315", -); - -// Use chrome.storage for tokens (survives popup closing) +import { computed, ref, watch } from "vue"; + +const DEFAULT_ENDPOINT = "https://app.solidtime.io"; +const DEFAULT_CLIENT_ID = "019b27e8-a52a-71d8-8d67-071cff97f315"; + +export const endpoint = ref(DEFAULT_ENDPOINT); +export const clientId = ref(DEFAULT_CLIENT_ID); export const accessToken = ref(""); export const refreshToken = ref(""); -// Load tokens from chrome.storage on init -async function loadTokens() { +let loaded = false; + +// Load instance config + tokens from chrome.storage on init. Exported so +// callers can await it before making the first authenticated request. +export const ready = (async function loadTokens() { const result = await browser.storage.local.get([ + "instance_endpoint", + "instance_client_id", "access_token", "refresh_token", ]); - accessToken.value = result.access_token || ""; - refreshToken.value = result.refresh_token || ""; -} + endpoint.value = (result.instance_endpoint as string) || DEFAULT_ENDPOINT; + clientId.value = (result.instance_client_id as string) || DEFAULT_CLIENT_ID; + accessToken.value = (result.access_token as string) || ""; + refreshToken.value = (result.refresh_token as string) || ""; + loaded = true; +})(); + +// Persist instance config changes (made in the popup settings modal) back to +// the shared storage so content scripts pick them up. +watch(endpoint, (value) => { + if (loaded) browser.storage.local.set({ instance_endpoint: value }); +}); +watch(clientId, (value) => { + if (loaded) browser.storage.local.set({ instance_client_id: value }); +}); -// Watch for storage changes (from background script) +// Watch for storage changes (from other contexts / background script) browser.storage.onChanged.addListener((changes, area) => { if (area === "local") { + if (changes.instance_endpoint) { + endpoint.value = + (changes.instance_endpoint.newValue as string) || DEFAULT_ENDPOINT; + } + if (changes.instance_client_id) { + clientId.value = + (changes.instance_client_id.newValue as string) || DEFAULT_CLIENT_ID; + } if (changes.access_token) { - accessToken.value = changes.access_token.newValue || ""; + accessToken.value = (changes.access_token.newValue as string) || ""; } if (changes.refresh_token) { - refreshToken.value = changes.refresh_token.newValue || ""; + refreshToken.value = (changes.refresh_token.newValue as string) || ""; } } }); -// Initialize -loadTokens(); - // Use browser.identity.getRedirectURL() which works for both Firefox and Chrome export const getRedirectUrl = () => browser.identity.getRedirectURL(); diff --git a/entrypoints/utils/plane.ts b/entrypoints/utils/plane.ts index fe8eced..701291a 100644 --- a/entrypoints/utils/plane.ts +++ b/entrypoints/utils/plane.ts @@ -5,7 +5,7 @@ import { apiClient } from "./api"; import { getCurrentTimeEntry } from "./timeEntries"; import type { CreateTimeEntryBody } from "@solidtime/api"; -import { accessToken } from "./oauth"; +import { accessToken, ready } from "./oauth"; import { dayjs } from "./dayjs"; export interface PlaneIssueInfo { @@ -299,6 +299,10 @@ async function handlePlaneTrackingClick( button.style.cursor = "not-allowed"; try { + // Ensure instance config + tokens are loaded from shared storage before + // building the API client (content scripts load these asynchronously). + await ready; + if (!accessToken.value) { alert("Please log in to Solidtime first by clicking the extension icon"); return;