Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
13 changes: 13 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,19 @@ export default [
modulesConfig,
testConfig,
esmJsConfig,
{
files: [
'src/core/**/*.ts',
'src/common/**/*.ts',
'src/editor/**/*.ts',
'src/code-editor/**/*.ts',
'src/launch/**/*.ts',
'src/plugins/**/*.ts'
],
rules: {
'no-unused-expressions': ['error', { allowTaggedTemplates: true }]
}
},
{
ignores: [
'**/node_modules/**',
Expand Down
1 change: 1 addition & 0 deletions src/code-editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class CodeEditor extends Editor<EditorMethods> {
window.editor = new CodeEditor();

setSentryTags({
user_id: config.self?.id,
project_id: config.project?.id,
branch_id: config.self?.branch?.id
});
Expand Down
9 changes: 6 additions & 3 deletions src/code-editor/monaco/intellisense/attribute-autofill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,16 @@ const fetchModuleScripts = async (
try {
const url = editor.call('assets:realPath', asset);

const promise: Promise<[string, string]> = fetch(url).then(res => res.text()).then((content) => {
const promise: Promise<[string, string] | null> = fetch(url).then(res => res.text()).then((content) => {
cache.set(path, hash);
return [path, content];
return [path, content] as [string, string];
}).catch((e) => {
log.error`failed to fetch esm script ${path}: ${e}`;
return null;
});
acc.push(promise);
} catch (e) {
console.error(`Failed to fetch ESM script ${path}`, e);
log.error`failed to fetch esm script ${path}: ${e}`;
}
return acc;
}, [] as Promise<[string, string]>[]));
Expand Down
2 changes: 2 additions & 0 deletions src/code-editor/monaco/monaco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ editor.once('load', () => {
monaco.languages.typescript.javascriptDefaults.addExtraLib(code, 'playcanvas.d.ts');
});
}
}).catch((err) => {
log.error`failed to fetch engine type definitions: ${typesURL}: ${err}`;
});

// hide initially
Expand Down
3 changes: 3 additions & 0 deletions src/code-editor/realtime/realtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ editor.once('load', () => {
socket.onopen = onOpen;
socket.onclose = onClose;
socket.onmessage = onMessage;
socket.onerror = () => {
log.error`code-editor websocket error (url: ${config.url.realtime.http})`;
};
};

// Raw socket send
Expand Down
1 change: 1 addition & 0 deletions src/common/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type FingerprintedError = Error & { fingerprint: string; context: unknown[] };
2 changes: 1 addition & 1 deletion src/common/observer-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ObserverSync extends Events {

// if this happens it's a bug
if (this.item.sync !== this) {
log.error('Garbage Observer Sync still pointing to item', this.item);
log.error`garbage observer sync still pointing to item: ${this.item}`;
}

// check if path is allowed
Expand Down
49 changes: 45 additions & 4 deletions src/common/sentry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { BrowserClient, defaultStackParser, makeFetchTransport, Scope } from '@sentry/browser';

import type { FingerprintedError } from './error';
import packageJson from '../../package.json';

const SENTRY_DSN = 'https://0defef72baf64d99bf53b92a23d5bd14@sentry.sc-prod.net/87';

const SANITIZE_KEYS = /password|token|secret|passwd|authorization|api_key|apikey|sentry_dsn|access_token|stripetoken|mysql_pwd|credentials/i;
Expand Down Expand Up @@ -58,9 +61,9 @@ if (sentryConfig.enabled) {
transport: makeFetchTransport,
stackParser: defaultStackParser,
environment: sentryConfig.env,
release: sentryConfig.version,
release: packageJson.version,
integrations: [],
beforeSend: (event) => {
beforeSend: (event, hint) => {
// filter errors from user code (asset scripts)
const frames = event.exception?.values?.[0]?.stacktrace?.frames;
if (frames?.length) {
Expand All @@ -70,6 +73,31 @@ if (sentryConfig.enabled) {
}
}

// set fingerprint for tagged template errors
const original = hint?.originalException;
if (original instanceof Error && 'fingerprint' in original) {
const fe = original as FingerprintedError;
event.fingerprint = [fe.fingerprint];
event.extra = {
...(event.extra || {}),
metadata: { message: fe.message, context: fe.context }
};
}

// auto-categorize by source module from stack trace
if (frames?.length) {
const top = frames[frames.length - 1];
if (top.filename) {
const m = top.filename.match(/\/(?:editor|code-editor|launch|common)\/(.+)\.[^.]+$/);
if (m) {
const parts = m[1].split('/');
// use directory path for nested files, filename for top-level files
const source = parts.length > 1 ? parts.slice(0, -1).join('/') : parts[0];
event.tags = { ...event.tags, source };
}
}
}

// report error count to graphene metrics
if (window.metrics) {
metrics.increment({ metricsName: `${sentryConfig.service}.frontend_errors.count.by_page.${sentryConfig.page}` });
Expand All @@ -85,13 +113,26 @@ if (sentryConfig.enabled) {
client.init();

// capture errors via sentry
// supports both normal calls and tagged templates:
// log.error(err) — existing Error
// log.error('message') — string wrapped in Error
// log.error`missing asset ${id}` — fingerprinted Error for grouping
window.log.error = (...args: any[]) => {
if (args[0]?.raw) {
const strings = args[0] as TemplateStringsArray;
const values = args.slice(1);
const e = new Error(String.raw(strings, ...values)) as FingerprintedError;
e.fingerprint = strings.join('{}');
e.context = values;
console.error(e);
captureException(e);
return;
}
console.error(...args);

if (args.length === 1 && args[0]?.stack) {
captureException(args[0]);
} else {
captureMessage(args.map(String).join(' '));
captureException(new Error(args.map(String).join(' ')));
}
};
} else {
Expand Down
4 changes: 4 additions & 0 deletions src/editor-api/realtime/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ class RealtimeConnection extends Events {
};
socket.addEventListener('message', onmessage);

socket.addEventListener('error', () => {
log.error`websocket error (state: ${this._state}, url: ${url}, attempts: ${this._reconnectAttempts})`;
});

// ! use event listener as sharedb overrides socket.on* handlers
socket.addEventListener('close', (reason) => {
// block sending messages
Expand Down
13 changes: 10 additions & 3 deletions src/editor-api/rest/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ type UploadCompleteData = {
* @param data - The data for the upload
* @returns A promise that resolves with the upload ID and s3 key
*/
const check = (res: Response) => {
if (!res.ok) {
throw new Error(`upload error ${res.status}: ${res.statusText}`);
}
return res;
};

export const uploadStart = (data: UploadStartData): Promise<{ uploadId: string, key: string }> => {
return fetch(`${api.apiUrl}/upload/start-upload`, {
method: 'POST',
Expand All @@ -60,7 +67,7 @@ export const uploadStart = (data: UploadStartData): Promise<{ uploadId: string,
'Authorization': `Bearer ${api.accessToken}`,
'Content-Type': 'application/json'
}
}).then(res => res.json());
}).then(check).then(res => res.json());
};

/**
Expand All @@ -77,7 +84,7 @@ export const uploadUrls = (data: UploadUrlsData): Promise<{ signedUrls: string[]
'Authorization': `Bearer ${api.accessToken}`,
'Content-Type': 'application/json'
}
}).then(res => res.json());
}).then(check).then(res => res.json());
};

/**
Expand All @@ -94,5 +101,5 @@ export const uploadComplete = (data: UploadCompleteData): Promise<Response> => {
'Authorization': `Bearer ${api.accessToken}`,
'Content-Type': 'application/json'
}
});
}).then(check);
};
2 changes: 1 addition & 1 deletion src/editor/assets/assets-rename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ editor.once('load', () => {
const changeName = function (assetId: string | number, assetName: string) {
editor.api.globals.rest.assets.assetUpdate(assetId, { name: assetName })
.on('error', (err, data) => {
log.error(err + data);
log.error`rename error: ${err}${data}`;
editor.call('status:error', `Couldn't update the name: ${data}`);
});
};
Expand Down
2 changes: 1 addition & 1 deletion src/editor/assets/assets-sprite-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ editor.once('load', () => {
if (callback) {
callback(status);
} else {
log.error('error', status, res);
log.error`sprite utils error ${status}: ${res}`;
}
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/editor/assets/assets-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ editor.once('load', () => {
// console.log(op);
asset.sync.write(op);
} else {
log.error(`realtime operation on missing asset: ${op.p[1]}`);
log.error`realtime operation on missing asset: ${op.p[1]}`;
}
});

Expand Down
4 changes: 4 additions & 0 deletions src/editor/assets/assets-texture-convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ editor.once('load', () => {

// FIXME: No way to use arrayBuffer with AJAX
const response = await fetch(`/api/assets/${id}/download?branchId=${config.self.branch.id}`);
if (!response.ok) {
log.error`texture download failed ${response.status}: ${response.statusText}`;
return;
}
const buffer = await response.arrayBuffer();

// N.B. Update to use frontend URL
Expand Down
1 change: 1 addition & 0 deletions src/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class MainEditor extends Editor<EditorMethods> {
window.editor = new MainEditor();

setSentryTags({
user_id: config.self?.id,
project_id: config.project?.id,
scene_id: config.scene?.id,
branch_id: config.self?.branch?.id
Expand Down
5 changes: 2 additions & 3 deletions src/editor/entities/entities-treeview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,9 +810,8 @@ class EntitiesTreeView extends TreeView {
if (child) {
treeViewItem.append(this._onAddEntity(child));
} else {
const err = `Cannot find child entity ${childId} of parent "${entity.get('name')}" (${resourceId})`;
log.error(err);
editor.call('status:error', err);
log.error`cannot find child entity ${childId} of parent ${entity.get('name')} (${resourceId})`;
editor.call('status:error', `Cannot find child entity ${childId} of parent "${entity.get('name')}" (${resourceId})`);
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion src/editor/inspector/attributes-inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ class AttributesInspector extends Container {
if (observer) {
field.link([observer], attr.path || attr.paths);
} else {
log.error(`This attributes inspector does not contain a valid observer for attr: "${key}". attr.observer is currently: "${attr.observer}".`);
log.error`attributes inspector does not contain a valid observer for attr: ${key}. attr.observer is currently: ${attr.observer}`;
}
} else {
field.link(this._observers, attr.path || attr.paths);
Expand Down
2 changes: 1 addition & 1 deletion src/editor/pickers/picker-publish-new.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ editor.once('load', () => {
refreshButtonsState();

// error
log.error(status, error);
log.error`publish error ${status}: ${error}`;
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ editor.once('load', () => {
previousCheckpoint.id
);
} else {
log.error(`Trying to view changes in checkpoint: '${currentCheckpoint.id}'. Cannot find previous checkpoint to diff against.`);
log.error`trying to view changes in checkpoint: ${currentCheckpoint.id}. cannot find previous checkpoint to diff against`;
}
});

Expand Down
2 changes: 1 addition & 1 deletion src/editor/realtime/realtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ editor.once('start', () => {
});

realtime.on('error', (err: unknown) => {
console.error('realtime error', err);
log.error(err);
editor.emit('realtime:error', err);
});

Expand Down
4 changes: 4 additions & 0 deletions src/editor/store/sketchFabStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ class SketchFabStore extends BaseStore {
const load = async function (item: { id: string; thumbnails: { images: { url: string }[] }; assets?: unknown }) {
const url = `https://api.sketchfab.com/v3/models/${item.id}`;
const response = await fetch(url);
if (!response.ok) {
log.error`sketchfab fetch failed ${response.status}: ${response.statusText}`;
return null;
}
return self._prepareItem(await response.json(), item.assets);
};

Expand Down
4 changes: 4 additions & 0 deletions src/editor/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ editor.once('load', () => {
url += sortQuery(sortPolicy, sortDescending);

const response = await fetch(url);
if (!response.ok) {
log.error`store search failed ${response.status}: ${response.statusText}`;
return { result: [] };
}
return response.json();
});

Expand Down
2 changes: 1 addition & 1 deletion src/editor/userdata/userdata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ editor.once('load', () => {
// client > server
userdata.sync.on('op', (op: unknown) => {
if (op.oi === null) {
log.error('Tried to send invalid userdata op', op);
log.error`tried to send invalid userdata op: ${op}`;
return;
}

Expand Down
4 changes: 2 additions & 2 deletions src/launch/assets/assets-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ editor.once('load', () => {

const assetData = doc.data;
if (!assetData) {
log.error(`Could not load asset: ${uniqueId}`);
log.error`could not load asset: ${uniqueId}`;
doc.unsubscribe();
doc.destroy();
return callback?.();
Expand Down Expand Up @@ -453,7 +453,7 @@ editor.once('load', () => {
if (asset) {
asset.sync.write(op);
} else {
log.error(`realtime operation on missing asset: ${op.p[1]}`);
log.error`realtime operation on missing asset: ${op.p[1]}`;
}
});
});
1 change: 1 addition & 0 deletions src/launch/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class LaunchEditor extends Editor<EditorMethods> {
window.editor = new LaunchEditor();

setSentryTags({
user_id: config.self?.id,
project_id: config.project?.id,
scene_id: config.scene?.id,
branch_id: config.self?.branch?.id
Expand Down
3 changes: 3 additions & 0 deletions src/launch/realtime/realtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ editor.once('load', () => {
reconnect = function () {
// create new socket...
socket = new WebSocket(config.url.realtime.http);
socket.onerror = () => {
log.error`launch websocket error (url: ${config.url.realtime.http})`;
};
// ... and new sharedb connection
connection = new share.Connection(socket);
// connect again
Expand Down
2 changes: 1 addition & 1 deletion src/launch/sourcefiles/sourcefiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ editor.once('load', () => {

editor.emit('sourcefiles:load', filenames);
}).catch((err: unknown) => {
console.error(err);
log.error(err);
editor.emit('sourcefiles:load', []);
});
});
2 changes: 1 addition & 1 deletion src/launch/viewport/viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ editor.once('load', () => {
};

loadingScript.onerror = function () {
log.error(`Could not load loading screen script: ${config.project.settings.loadingScreenScript}`);
log.error`could not load loading screen script: ${config.project.settings.loadingScreenScript}`;
defaultLoadingScreen();
};

Expand Down
Loading