mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
fix(core): Inline config.js to index.html to prevent CF from caching it (#18945)
This commit is contained in:
@@ -16,7 +16,7 @@ import { z } from 'zod';
|
|||||||
import { ActiveExecutions } from '@/active-executions';
|
import { ActiveExecutions } from '@/active-executions';
|
||||||
import { ActiveWorkflowManager } from '@/active-workflow-manager';
|
import { ActiveWorkflowManager } from '@/active-workflow-manager';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { EDITOR_UI_DIST_DIR } from '@/constants';
|
import { EDITOR_UI_DIST_DIR, N8N_VERSION } from '@/constants';
|
||||||
import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error';
|
import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error';
|
||||||
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
||||||
import { EventService } from '@/events/event.service';
|
import { EventService } from '@/events/event.service';
|
||||||
@@ -117,6 +117,31 @@ export class Start extends BaseCommand<z.infer<typeof flagsSchema>> {
|
|||||||
await this.exitSuccessFully();
|
await this.exitSuccessFully();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates meta tags with base64-encoded configuration values
|
||||||
|
* for REST endpoint path and Sentry config.
|
||||||
|
*/
|
||||||
|
private generateConfigTags() {
|
||||||
|
const frontendSentryConfig = JSON.stringify({
|
||||||
|
dsn: this.globalConfig.sentry.frontendDsn,
|
||||||
|
environment: process.env.ENVIRONMENT || 'development',
|
||||||
|
serverName: process.env.DEPLOYMENT_NAME,
|
||||||
|
release: `n8n@${N8N_VERSION}`,
|
||||||
|
});
|
||||||
|
const b64Encode = (value: string) => Buffer.from(value).toString('base64');
|
||||||
|
|
||||||
|
// Base64 encode the configuration values
|
||||||
|
const restEndpointEncoded = b64Encode(this.globalConfig.endpoints.rest);
|
||||||
|
const sentryConfigEncoded = b64Encode(frontendSentryConfig);
|
||||||
|
|
||||||
|
const configMetaTags = [
|
||||||
|
`<meta name="n8n:config:rest-endpoint" content="${restEndpointEncoded}">`,
|
||||||
|
`<meta name="n8n:config:sentry" content="${sentryConfigEncoded}">`,
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
return configMetaTags;
|
||||||
|
}
|
||||||
|
|
||||||
private async generateStaticAssets() {
|
private async generateStaticAssets() {
|
||||||
// Read the index file and replace the path placeholder
|
// Read the index file and replace the path placeholder
|
||||||
const n8nPath = this.globalConfig.path;
|
const n8nPath = this.globalConfig.path;
|
||||||
@@ -138,6 +163,7 @@ export class Start extends BaseCommand<z.infer<typeof flagsSchema>> {
|
|||||||
await mkdir(path.dirname(destFile), { recursive: true });
|
await mkdir(path.dirname(destFile), { recursive: true });
|
||||||
const streams = [
|
const streams = [
|
||||||
createReadStream(filePath, 'utf-8'),
|
createReadStream(filePath, 'utf-8'),
|
||||||
|
replaceStream('%CONFIG_TAGS%', this.generateConfigTags(), { ignoreCase: false }),
|
||||||
replaceStream('/{{BASE_PATH}}/', n8nPath, { ignoreCase: false }),
|
replaceStream('/{{BASE_PATH}}/', n8nPath, { ignoreCase: false }),
|
||||||
replaceStream('/%7B%7BBASE_PATH%7D%7D/', n8nPath, { ignoreCase: false }),
|
replaceStream('/%7B%7BBASE_PATH%7D%7D/', n8nPath, { ignoreCase: false }),
|
||||||
replaceStream('/%257B%257BBASE_PATH%257D%257D/', n8nPath, { ignoreCase: false }),
|
replaceStream('/%257B%257BBASE_PATH%257D%257D/', n8nPath, { ignoreCase: false }),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { CLI_DIR, EDITOR_UI_DIST_DIR, inE2ETests, N8N_VERSION } from '@/constants';
|
|
||||||
import { inDevelopment, inProduction } from '@n8n/backend-common';
|
import { inDevelopment, inProduction } from '@n8n/backend-common';
|
||||||
import { SecurityConfig } from '@n8n/config';
|
import { SecurityConfig } from '@n8n/config';
|
||||||
import { Time } from '@n8n/constants';
|
import { Time } from '@n8n/constants';
|
||||||
@@ -15,6 +14,7 @@ import { resolve } from 'path';
|
|||||||
|
|
||||||
import { AbstractServer } from '@/abstract-server';
|
import { AbstractServer } from '@/abstract-server';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
import { CLI_DIR, EDITOR_UI_DIST_DIR, inE2ETests } from '@/constants';
|
||||||
import { ControllerRegistry } from '@/controller.registry';
|
import { ControllerRegistry } from '@/controller.registry';
|
||||||
import { CredentialsOverwrites } from '@/credentials-overwrites';
|
import { CredentialsOverwrites } from '@/credentials-overwrites';
|
||||||
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
||||||
@@ -274,22 +274,6 @@ export class Server extends AbstractServer {
|
|||||||
`/${this.restEndpoint}/settings`,
|
`/${this.restEndpoint}/settings`,
|
||||||
ResponseHelper.send(async () => frontendService.getSettings()),
|
ResponseHelper.send(async () => frontendService.getSettings()),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.app.get(`/${this.restEndpoint}/config.js`, (_req, res) => {
|
|
||||||
const frontendSentryConfig = JSON.stringify({
|
|
||||||
dsn: this.globalConfig.sentry.frontendDsn,
|
|
||||||
environment: process.env.ENVIRONMENT || 'development',
|
|
||||||
serverName: process.env.DEPLOYMENT_NAME,
|
|
||||||
release: `n8n@${N8N_VERSION}`,
|
|
||||||
});
|
|
||||||
const frontendConfig = [
|
|
||||||
`window.REST_ENDPOINT = '${this.globalConfig.endpoints.rest}';`,
|
|
||||||
`window.sentry = ${frontendSentryConfig};`,
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
res.type('application/javascript');
|
|
||||||
res.send(frontendConfig);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
import { getConfigFromMetaTag, getAndParseConfigFromMetaTag } from '../metaTagConfig';
|
||||||
|
|
||||||
|
describe('metaTagConfig', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
document.head.innerHTML = '';
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to create and insert a meta tag into the document head
|
||||||
|
*/
|
||||||
|
function createMetaTag(configName: string, content?: string): void {
|
||||||
|
const metaTag = document.createElement('meta');
|
||||||
|
metaTag.setAttribute('name', `n8n:config:${configName}`);
|
||||||
|
|
||||||
|
if (content !== undefined) {
|
||||||
|
metaTag.setAttribute('content', content);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.head.appendChild(metaTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to create a meta tag with base64-encoded content
|
||||||
|
*/
|
||||||
|
function createMetaTagWithBase64Content(configName: string, value: string): void {
|
||||||
|
const base64Value = btoa(value);
|
||||||
|
createMetaTag(configName, base64Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to create a meta tag with JSON content (base64-encoded)
|
||||||
|
*/
|
||||||
|
function createMetaTagWithJsonContent(configName: string, value: unknown): void {
|
||||||
|
const jsonString = JSON.stringify(value);
|
||||||
|
createMetaTagWithBase64Content(configName, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('getConfigFromMetaTag', () => {
|
||||||
|
it('should return null when meta tag does not exist', () => {
|
||||||
|
const result = getConfigFromMetaTag('testConfig');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when meta tag exists but has no content attribute', () => {
|
||||||
|
createMetaTag('testConfig');
|
||||||
|
|
||||||
|
const result = getConfigFromMetaTag('testConfig');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when meta tag has empty content attribute', () => {
|
||||||
|
createMetaTag('testConfig', '');
|
||||||
|
|
||||||
|
const result = getConfigFromMetaTag('testConfig');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decode and return base64 content successfully', () => {
|
||||||
|
const originalValue = 'Hello World';
|
||||||
|
createMetaTagWithBase64Content('testConfig', originalValue);
|
||||||
|
|
||||||
|
const result = getConfigFromMetaTag('testConfig');
|
||||||
|
expect(result).toBe(originalValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle complex string content correctly', () => {
|
||||||
|
const originalValue = 'This is a test with special chars: !@#$%^&*()';
|
||||||
|
createMetaTagWithBase64Content('complexConfig', originalValue);
|
||||||
|
|
||||||
|
const result = getConfigFromMetaTag('complexConfig');
|
||||||
|
expect(result).toBe(originalValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null and log warning when base64 decoding fails', () => {
|
||||||
|
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
|
||||||
|
createMetaTag('invalidConfig', 'invalid-base64!!!');
|
||||||
|
|
||||||
|
const result = getConfigFromMetaTag('invalidConfig');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
expect(consoleSpy).toHaveBeenCalledWith(
|
||||||
|
'Failed to read n8n config for "n8n:config:invalidConfig":',
|
||||||
|
expect.any(Error),
|
||||||
|
);
|
||||||
|
|
||||||
|
consoleSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{ name: 'config1', value: 'value1' },
|
||||||
|
{ name: 'config2', value: 'value2' },
|
||||||
|
{ name: 'special-config', value: 'special-value' },
|
||||||
|
])('should handle different config names correctly: $name', ({ name, value }) => {
|
||||||
|
createMetaTagWithBase64Content(name, value);
|
||||||
|
|
||||||
|
const result = getConfigFromMetaTag(name);
|
||||||
|
expect(result).toBe(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAndParseConfigFromMetaTag', () => {
|
||||||
|
it('should return null when meta tag does not exist', () => {
|
||||||
|
const result = getAndParseConfigFromMetaTag('nonExistentConfig');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when getConfigFromMetaTag returns null', () => {
|
||||||
|
createMetaTag('emptyConfig');
|
||||||
|
|
||||||
|
const result = getAndParseConfigFromMetaTag('emptyConfig');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse and return valid JSON object', () => {
|
||||||
|
const originalObject = {
|
||||||
|
key1: 'value1',
|
||||||
|
key2: 42,
|
||||||
|
key3: true,
|
||||||
|
nested: { prop: 'nestedValue' },
|
||||||
|
};
|
||||||
|
createMetaTagWithJsonContent('jsonConfig', originalObject);
|
||||||
|
|
||||||
|
const result = getAndParseConfigFromMetaTag<typeof originalObject>('jsonConfig');
|
||||||
|
expect(result).toEqual(originalObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse and return valid JSON array', () => {
|
||||||
|
const originalArray = [1, 2, 3, { name: 'test' }];
|
||||||
|
createMetaTagWithJsonContent('arrayConfig', originalArray);
|
||||||
|
|
||||||
|
const result = getAndParseConfigFromMetaTag<typeof originalArray>('arrayConfig');
|
||||||
|
expect(result).toEqual(originalArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse and return primitive JSON values', () => {
|
||||||
|
const testCases = [
|
||||||
|
{ value: 'simple string', name: 'stringConfig' },
|
||||||
|
{ value: 42, name: 'numberConfig' },
|
||||||
|
{ value: true, name: 'booleanConfig' },
|
||||||
|
{ value: null, name: 'nullConfig' },
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach((testCase) => {
|
||||||
|
createMetaTagWithJsonContent(testCase.name, testCase.value);
|
||||||
|
|
||||||
|
const result = getAndParseConfigFromMetaTag(testCase.name);
|
||||||
|
expect(result).toEqual(testCase.value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null and log warning when JSON parsing fails', () => {
|
||||||
|
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
|
||||||
|
const invalidJson = 'this is not json';
|
||||||
|
createMetaTagWithBase64Content('invalidJsonConfig', invalidJson);
|
||||||
|
|
||||||
|
const result = getAndParseConfigFromMetaTag('invalidJsonConfig');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
expect(consoleSpy).toHaveBeenCalledWith(
|
||||||
|
'Failed to parse n8n config for "n8n:config:invalidJsonConfig":',
|
||||||
|
expect.any(Error),
|
||||||
|
);
|
||||||
|
|
||||||
|
consoleSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle complex nested objects with type safety', () => {
|
||||||
|
interface TestConfig {
|
||||||
|
api: {
|
||||||
|
url: string;
|
||||||
|
version: number;
|
||||||
|
features: string[];
|
||||||
|
};
|
||||||
|
user: {
|
||||||
|
id: number;
|
||||||
|
permissions: Record<string, boolean>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalConfig: TestConfig = {
|
||||||
|
api: {
|
||||||
|
url: 'https://api.example.com',
|
||||||
|
version: 2,
|
||||||
|
features: ['feature1', 'feature2'],
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
id: 123,
|
||||||
|
permissions: {
|
||||||
|
read: true,
|
||||||
|
write: false,
|
||||||
|
admin: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
createMetaTagWithJsonContent('complexConfig', originalConfig);
|
||||||
|
|
||||||
|
const result = getAndParseConfigFromMetaTag<TestConfig>('complexConfig');
|
||||||
|
expect(result).toEqual(originalConfig);
|
||||||
|
|
||||||
|
assert(result);
|
||||||
|
// Type assertions to ensure type safety works
|
||||||
|
expect(result.api.url).toBe('https://api.example.com');
|
||||||
|
expect(result.user.permissions.read).toBe(true);
|
||||||
|
expect(result.api.features).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty JSON objects and arrays', () => {
|
||||||
|
const testCases = [
|
||||||
|
{ value: {}, name: 'emptyObject' },
|
||||||
|
{ value: [], name: 'emptyArray' },
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach((testCase) => {
|
||||||
|
createMetaTagWithJsonContent(testCase.name, testCase.value);
|
||||||
|
|
||||||
|
const result = getAndParseConfigFromMetaTag(testCase.name);
|
||||||
|
expect(result).toEqual(testCase.value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('integration tests', () => {
|
||||||
|
it('should handle multiple config retrieval operations', () => {
|
||||||
|
const configs = {
|
||||||
|
stringConfig: 'test string',
|
||||||
|
objectConfig: { key: 'value', num: 42 },
|
||||||
|
arrayConfig: [1, 2, 3],
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.entries(configs).forEach(([name, value]) => {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
createMetaTagWithBase64Content(name, value);
|
||||||
|
} else {
|
||||||
|
createMetaTagWithJsonContent(name, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringResult = getConfigFromMetaTag('stringConfig');
|
||||||
|
expect(stringResult).toBe('test string');
|
||||||
|
|
||||||
|
const objectResult = getAndParseConfigFromMetaTag('objectConfig');
|
||||||
|
expect(objectResult).toEqual({ key: 'value', num: 42 });
|
||||||
|
|
||||||
|
const arrayResult = getAndParseConfigFromMetaTag('arrayConfig');
|
||||||
|
expect(arrayResult).toEqual([1, 2, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle edge case of config name with special characters', () => {
|
||||||
|
const configName = 'config-with-dashes_and_underscores.123';
|
||||||
|
const configValue = { test: true };
|
||||||
|
createMetaTagWithJsonContent(configName, configValue);
|
||||||
|
|
||||||
|
const result = getAndParseConfigFromMetaTag(configName);
|
||||||
|
expect(result).toEqual(configValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
46
packages/frontend/@n8n/stores/src/metaTagConfig.ts
Normal file
46
packages/frontend/@n8n/stores/src/metaTagConfig.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
function getTagName(configName: string): string {
|
||||||
|
return `n8n:config:${configName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to read and decode base64-encoded configuration values from meta tags
|
||||||
|
*/
|
||||||
|
export function getConfigFromMetaTag(configName: string): string | null {
|
||||||
|
const tagName = getTagName(configName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const metaTag = document.querySelector(`meta[name="${tagName}"]`);
|
||||||
|
if (!metaTag) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const encodedContent = metaTag.getAttribute('content');
|
||||||
|
if (!encodedContent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode base64 content
|
||||||
|
const content = atob(encodedContent);
|
||||||
|
return content;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to read n8n config for "${tagName}":`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to read and parse configuration values from meta tags
|
||||||
|
*/
|
||||||
|
export function getAndParseConfigFromMetaTag<T>(configName: string): T | null {
|
||||||
|
const config = getConfigFromMetaTag(configName);
|
||||||
|
if (!config) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(config) as T;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to parse n8n config for "${getTagName(configName)}":`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { defineStore } from 'pinia';
|
|||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { STORES } from './constants';
|
import { STORES } from './constants';
|
||||||
|
import { getConfigFromMetaTag } from './metaTagConfig';
|
||||||
|
|
||||||
const { VUE_APP_URL_BASE_API } = import.meta.env;
|
const { VUE_APP_URL_BASE_API } = import.meta.env;
|
||||||
|
|
||||||
@@ -36,10 +37,7 @@ export type RootStoreState = {
|
|||||||
export const useRootStore = defineStore(STORES.ROOT, () => {
|
export const useRootStore = defineStore(STORES.ROOT, () => {
|
||||||
const state = ref<RootStoreState>({
|
const state = ref<RootStoreState>({
|
||||||
baseUrl: VUE_APP_URL_BASE_API ?? window.BASE_PATH,
|
baseUrl: VUE_APP_URL_BASE_API ?? window.BASE_PATH,
|
||||||
restEndpoint:
|
restEndpoint: getConfigFromMetaTag('rest-endpoint') ?? 'rest',
|
||||||
!window.REST_ENDPOINT || window.REST_ENDPOINT === '{{REST_ENDPOINT}}'
|
|
||||||
? 'rest'
|
|
||||||
: window.REST_ENDPOINT,
|
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'en',
|
||||||
endpointForm: 'form',
|
endpointForm: 'form',
|
||||||
endpointFormTest: 'form-test',
|
endpointFormTest: 'form-test',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
%CONFIG_SCRIPT%
|
%CONFIG_TAGS%
|
||||||
<link rel="stylesheet" href="/{{BASE_PATH}}/static/prefers-color-scheme.css">
|
<link rel="stylesheet" href="/{{BASE_PATH}}/static/prefers-color-scheme.css">
|
||||||
<script src="/{{BASE_PATH}}/static/base-path.js" type="text/javascript"></script>
|
<script src="/{{BASE_PATH}}/static/base-path.js" type="text/javascript"></script>
|
||||||
<script src="/{{BASE_PATH}}/static/posthog.init.js" type="text/javascript"></script>
|
<script src="/{{BASE_PATH}}/static/posthog.init.js" type="text/javascript"></script>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { Plugin } from 'vue';
|
|||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { ResponseError } from '@n8n/rest-api-client';
|
import { ResponseError } from '@n8n/rest-api-client';
|
||||||
import * as Sentry from '@sentry/vue';
|
import * as Sentry from '@sentry/vue';
|
||||||
|
import { getAndParseConfigFromMetaTag } from '@n8n/stores/metaTagConfig';
|
||||||
|
|
||||||
const ignoredErrors = [
|
const ignoredErrors = [
|
||||||
{ instanceof: AxiosError },
|
{ instanceof: AxiosError },
|
||||||
@@ -14,6 +15,13 @@ const ignoredErrors = [
|
|||||||
{ instanceof: Error, message: /ResizeObserver/ },
|
{ instanceof: Error, message: /ResizeObserver/ },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
type SentryConfig = {
|
||||||
|
dsn?: string;
|
||||||
|
environment?: string;
|
||||||
|
serverName?: string;
|
||||||
|
release?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function beforeSend(event: Sentry.ErrorEvent, { originalException }: Sentry.EventHint) {
|
export function beforeSend(event: Sentry.ErrorEvent, { originalException }: Sentry.EventHint) {
|
||||||
if (
|
if (
|
||||||
!originalException ||
|
!originalException ||
|
||||||
@@ -42,11 +50,12 @@ export function beforeSend(event: Sentry.ErrorEvent, { originalException }: Sent
|
|||||||
|
|
||||||
export const SentryPlugin: Plugin = {
|
export const SentryPlugin: Plugin = {
|
||||||
install: (app) => {
|
install: (app) => {
|
||||||
if (!window.sentry?.dsn) {
|
const sentryConfig = getAndParseConfigFromMetaTag<SentryConfig>('sentry');
|
||||||
|
if (!sentryConfig?.dsn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { dsn, release, environment, serverName } = window.sentry;
|
const { dsn, release, environment, serverName } = sentryConfig;
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
app,
|
app,
|
||||||
|
|||||||
1
packages/frontend/editor-ui/src/shims.d.ts
vendored
1
packages/frontend/editor-ui/src/shims.d.ts
vendored
@@ -21,7 +21,6 @@ declare global {
|
|||||||
interface Window {
|
interface Window {
|
||||||
BASE_PATH: string;
|
BASE_PATH: string;
|
||||||
REST_ENDPOINT: string;
|
REST_ENDPOINT: string;
|
||||||
sentry?: { dsn?: string; environment: string; release: string; serverName?: string };
|
|
||||||
n8nExternalHooks?: PartialDeep<ExternalHooks>;
|
n8nExternalHooks?: PartialDeep<ExternalHooks>;
|
||||||
preventNodeViewBeforeUnload?: boolean;
|
preventNodeViewBeforeUnload?: boolean;
|
||||||
maxPinnedDataSize?: number;
|
maxPinnedDataSize?: number;
|
||||||
|
|||||||
@@ -126,11 +126,9 @@ const plugins: UserConfig['plugins'] = [
|
|||||||
{
|
{
|
||||||
name: 'Insert config script',
|
name: 'Insert config script',
|
||||||
transformIndexHtml: (html, ctx) => {
|
transformIndexHtml: (html, ctx) => {
|
||||||
const replacement = ctx.server
|
// Skip config script when using Vite dev server. Otherwise the BE
|
||||||
? '' // Skip when using Vite dev server
|
// will replace it with the actual config script in cli/src/commands/start.ts.
|
||||||
: '<script src="/{{BASE_PATH}}/{{REST_ENDPOINT}}/config.js"></script>';
|
return ctx.server ? html.replace('%CONFIG_SCRIPT%', '') : html;
|
||||||
|
|
||||||
return html.replace('%CONFIG_SCRIPT%', replacement);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// For sanitize-html
|
// For sanitize-html
|
||||||
|
|||||||
Reference in New Issue
Block a user