aethex-forge/client/contexts/DiscordActivityContext.tsx
sirpiglr 6454eb224e Update Discord activity features and documentation
Refactors Discord activity integration to use new SDK authentication methods, updates API endpoints for token exchange and OAuth, and revises documentation for clarity.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: e54ac654-1faa-42a2-b227-b7df7908c2b7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/qPXTzuE
Replit-Helium-Checkpoint-Created: true
2025-12-08 00:44:36 +00:00

250 lines
7.4 KiB
TypeScript

import React, { createContext, useContext, useEffect, useState } from "react";
const API_BASE = import.meta.env.VITE_API_BASE || "";
interface DiscordUser {
id: string;
discord_id: string;
full_name: string | null;
username: string | null;
avatar_url: string | null;
bio: string | null;
user_type: string | null;
primary_arm: string | null;
}
interface DiscordActivityContextType {
isActivity: boolean;
isLoading: boolean;
user: DiscordUser | null;
error: string | null;
discordSdk: any | null;
}
const DiscordActivityContext = createContext<DiscordActivityContextType>({
isActivity: false,
isLoading: false,
user: null,
error: null,
discordSdk: null,
});
export const useDiscordActivity = () => {
const context = useContext(DiscordActivityContext);
if (!context) {
throw new Error(
"useDiscordActivity must be used within DiscordActivityProvider",
);
}
return context;
};
interface DiscordActivityProviderProps {
children: React.ReactNode;
}
export const DiscordActivityProvider: React.FC<
DiscordActivityProviderProps
> = ({ children }) => {
const [isActivity, setIsActivity] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [user, setUser] = useState<DiscordUser | null>(null);
const [error, setError] = useState<string | null>(null);
const [discordSdk, setDiscordSdk] = useState<any>(null);
const [auth, setAuth] = useState<any>(null);
useEffect(() => {
const initializeActivity = async () => {
// Check if we're running inside a Discord Activity
// Discord passes frame_id as a query parameter when launching an Activity
if (typeof window === "undefined") {
return; // Skip on server-side
}
const searchParams = new URLSearchParams(window.location.search);
const frameId = searchParams.get("frame_id");
const isInDiscordActivity = frameId !== null;
console.log("[Discord Activity] Checking for Discord context...", {
frameId,
isInDiscordActivity,
});
// If we're NOT in Discord Activity, exit early - don't load Discord SDK
if (!isInDiscordActivity) {
console.log(
"[Discord Activity] Not in Discord Activity - skipping SDK load",
);
setIsActivity(false);
setIsLoading(false);
return;
}
// Only initialize Discord SDK if we're actually in a Discord Activity
if (isInDiscordActivity) {
try {
setIsActivity(true);
setIsLoading(true);
// Import the Discord SDK dynamically
const { DiscordSDK } = await import("@discord/embedded-app-sdk");
const clientId =
import.meta.env.VITE_DISCORD_CLIENT_ID || "578971245454950421";
console.log(
"[Discord Activity] Creating SDK with clientId:",
clientId,
);
const sdk = new DiscordSDK(clientId);
setDiscordSdk(sdk);
// Wait for SDK to be ready
console.log("[Discord Activity] Waiting for SDK to be ready...");
await sdk.ready();
console.log("[Discord Activity] SDK is ready");
// Authorize the user to get an access token
console.log("[Discord Activity] Authorizing user...");
const { code } = await sdk.commands.authorize({
client_id: clientId,
response_type: "code",
state: "",
scope: ["identify", "guilds"],
prompt: "none",
});
console.log(
"[Discord Activity] Got authorization code, exchanging for token...",
);
// Exchange code for access token via our backend
const tokenResponse = await fetch(`${API_BASE}/api/discord/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code }),
});
if (!tokenResponse.ok) {
const errorData = await tokenResponse.json();
const errMsg = errorData.error || "Failed to exchange code";
console.error(
"[Discord Activity] Token exchange failed:",
errMsg,
);
setError(errMsg);
setIsLoading(false);
return;
}
const tokenData = await tokenResponse.json();
const access_token = tokenData.access_token;
console.log(
"[Discord Activity] Got access token, authenticating with SDK...",
);
// Authenticate with SDK using the access token
const authResult = await sdk.commands.authenticate({
access_token,
});
if (!authResult) {
console.error("[Discord Activity] SDK authentication failed");
setError("SDK authentication failed");
setIsLoading(false);
return;
}
console.log(
"[Discord Activity] Authenticated with SDK, fetching user profile...",
);
setAuth(authResult);
// Get user info using the access token
const userResponse = await fetch(
"https://discord.com/api/v10/users/@me",
{
headers: {
Authorization: `Bearer ${access_token}`,
},
},
);
if (!userResponse.ok) {
console.error("[Discord Activity] Failed to fetch user profile");
setError("Failed to fetch user profile");
setIsLoading(false);
return;
}
const discordUserData = await userResponse.json();
console.log(
"[Discord Activity] User profile fetched:",
discordUserData.username,
);
// Store the user data
const userData: DiscordUser = {
id: discordUserData.id,
discord_id: discordUserData.id,
full_name:
discordUserData.global_name || discordUserData.username,
username: discordUserData.username,
avatar_url: discordUserData.avatar
? `https://cdn.discordapp.com/avatars/${discordUserData.id}/${discordUserData.avatar}.png`
: null,
bio: null,
user_type: "community_member",
primary_arm: "labs",
};
setUser(userData);
setError(null);
console.log("[Discord Activity] User authenticated successfully");
} catch (err: any) {
console.error("Discord Activity initialization error:", err);
console.error("Error details:", {
message: err?.message,
code: err?.code,
stack: err?.stack,
});
setError(
`${err?.message || "Failed to initialize Discord Activity"}. Check browser console for details.`,
);
} finally {
setIsLoading(false);
}
} else {
// Not in a Discord iframe
console.log(
"[Discord Activity] Not in Discord Activity context (no frame_id)",
);
setIsActivity(false);
setIsLoading(false);
}
};
initializeActivity();
}, []);
return (
<DiscordActivityContext.Provider
value={{
isActivity,
isLoading,
user,
error,
discordSdk,
}}
>
{children}
</DiscordActivityContext.Provider>
);
};
export default DiscordActivityContext;