mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-25 21:36:52 +08:00
feat(frontend): integrate with Reo.dev (#11251)
This commit is contained in:
parent
fbf0429434
commit
c62a6616db
14
docs/reo-init.js
Normal file
14
docs/reo-init.js
Normal file
@ -0,0 +1,14 @@
|
||||
// Reo.dev tracking initialization
|
||||
(function() {
|
||||
var e, t, n;
|
||||
e = "6bac7145b4ee6ec";
|
||||
t = function() {
|
||||
Reo.init({clientID: "6bac7145b4ee6ec"});
|
||||
};
|
||||
n = document.createElement("script");
|
||||
n.src = "https://static.reo.dev/" + e + "/reo.js";
|
||||
n.defer = true;
|
||||
n.onload = t;
|
||||
document.head.appendChild(n);
|
||||
})();
|
||||
|
||||
14
frontend/global.d.ts
vendored
14
frontend/global.d.ts
vendored
@ -1,4 +1,18 @@
|
||||
interface Window {
|
||||
__APP_MODE__?: "saas" | "oss";
|
||||
__GITHUB_CLIENT_ID__?: string | null;
|
||||
Reo?: {
|
||||
init: (config: { clientID: string }) => void;
|
||||
identify: (identity: {
|
||||
username: string;
|
||||
type: "github" |"email";
|
||||
other_identities?: Array<{
|
||||
username: string;
|
||||
type: "github" | "email";
|
||||
}>;
|
||||
firstname?: string;
|
||||
lastname?: string;
|
||||
company?: string;
|
||||
}) => void;
|
||||
};
|
||||
}
|
||||
|
||||
129
frontend/src/hooks/use-reo-tracking.ts
Normal file
129
frontend/src/hooks/use-reo-tracking.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import React from "react";
|
||||
import { useConfig } from "./query/use-config";
|
||||
import { useGitUser } from "./query/use-git-user";
|
||||
import { getLoginMethod, LoginMethod } from "#/utils/local-storage";
|
||||
import reoService, { ReoIdentity } from "#/utils/reo";
|
||||
|
||||
/**
|
||||
* Maps login method to Reo identity type
|
||||
*/
|
||||
const mapLoginMethodToReoType = (method: LoginMethod): ReoIdentity["type"] => {
|
||||
// Reo is not supporting gitlab and bitbucket.
|
||||
switch (method) {
|
||||
case LoginMethod.GITHUB:
|
||||
return "github";
|
||||
case LoginMethod.ENTERPRISE_SSO:
|
||||
return "email";
|
||||
default:
|
||||
return "email";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates email identity object if email is available
|
||||
*/
|
||||
const buildEmailIdentity = (
|
||||
email?: string | null,
|
||||
): ReoIdentity["other_identities"] => {
|
||||
if (!email) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
username: email,
|
||||
type: "email",
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses full name into firstname and lastname
|
||||
* Handles cases where name might be empty or only have one part
|
||||
*/
|
||||
const parseNameFields = (
|
||||
fullName?: string | null,
|
||||
): { firstname?: string; lastname?: string } => {
|
||||
if (!fullName) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const [firstname, ...rest] = fullName.split(" ");
|
||||
if (!firstname) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
firstname,
|
||||
lastname: rest.length > 0 ? rest.join(" ") : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds complete Reo identity from user data and login method
|
||||
*/
|
||||
const buildReoIdentity = (
|
||||
user: {
|
||||
login: string;
|
||||
email?: string | null;
|
||||
name?: string | null;
|
||||
company?: string | null;
|
||||
},
|
||||
loginMethod: LoginMethod,
|
||||
): ReoIdentity => {
|
||||
const { firstname, lastname } = parseNameFields(user.name);
|
||||
|
||||
return {
|
||||
username: user.login,
|
||||
type: mapLoginMethodToReoType(loginMethod),
|
||||
other_identities: buildEmailIdentity(user.email),
|
||||
firstname,
|
||||
lastname,
|
||||
company: user.company || undefined,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to handle Reo.dev tracking integration
|
||||
* Only active in SaaS mode
|
||||
*/
|
||||
export const useReoTracking = () => {
|
||||
const { data: config } = useConfig();
|
||||
const { data: user } = useGitUser();
|
||||
const [hasIdentified, setHasIdentified] = React.useState(false);
|
||||
|
||||
// Initialize Reo.dev when in SaaS mode
|
||||
React.useEffect(() => {
|
||||
const initReo = async () => {
|
||||
if (config?.APP_MODE === "saas" && !reoService.isInitialized()) {
|
||||
await reoService.init();
|
||||
}
|
||||
};
|
||||
|
||||
initReo();
|
||||
}, [config?.APP_MODE]);
|
||||
|
||||
// Identify user when user data is available and we're in SaaS mode
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
config?.APP_MODE !== "saas" ||
|
||||
!user ||
|
||||
hasIdentified ||
|
||||
!reoService.isInitialized()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loginMethod = getLoginMethod();
|
||||
if (!loginMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build identity payload from user data
|
||||
const identity = buildReoIdentity(user, loginMethod);
|
||||
|
||||
// Identify user in Reo
|
||||
reoService.identify(identity);
|
||||
setHasIdentified(true);
|
||||
}, [config?.APP_MODE, user, hasIdentified]);
|
||||
};
|
||||
@ -24,6 +24,7 @@ import { displaySuccessToast } from "#/utils/custom-toast-handlers";
|
||||
import { useIsOnTosPage } from "#/hooks/use-is-on-tos-page";
|
||||
import { useAutoLogin } from "#/hooks/use-auto-login";
|
||||
import { useAuthCallback } from "#/hooks/use-auth-callback";
|
||||
import { useReoTracking } from "#/hooks/use-reo-tracking";
|
||||
import { LOCAL_STORAGE_KEYS } from "#/utils/local-storage";
|
||||
import { EmailVerificationGuard } from "#/components/features/guards/email-verification-guard";
|
||||
import { MaintenanceBanner } from "#/components/features/maintenance/maintenance-banner";
|
||||
@ -96,6 +97,9 @@ export default function MainApp() {
|
||||
// Handle authentication callback and set login method after successful authentication
|
||||
useAuthCallback();
|
||||
|
||||
// Initialize Reo.dev tracking in SaaS mode
|
||||
useReoTracking();
|
||||
|
||||
React.useEffect(() => {
|
||||
// Don't change language when on TOS page
|
||||
if (!isOnTosPage && settings?.LANGUAGE) {
|
||||
|
||||
104
frontend/src/utils/reo.ts
Normal file
104
frontend/src/utils/reo.ts
Normal file
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Reo.dev tracking service for SaaS mode
|
||||
* Tracks developer activity and engagement in the product
|
||||
* Using CDN approach for better TypeScript compatibility
|
||||
*/
|
||||
|
||||
export interface ReoIdentity {
|
||||
username: string;
|
||||
type: "github" | "email";
|
||||
other_identities?: Array<{
|
||||
username: string;
|
||||
type: "github" | "email";
|
||||
}>;
|
||||
firstname?: string;
|
||||
lastname?: string;
|
||||
company?: string;
|
||||
}
|
||||
|
||||
const REO_CLIENT_ID = "6bac7145b4ee6ec";
|
||||
|
||||
class ReoService {
|
||||
private initialized = false;
|
||||
|
||||
private scriptLoaded = false;
|
||||
|
||||
/**
|
||||
* Load and initialize the Reo.dev tracking script from CDN
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the Reo script dynamically from CDN
|
||||
await this.loadScript();
|
||||
|
||||
// Initialize Reo with client ID
|
||||
if (window.Reo) {
|
||||
window.Reo.init({ clientID: REO_CLIENT_ID });
|
||||
this.initialized = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize Reo.dev tracking:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the Reo.dev script from CDN
|
||||
*/
|
||||
private loadScript(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.scriptLoaded) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.src = `https://static.reo.dev/${REO_CLIENT_ID}/reo.js`;
|
||||
script.defer = true;
|
||||
|
||||
script.onload = () => {
|
||||
this.scriptLoaded = true;
|
||||
resolve();
|
||||
};
|
||||
|
||||
script.onerror = () => {
|
||||
reject(new Error("Failed to load Reo.dev script"));
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify a user in Reo.dev tracking
|
||||
* Should be called after successful login
|
||||
*/
|
||||
identify(identity: ReoIdentity): void {
|
||||
if (!this.initialized) {
|
||||
console.warn("Reo.dev not initialized. Call init() first.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (window.Reo) {
|
||||
window.Reo.identify(identity);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to identify user in Reo.dev:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Reo.dev is initialized
|
||||
*/
|
||||
isInitialized(): boolean {
|
||||
return this.initialized;
|
||||
}
|
||||
}
|
||||
|
||||
const reoService = new ReoService();
|
||||
|
||||
export default reoService;
|
||||
Loading…
x
Reference in New Issue
Block a user