mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 00:09:35 +00:00
Summary: - Add or tighten focused edge-case tests or type assertions in web/src/main.ts, web/src/App.spec.ts related to TypeScript, frontend tooling, test stability, build fixes; avoid docs-only changes and broad refactors. - Keep the change narrow so it is straightforward to review. Notes: - I kept this scoped to the relevant implementation and tests. Co-authored-by: Kai Tanaka <275430420+quyentonndbs@users.noreply.github.com>
278 lines
8.7 KiB
TypeScript
278 lines
8.7 KiB
TypeScript
// Copyright 2026 OpenObserve Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import { createApp } from "vue";
|
|
import store from "./stores";
|
|
import App from "./App.vue";
|
|
import createRouter from "./router";
|
|
import i18n from "./locales";
|
|
import "./styles/tailwind.css";
|
|
import config from "./aws-exports";
|
|
import configService from "./services/config";
|
|
|
|
import { openobserveRum } from "@openobserve/browser-rum";
|
|
import { openobserveLogs } from "@openobserve/browser-logs";
|
|
import { useReo } from "./services/reodotdev_analytics";
|
|
import {
|
|
contextRegistry,
|
|
createDefaultContextProvider,
|
|
} from "./composables/contextProviders";
|
|
import { buildVersionChecker } from "./utils/buildVersionChecker";
|
|
import { toast } from "@/lib/feedback/Toast/useToast";
|
|
import { bootstrapTheme } from "@/utils/themeManager";
|
|
|
|
// Apply the resolved theme synchronously before the app mounts so the first
|
|
// paint already uses the correct colors (no flash of the base stylesheet theme).
|
|
bootstrapTheme();
|
|
|
|
const app = createApp(App);
|
|
const router = createRouter(store);
|
|
|
|
app.use(i18n);
|
|
|
|
// const router = createRouter(store);
|
|
app.use(store).use(router);
|
|
|
|
// Initialize default context provider globally
|
|
const defaultProvider = createDefaultContextProvider(router, store);
|
|
contextRegistry.register("default", defaultProvider);
|
|
|
|
const { reoInit } = useReo();
|
|
|
|
reoInit();
|
|
|
|
// app.use(SearchPlugin);
|
|
|
|
interface ConfigResponse {
|
|
data?: {
|
|
telemetry_enabled?: boolean;
|
|
commit_hash?: string;
|
|
rum?: {
|
|
enabled: boolean;
|
|
client_token: string;
|
|
application_id: string;
|
|
site: string;
|
|
service: string;
|
|
env: string;
|
|
version?: string;
|
|
organization_identifier: string;
|
|
insecure_http?: boolean;
|
|
api_version?: string;
|
|
};
|
|
};
|
|
}
|
|
|
|
const getConfig = async () => {
|
|
await configService.get_config().then((res: ConfigResponse) => {
|
|
if (!res.data) return;
|
|
|
|
store.dispatch("setConfig", res.data);
|
|
config.enableAnalytics = res.data.telemetry_enabled?.toString() ?? "false";
|
|
|
|
// Store initial commit hash for version checking
|
|
if (res.data.commit_hash) {
|
|
buildVersionChecker.setInitialVersion(res.data.commit_hash);
|
|
}
|
|
if (res.data.rum?.enabled) {
|
|
const options = {
|
|
clientToken: res.data.rum.client_token,
|
|
applicationId: res.data.rum.application_id,
|
|
site: res.data.rum.site,
|
|
service: res.data.rum.service,
|
|
env: res.data.rum.env,
|
|
version: res.data.rum.version || "0.0.1",
|
|
organizationIdentifier: res.data.rum.organization_identifier,
|
|
insecureHTTP: res.data.rum.insecure_http || false,
|
|
apiVersion: res.data.rum.api_version || "v1",
|
|
};
|
|
|
|
openobserveRum.init({
|
|
applicationId: options.applicationId, // required, any string identifying your application
|
|
clientToken: options.clientToken,
|
|
site: options.site,
|
|
organizationIdentifier: options.organizationIdentifier,
|
|
service: options.service,
|
|
env: options.env,
|
|
version: options.version,
|
|
trackResources: true,
|
|
trackLongTasks: true,
|
|
trackUserInteractions: true,
|
|
actionNameAttribute: "data-test",
|
|
apiVersion: options.apiVersion,
|
|
insecureHTTP: options.insecureHTTP,
|
|
defaultPrivacyLevel: "allow",
|
|
allowedTracingUrls: [
|
|
{
|
|
match: store.state.API_ENDPOINT + "/api",
|
|
propagatorTypes: ["openobserve", "tracecontext"],
|
|
},
|
|
],
|
|
beforeSend: (event) => {
|
|
// Filter out specific errors before sending to RUM
|
|
if (event.type === "error") {
|
|
const errorMessage = event.error?.message || "";
|
|
const errorStack = event.error?.stack || "";
|
|
|
|
// List of error patterns to ignore
|
|
const ignoredErrorPatterns = [
|
|
/ResizeObserver loop/i,
|
|
/RS SDK/i,
|
|
/reo.dev/i,
|
|
/Cannot set properties of null \(setting 'innerHTML'\)/,
|
|
];
|
|
|
|
// Check if error matches any ignored pattern
|
|
const shouldIgnore = ignoredErrorPatterns.some(
|
|
(pattern) =>
|
|
pattern.test(errorMessage) || pattern.test(errorStack),
|
|
);
|
|
|
|
if (shouldIgnore) {
|
|
return false; // Don't send this error
|
|
}
|
|
}
|
|
|
|
// Allow all other events to be sent
|
|
return true;
|
|
},
|
|
});
|
|
|
|
openobserveLogs.init({
|
|
clientToken: options.clientToken,
|
|
site: options.site,
|
|
organizationIdentifier: options.organizationIdentifier,
|
|
service: options.service,
|
|
env: options.env,
|
|
version: options.version,
|
|
forwardErrorsToLogs: true,
|
|
insecureHTTP: options.insecureHTTP,
|
|
apiVersion: options.apiVersion,
|
|
beforeSend: (log) => {
|
|
// Filter out specific logs before sending
|
|
const logMessage = log.message || "";
|
|
|
|
// List of log patterns to ignore
|
|
const ignoredLogPatterns = [/ResizeObserver loop/i];
|
|
|
|
// Check if log matches any ignored pattern
|
|
const shouldIgnore = ignoredLogPatterns.some((pattern) =>
|
|
pattern.test(logMessage),
|
|
);
|
|
|
|
if (shouldIgnore) {
|
|
return false; // Don't send this log
|
|
}
|
|
|
|
// Allow all other logs to be sent
|
|
return true;
|
|
},
|
|
});
|
|
|
|
// Don't start session replay automatically - it will be started after login
|
|
// to avoid capturing login page with "Unknown" user
|
|
// Session replay will be started in Login.vue and MainLayout.vue after user is authenticated
|
|
}
|
|
});
|
|
};
|
|
|
|
getConfig();
|
|
|
|
// ===== Smart Stale Build Detection (Industry Standard) =====
|
|
// Detects stale build errors (new deployment, old chunks deleted) and prompts user to refresh
|
|
|
|
// Track if notification is already shown to prevent duplicates
|
|
let staleNotificationShown = false;
|
|
|
|
// Helper: Show new version notification (only once)
|
|
function showNewVersionNotification() {
|
|
if (staleNotificationShown) {
|
|
return;
|
|
}
|
|
|
|
staleNotificationShown = true;
|
|
toast({
|
|
variant: "error",
|
|
message: i18n.global.t("common.chunkLoadErrorMsg"),
|
|
timeout: 0, // Don't auto-dismiss
|
|
action: {
|
|
label: i18n.global.t("common.refresh"),
|
|
handler: () => {
|
|
window.location.reload();
|
|
},
|
|
},
|
|
position: "top-center",
|
|
});
|
|
}
|
|
|
|
// Handle resource load errors (script/link tags)
|
|
window.addEventListener(
|
|
"error",
|
|
async (event) => {
|
|
const target = event.target as HTMLScriptElement | HTMLLinkElement;
|
|
|
|
// Check if the error is from loading a script or stylesheet
|
|
if (target && (target.tagName === "SCRIPT" || target.tagName === "LINK")) {
|
|
const url =
|
|
target.tagName === "SCRIPT"
|
|
? (target as HTMLScriptElement).src
|
|
: (target as HTMLLinkElement).href;
|
|
|
|
if (!url) return;
|
|
|
|
// Only check for OpenObserve's own chunks (relative URLs or same origin)
|
|
// Ignore external CDN resources (absolute URLs with different origins)
|
|
const isOpenObserveResource = (() => {
|
|
try {
|
|
// Resolve relative URLs against current origin
|
|
const resourceUrl = new URL(url, window.location.origin);
|
|
// Only check resources from same origin
|
|
return resourceUrl.origin === window.location.origin;
|
|
} catch (e) {
|
|
// Invalid URL - assume it's a relative path (OpenObserve resource)
|
|
return true;
|
|
}
|
|
})();
|
|
|
|
if (isOpenObserveResource) {
|
|
// Smart detection: Check if it's stale build
|
|
const isStale = await buildVersionChecker.isStaleResourceError();
|
|
|
|
if (isStale) {
|
|
showNewVersionNotification();
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
true,
|
|
);
|
|
|
|
// Handle dynamic import errors (for code splitting)
|
|
router.onError(async (error) => {
|
|
const chunkFailedPattern =
|
|
/Loading chunk [\d]+ failed|Failed to fetch dynamically imported module/i;
|
|
|
|
if (chunkFailedPattern.test(error.message)) {
|
|
// Smart detection: Check if it's stale build or network error
|
|
const isStale = await buildVersionChecker.isStaleChunkError(error);
|
|
|
|
if (isStale) {
|
|
showNewVersionNotification();
|
|
}
|
|
}
|
|
});
|
|
|
|
app.mount("#app");
|