Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions apps/roam/src/components/Export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import createPage from "roamjs-components/writes/createPage";
import { createInitialTldrawProps } from "~/utils/createInitialTldrawProps";
import { isCanvasPage as checkIfCanvasPage } from "~/utils/isCanvasPage";
import sendErrorEmail from "~/utils/sendErrorEmail";
import { getSetting, setSetting } from "~/utils/settings";

const ExportProgress = ({ id }: { id: string }) => {
const [progress, setProgress] = useState(0);
Expand Down Expand Up @@ -115,8 +116,8 @@ const ExportDialog: ExportDialogComponent = ({
isExportDiscourseGraph = false,
initialPanel,
}) => {
const [selectedRepo, setSelectedRepo] = useState(
localStorageGet("selected-repo"),
const [selectedRepo, setSelectedRepo] = useState<string>(
getSetting<string>("selected-repo", ""),
);
const exportId = useMemo(() => nanoid(), []);
useEffect(() => {
Expand Down Expand Up @@ -169,8 +170,10 @@ const ExportDialog: ExportDialogComponent = ({
discourseGraphEnabled as boolean,
);
const [gitHubAccessToken, setGitHubAccessToken] = useState<string | null>(
localStorageGet("oauth-github"),
getSetting<string | null>("oauth-github", null),
);

console.log("gitHubAccessToken", gitHubAccessToken);
const [canSendToGitHub, setCanSendToGitHub] = useState(false);

const writeFileToRepo = async ({
Expand Down Expand Up @@ -199,7 +202,7 @@ const ExportDialog: ExportDialogComponent = ({
if (response.status === 401) {
setGitHubAccessToken(null);
setError("Authentication failed. Please log in again.");
localStorageSet("oauth-github", "");
setSetting("oauth-github", "");
return { status: 401 };
}
return { status: response.status };
Expand Down
165 changes: 119 additions & 46 deletions apps/roam/src/components/ExportGithub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import MenuItemSelect from "roamjs-components/components/MenuItemSelect";
import apiGet from "roamjs-components/util/apiGet";
import apiPost from "roamjs-components/util/apiPost";
import { getNodeEnv } from "roamjs-components/util/env";
import localStorageGet from "roamjs-components/util/localStorageGet";
import localStorageSet from "roamjs-components/util/localStorageSet";
import { getSetting, setSetting } from "~/utils/settings";

type UserReposResponse = {
data: [
Expand Down Expand Up @@ -57,56 +56,106 @@ export const ExportGithub = ({
const showGitHubLogin = isGitHubAppInstalled && !gitHubAccessToken;
const repoSelectEnabled = isGitHubAppInstalled && gitHubAccessToken;

const isDev = useMemo(() => getNodeEnv() === "development", []);

// const isDev = useMemo(() => getNodeEnv() === "development", []);
const isDev = false;
const setRepo = (repo: string) => {
setSelectedRepo(repo);
localStorageSet("selected-repo", repo);
setSetting("selected-repo", repo);
};

const handleReceivedAccessToken = (token: string) => {
localStorageSet("oauth-github", token);
console.log("Received GitHub token:", !!token);

// Store in both localStorage and extension settings for maximum compatibility
localStorage.setItem("oauth-github", token);
setSetting("oauth-github", token);

setGitHubAccessToken(token);
setClickedInstall(false);
authWindow.current?.close();
};

const fetchAndSetInstallation = useCallback(async () => {
try {
// Try to get token directly from both sources
const tokenFromSettings = getSetting<string>("oauth-github", "");
const tokenFromLocalStorage = localStorage.getItem("oauth-github");
const token = tokenFromSettings || tokenFromLocalStorage || "";

console.log(
"Using GitHub token from:",
tokenFromSettings
? "extension settings"
: tokenFromLocalStorage
? "localStorage"
: "nowhere",
);

if (!token) {
console.error("No GitHub token found in any storage location");
return false;
}

const res = await apiGet<{ installations: { app_id: number }[] }>({
domain: "https://api.github.com",
path: "user/installations",
headers: {
Authorization: `token ${localStorageGet("oauth-github")}`,
Authorization: `token ${token}`,
},
});

const installations = res.installations;
const APP_ID = isDev ? 882491 : 312167; // TODO - pull from process.env.GITHUB_APP_ID
const isAppInstalled = installations.some(
(installation) => installation.app_id === APP_ID,
);

console.log(
"GitHub app installations:",
installations.length,
"App ID:",
APP_ID,
);
setIsGitHubAppInstalled(isAppInstalled);
return isAppInstalled;
} catch (error) {
const e = error as Error;
console.error("GitHub installation check error:", e.message);

if (e.message === "Bad credentials") {
setGitHubAccessToken(null);
localStorageSet("oauth-github", "");
setSetting("oauth-github", "");
localStorage.removeItem("oauth-github");
}
return false;
}
}, []);

// listen for messages from the auth window
useEffect(() => {
const otp = nanoid().replace(/_/g, "-");
const key = nanoid().replace(/_/g, "-");
const state = `github_${otp}_${key}`;
setState(state);
// Create a stable state value that persists between renders
if (!state) {
const otp = nanoid().replace(/_/g, "-");
const key = nanoid().replace(/_/g, "-");
const newState = `github_${otp}_${key}`;
console.log("Setting GitHub OAuth state:", newState);
setState(newState);
// Store state in localStorage to preserve it across page refreshes
localStorage.setItem("github-oauth-state", newState);
}

const handleGitHubAuthMessage = (event: MessageEvent) => {
const targetOrigin = isDev
? "https://samepage.ngrok.io"
: "https://samepage.network";
console.log(
"Received auth message:",
!!event.data,
"from:",
event.origin,
"expected:",
targetOrigin,
);
if (event.data && event.origin === targetOrigin) {
handleReceivedAccessToken(event.data);
}
Expand All @@ -121,7 +170,7 @@ export const ExportGithub = ({
window.removeEventListener("message", handleGitHubAuthMessage);
}
};
}, [isVisible]);
}, [isVisible, state]);

// check for installation
useEffect(() => {
Expand Down Expand Up @@ -156,6 +205,62 @@ export const ExportGithub = ({
}
}, [gitHubAccessToken, isGitHubAppInstalled, selectedRepo]);

const handleAuthButtonClick = async () => {
// Get the stored state or create a new one
const currentState =
state || localStorage.getItem("github-oauth-state") || "";
console.log("Using OAuth state:", currentState);

const params = isDev
? `client_id=Iv1.4bf062a6c6636672&state=${currentState}`
: `client_id=Iv1.e7e282a385b7b2da&state=${currentState}`;

authWindow.current = window.open(
`https://github.com/login/oauth/authorize?${params}`,
"_blank",
`width=${WINDOW_WIDTH}, height=${WINDOW_HEIGHT}, top=${WINDOW_TOP}, left=${WINDOW_LEFT}`,
);

let attemptCount = 0;
const check = () => {
if (attemptCount < 30) {
const apiDomain = isDev
? "https://api.samepage.ngrok.io"
: "https://api.samepage.network";

console.log(
`Attempt ${attemptCount + 1}/30: Checking for token at ${apiDomain}/access-token`,
);

apiPost({
path: "access-token",
domain: apiDomain,
data: { state: currentState },
})
.then((r) => {
console.log(
"Token API response:",
r.accessToken ? "has token" : "no token",
);
if (r.accessToken) {
handleReceivedAccessToken(r.accessToken);
} else {
attemptCount++;
setTimeout(check, 1000);
}
})
.catch((err) => {
console.error("Token check error:", err);
attemptCount++;
setTimeout(check, 1000);
});
} else {
setError("Something went wrong. Please try again or contact support.");
}
};
setTimeout(check, 1500);
};

if (!isVisible) return null;
return (
<div className="mb-4 flex">
Expand Down Expand Up @@ -197,39 +302,7 @@ export const ExportGithub = ({
text="Authorize"
icon="key"
intent="primary"
onClick={async () => {
const params = isDev
? `client_id=Iv1.4bf062a6c6636672&state=${state}`
: `client_id=Iv1.e7e282a385b7b2da&state=${state}`;
authWindow.current = window.open(
`https://github.com/login/oauth/authorize?${params}`,
"_blank",
`width=${WINDOW_WIDTH}, height=${WINDOW_HEIGHT}, top=${WINDOW_TOP}, left=${WINDOW_LEFT}`,
);

let attemptCount = 0;
const check = () => {
if (attemptCount < 30) {
apiPost({
path: "access-token",
domain: isDev
? "https://api.samepage.ngrok.io"
: "https://api.samepage.network",
data: { state },
}).then((r) => {
if (r.accessToken) {
handleReceivedAccessToken(r.accessToken);
} else {
attemptCount++;
setTimeout(check, 1000);
}
});
} else {
setError("Something went wrong. Please contact support.");
}
};
setTimeout(check, 1500);
}}
onClick={handleAuthButtonClick}
/>
)}
{repoSelectEnabled && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import getDiscourseNodes from "~/utils/getDiscourseNodes";
import { getConditionLabels } from "~/utils/conditionToDatalog";
import { formatHexColor } from "./DiscourseNodeCanvasSettings";
import posthog from "posthog-js";
import { getRoamJSSetting, setRoamJSSetting } from "~/utils/settings";

const DEFAULT_SELECTED_RELATION = {
display: "none",
Expand Down Expand Up @@ -868,7 +869,7 @@ export const RelationEditPanel = ({
style={{ marginRight: 8 }}
/>
</Tooltip>
{!!localStorage.getItem("roamjs:discourse-relation-copy") && (
{!!getRoamJSSetting("discourse-relation-copy") && (
<Tooltip content={"Paste Relation"}>
<Button
minimal
Expand All @@ -877,8 +878,7 @@ export const RelationEditPanel = ({
style={{ marginRight: 8 }}
onClick={() => {
elementsRef.current[tab] = JSON.parse(
localStorage.getItem("roamjs:discourse-relation-copy") ||
"{}",
getRoamJSSetting("discourse-relation-copy", "{}") || "{}",
).map((n: { data: { id: string } }) =>
n.data.id === "source"
? {
Expand Down Expand Up @@ -910,8 +910,8 @@ export const RelationEditPanel = ({
disabled={loading}
onClick={() => {
saveCyToElementRef(tab);
localStorage.setItem(
"roamjs:discourse-relation-copy",
setRoamJSSetting(
"discourse-relation-copy",
JSON.stringify(elementsRef.current[tab]),
);
renderToast({
Expand Down
4 changes: 4 additions & 0 deletions apps/roam/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import discourseGraphStyles from "./styles/discourseGraphStyles.css";
import posthog from "posthog-js";
import getDiscourseNodes from "./utils/getDiscourseNodes";
import { initFeedbackWidget } from "./components/BirdEatsBugs";
import migrateSettings from "./utils/migrateSettings";

const initPostHog = () => {
posthog.init("phc_SNMmBqwNfcEpNduQ41dBUjtGNEUEKAy6jTn63Fzsrax", {
Expand Down Expand Up @@ -50,6 +51,9 @@ const initPostHog = () => {
export const DEFAULT_CANVAS_PAGE_FORMAT = "Canvas/*";

export default runExtension(async (onloadArgs) => {
// Migrate settings from localStorage to extension settings API
migrateSettings();

const isEncrypted = window.roamAlphaAPI.graph.isEncrypted;
const isOffline = window.roamAlphaAPI.graph.type === "offline";
if (!isEncrypted && !isOffline) {
Expand Down
38 changes: 38 additions & 0 deletions apps/roam/src/utils/migrateSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { getSetting, setRoamJSSetting } from "./settings";
const SETTINGS_TO_MIGRATE = ["selected-repo", "oauth-github"];

const ROAMJS_PREFIXED_SETTINGS = ["discourse-relation-copy"];

/**
* Migrates settings from localStorage to extension settings API
* Call this on plugin initialization
*/
export default function migrateSettings(): void {
// Migrate standard settings
SETTINGS_TO_MIGRATE.forEach((key) => {
// getSetting will automatically migrate from localStorage if needed
getSetting(key);
});

// Migrate roamjs: prefixed settings
ROAMJS_PREFIXED_SETTINGS.forEach((key) => {
const localKey = `roamjs:${key}`;
const localValue = localStorage.getItem(localKey);

if (localValue !== null) {
try {
// Try to parse as JSON if possible
const parsedValue = JSON.parse(localValue);
setRoamJSSetting(key, parsedValue);
} catch {
// If not JSON, store as string
setRoamJSSetting(key, localValue);
}

// Clean up localStorage
localStorage.removeItem(localKey);
}
});

console.log("Settings migration completed");
}
Loading