@magnet-cms/plugin-sentry 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/index.cjs +921 -0
- package/dist/backend/index.d.cts +21 -0
- package/dist/backend/index.d.ts +21 -0
- package/dist/backend/index.js +7 -0
- package/dist/chunk-NDBQHOLK.js +907 -0
- package/dist/frontend/bundle.iife.js +24998 -0
- package/dist/frontend/bundle.iife.js.map +1 -0
- package/dist/index.cjs +922 -0
- package/dist/index.d.cts +305 -0
- package/dist/index.d.ts +305 -0
- package/dist/index.js +6 -0
- package/package.json +87 -0
- package/src/admin/components/error-metrics.tsx +54 -0
- package/src/admin/components/feedback-widget.tsx +76 -0
- package/src/admin/components/recent-issues.tsx +100 -0
- package/src/admin/hooks/use-project-filter.ts +59 -0
- package/src/admin/index.ts +121 -0
- package/src/admin/pages/sentry-dashboard.tsx +149 -0
- package/src/admin/pages/sentry-issues.tsx +269 -0
- package/src/admin/pages/sentry-settings.tsx +182 -0
|
@@ -0,0 +1,921 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var common = require('@nestjs/common');
|
|
4
|
+
var core$1 = require('@magnet-cms/core');
|
|
5
|
+
var core = require('@nestjs/core');
|
|
6
|
+
var nestjs = require('@sentry/nestjs');
|
|
7
|
+
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
13
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
14
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
15
|
+
}) : x)(function(x) {
|
|
16
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
17
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
18
|
+
});
|
|
19
|
+
var __esm = (fn, res) => function __init() {
|
|
20
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
21
|
+
};
|
|
22
|
+
var __export = (target, all) => {
|
|
23
|
+
for (var name in all)
|
|
24
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
25
|
+
};
|
|
26
|
+
var __copyProps = (to, from, except, desc) => {
|
|
27
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
28
|
+
for (let key of __getOwnPropNames(from))
|
|
29
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
30
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
31
|
+
}
|
|
32
|
+
return to;
|
|
33
|
+
};
|
|
34
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
|
+
|
|
36
|
+
// src/backend/constants.ts
|
|
37
|
+
exports.SENTRY_OPTIONS = void 0;
|
|
38
|
+
var init_constants = __esm({
|
|
39
|
+
"src/backend/constants.ts"() {
|
|
40
|
+
exports.SENTRY_OPTIONS = "SENTRY_OPTIONS";
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
44
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
45
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
46
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
47
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
48
|
+
}
|
|
49
|
+
function _ts_metadata(k, v) {
|
|
50
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
51
|
+
}
|
|
52
|
+
function _ts_param(paramIndex, decorator) {
|
|
53
|
+
return function(target, key) {
|
|
54
|
+
decorator(target, key, paramIndex);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function inferSentryApiBaseUrlFromDsn(dsn) {
|
|
58
|
+
if (!dsn?.trim()) return void 0;
|
|
59
|
+
try {
|
|
60
|
+
const host = new URL(dsn).hostname;
|
|
61
|
+
if (host.includes(".ingest.us.sentry.io")) return "https://us.sentry.io";
|
|
62
|
+
if (host.includes(".ingest.de.sentry.io")) return "https://de.sentry.io";
|
|
63
|
+
return void 0;
|
|
64
|
+
} catch {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function sentryApiFailureMessage(status, statusText) {
|
|
69
|
+
const base = `Sentry API error: ${status} ${statusText}`;
|
|
70
|
+
if (status === 401 || status === 403) {
|
|
71
|
+
return `${base}. Check SENTRY_AUTH_TOKEN scopes (e.g. org:read, project:read), that SENTRY_ORG and SENTRY_PROJECT are URL slugs (not numeric IDs), and SENTRY_URL matches your region (e.g. https://us.sentry.io when the DSN uses ingest.us.sentry.io).`;
|
|
72
|
+
}
|
|
73
|
+
if (status === 404) {
|
|
74
|
+
return `${base}. Check SENTRY_ORG and SENTRY_PROJECT slugs and SENTRY_URL.`;
|
|
75
|
+
}
|
|
76
|
+
return base;
|
|
77
|
+
}
|
|
78
|
+
var SentryApiService;
|
|
79
|
+
var init_sentry_api_service = __esm({
|
|
80
|
+
"src/backend/services/sentry-api.service.ts"() {
|
|
81
|
+
init_constants();
|
|
82
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
83
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
84
|
+
__name(_ts_param, "_ts_param");
|
|
85
|
+
__name(inferSentryApiBaseUrlFromDsn, "inferSentryApiBaseUrlFromDsn");
|
|
86
|
+
__name(sentryApiFailureMessage, "sentryApiFailureMessage");
|
|
87
|
+
SentryApiService = class {
|
|
88
|
+
static {
|
|
89
|
+
__name(this, "SentryApiService");
|
|
90
|
+
}
|
|
91
|
+
cache = /* @__PURE__ */ new Map();
|
|
92
|
+
scopeCache;
|
|
93
|
+
baseUrl;
|
|
94
|
+
authToken;
|
|
95
|
+
organization;
|
|
96
|
+
project;
|
|
97
|
+
constructor(config) {
|
|
98
|
+
this.baseUrl = config.sentryUrl ?? inferSentryApiBaseUrlFromDsn(config.dsn) ?? "https://sentry.io";
|
|
99
|
+
this.authToken = config.authToken;
|
|
100
|
+
this.organization = config.organization;
|
|
101
|
+
this.project = config.project;
|
|
102
|
+
}
|
|
103
|
+
/** Returns true when all required fields for API access are configured. */
|
|
104
|
+
isConfigured() {
|
|
105
|
+
return !!(this.authToken && this.organization && this.project);
|
|
106
|
+
}
|
|
107
|
+
/** Returns true when org-level API access is configured (no project required). */
|
|
108
|
+
isOrgConfigured() {
|
|
109
|
+
return !!(this.authToken && this.organization);
|
|
110
|
+
}
|
|
111
|
+
get orgSlug() {
|
|
112
|
+
return this.organization;
|
|
113
|
+
}
|
|
114
|
+
get projectSlug() {
|
|
115
|
+
return this.project;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Fetch org-level aggregate stats across all projects.
|
|
119
|
+
* Uses the org issues endpoint — no project slug required.
|
|
120
|
+
*/
|
|
121
|
+
async getOrgStats() {
|
|
122
|
+
if (!this.isOrgConfigured()) {
|
|
123
|
+
throw new Error("Sentry API not configured: authToken and organization are required");
|
|
124
|
+
}
|
|
125
|
+
const issuesUrl = `${this.baseUrl}/api/0/organizations/${this.organization}/issues/?query=is:unresolved&limit=100`;
|
|
126
|
+
const issues = await this.fetchCached(issuesUrl);
|
|
127
|
+
const unresolvedIssues = issues.length;
|
|
128
|
+
const totalErrors = issues.reduce((sum, issue) => sum + Number(issue.count || 0), 0);
|
|
129
|
+
const cutoff24h = Date.now() - 864e5;
|
|
130
|
+
const errorsLast24h = issues.filter((issue) => new Date(issue.lastSeen).getTime() > cutoff24h).length;
|
|
131
|
+
return {
|
|
132
|
+
totalErrors,
|
|
133
|
+
unresolvedIssues,
|
|
134
|
+
errorsLast24h
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Fetch issues from the Sentry organization (all projects).
|
|
139
|
+
* @param query - Optional Sentry search query (default: 'is:unresolved')
|
|
140
|
+
*/
|
|
141
|
+
async getOrgIssues(query = "is:unresolved") {
|
|
142
|
+
if (!this.isOrgConfigured()) {
|
|
143
|
+
throw new Error("Sentry API not configured: authToken and organization are required");
|
|
144
|
+
}
|
|
145
|
+
const url = `${this.baseUrl}/api/0/organizations/${this.organization}/issues/?query=${encodeURIComponent(query)}&limit=25`;
|
|
146
|
+
return this.fetchCached(url);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Fetch project-level stats: total errors, unresolved issues, errors last 24h.
|
|
150
|
+
* Derives all stats from the issues endpoint to keep it a single API call.
|
|
151
|
+
* @param projectSlug - Override the configured project slug.
|
|
152
|
+
* Throws if not configured.
|
|
153
|
+
*/
|
|
154
|
+
async getProjectStats(projectSlug) {
|
|
155
|
+
const project = projectSlug ?? this.project;
|
|
156
|
+
if (!this.authToken || !this.organization || !project) {
|
|
157
|
+
throw new Error("Sentry API not configured: authToken, organization, and project are required");
|
|
158
|
+
}
|
|
159
|
+
const issuesUrl = `${this.baseUrl}/api/0/projects/${this.organization}/${project}/issues/?query=is:unresolved&limit=100`;
|
|
160
|
+
const issues = await this.fetchCached(issuesUrl);
|
|
161
|
+
const unresolvedIssues = issues.length;
|
|
162
|
+
const totalErrors = issues.reduce((sum, issue) => sum + Number(issue.count || 0), 0);
|
|
163
|
+
const cutoff24h = Date.now() - 864e5;
|
|
164
|
+
const errorsLast24h = issues.filter((issue) => new Date(issue.lastSeen).getTime() > cutoff24h).length;
|
|
165
|
+
return {
|
|
166
|
+
totalErrors,
|
|
167
|
+
unresolvedIssues,
|
|
168
|
+
errorsLast24h
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Fetch issues from the Sentry project.
|
|
173
|
+
* @param query - Optional Sentry search query (default: 'is:unresolved')
|
|
174
|
+
* @param projectSlug - Override the configured project slug.
|
|
175
|
+
*/
|
|
176
|
+
async getIssues(query = "is:unresolved", projectSlug) {
|
|
177
|
+
const project = projectSlug ?? this.project;
|
|
178
|
+
if (!this.authToken || !this.organization || !project) {
|
|
179
|
+
throw new Error("Sentry API not configured: authToken, organization, and project are required");
|
|
180
|
+
}
|
|
181
|
+
const url = `${this.baseUrl}/api/0/projects/${this.organization}/${project}/issues/?query=${encodeURIComponent(query)}&limit=25`;
|
|
182
|
+
return this.fetchCached(url);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Fetch all projects in the organization.
|
|
186
|
+
* Requires authToken and organization (project not required).
|
|
187
|
+
* Sets isActive=true for the project matching the configured SENTRY_PROJECT.
|
|
188
|
+
* Populates errorCount from embedded stats if the API returns them.
|
|
189
|
+
*/
|
|
190
|
+
async getOrganizationProjects() {
|
|
191
|
+
if (!this.isOrgConfigured()) {
|
|
192
|
+
throw new Error("Sentry API not configured: authToken and organization are required");
|
|
193
|
+
}
|
|
194
|
+
const url = `${this.baseUrl}/api/0/organizations/${this.organization}/projects/?statsPeriod=24h`;
|
|
195
|
+
const raw = await this.fetchCached(url);
|
|
196
|
+
return raw.map((p) => ({
|
|
197
|
+
id: p.id,
|
|
198
|
+
slug: p.slug,
|
|
199
|
+
name: p.name,
|
|
200
|
+
platform: p.platform,
|
|
201
|
+
status: p.status,
|
|
202
|
+
dateCreated: p.dateCreated,
|
|
203
|
+
isActive: p.slug === this.project,
|
|
204
|
+
errorCount: p.stats ? p.stats.reduce((sum, [, count]) => sum + count, 0) : null
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Probe token scopes by making lightweight requests to known Sentry endpoints.
|
|
209
|
+
* Results are cached for 5 minutes.
|
|
210
|
+
*
|
|
211
|
+
* Probed scopes: org:read, project:read, event:read, alerts:read
|
|
212
|
+
* Non-200/non-403 responses are treated as scope unavailable.
|
|
213
|
+
* event:read probe is skipped when project is not configured.
|
|
214
|
+
*/
|
|
215
|
+
async probeTokenScopes() {
|
|
216
|
+
const allFalse = {
|
|
217
|
+
orgRead: false,
|
|
218
|
+
projectRead: false,
|
|
219
|
+
eventRead: false,
|
|
220
|
+
alertsRead: false
|
|
221
|
+
};
|
|
222
|
+
if (!this.isOrgConfigured()) {
|
|
223
|
+
return allFalse;
|
|
224
|
+
}
|
|
225
|
+
if (this.scopeCache && this.scopeCache.expiresAt > Date.now()) {
|
|
226
|
+
return this.scopeCache.data;
|
|
227
|
+
}
|
|
228
|
+
const probe = /* @__PURE__ */ __name(async (url) => {
|
|
229
|
+
try {
|
|
230
|
+
const response = await fetch(url, {
|
|
231
|
+
headers: {
|
|
232
|
+
Authorization: `Bearer ${this.authToken}`
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return response.status === 200;
|
|
236
|
+
} catch {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}, "probe");
|
|
240
|
+
const [orgRead, projectRead, alertsRead] = await Promise.all([
|
|
241
|
+
probe(`${this.baseUrl}/api/0/organizations/${this.organization}/`),
|
|
242
|
+
probe(`${this.baseUrl}/api/0/organizations/${this.organization}/projects/?per_page=1`),
|
|
243
|
+
probe(`${this.baseUrl}/api/0/organizations/${this.organization}/alert-rules/?per_page=1`)
|
|
244
|
+
]);
|
|
245
|
+
const eventRead = this.isConfigured() ? await probe(`${this.baseUrl}/api/0/projects/${this.organization}/${this.project}/issues/?limit=1`) : false;
|
|
246
|
+
const scopes = {
|
|
247
|
+
orgRead,
|
|
248
|
+
projectRead,
|
|
249
|
+
eventRead,
|
|
250
|
+
alertsRead
|
|
251
|
+
};
|
|
252
|
+
this.scopeCache = {
|
|
253
|
+
data: scopes,
|
|
254
|
+
expiresAt: Date.now() + 3e5
|
|
255
|
+
};
|
|
256
|
+
return scopes;
|
|
257
|
+
}
|
|
258
|
+
async fetchCached(url) {
|
|
259
|
+
const cached = this.cache.get(url);
|
|
260
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
261
|
+
return cached.data;
|
|
262
|
+
}
|
|
263
|
+
const response = await fetch(url, {
|
|
264
|
+
headers: {
|
|
265
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
266
|
+
"Content-Type": "application/json"
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
if (!response.ok) {
|
|
270
|
+
throw new common.BadGatewayException(sentryApiFailureMessage(response.status, response.statusText));
|
|
271
|
+
}
|
|
272
|
+
const data = await response.json();
|
|
273
|
+
this.cache.set(url, {
|
|
274
|
+
data,
|
|
275
|
+
expiresAt: Date.now() + 6e4
|
|
276
|
+
});
|
|
277
|
+
return data;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
SentryApiService = _ts_decorate([
|
|
281
|
+
common.Injectable(),
|
|
282
|
+
_ts_param(0, common.Inject(exports.SENTRY_OPTIONS)),
|
|
283
|
+
_ts_metadata("design:type", Function),
|
|
284
|
+
_ts_metadata("design:paramtypes", [
|
|
285
|
+
typeof Partial === "undefined" ? Object : Partial
|
|
286
|
+
])
|
|
287
|
+
], SentryApiService);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
291
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
292
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
293
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
294
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
295
|
+
}
|
|
296
|
+
function _ts_metadata2(k, v) {
|
|
297
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
298
|
+
}
|
|
299
|
+
function _ts_param2(paramIndex, decorator) {
|
|
300
|
+
return function(target, key) {
|
|
301
|
+
decorator(target, key, paramIndex);
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function messageFromHttpException(e) {
|
|
305
|
+
const r = e.getResponse();
|
|
306
|
+
if (typeof r === "string") return r;
|
|
307
|
+
if (r && typeof r === "object" && "message" in r) {
|
|
308
|
+
const m = r.message;
|
|
309
|
+
return Array.isArray(m) ? m.join(" ") : String(m);
|
|
310
|
+
}
|
|
311
|
+
return e.message;
|
|
312
|
+
}
|
|
313
|
+
var SentryAdminController;
|
|
314
|
+
var init_sentry_admin_controller = __esm({
|
|
315
|
+
"src/backend/controllers/sentry-admin.controller.ts"() {
|
|
316
|
+
init_sentry_api_service();
|
|
317
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
318
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
319
|
+
__name(_ts_param2, "_ts_param");
|
|
320
|
+
__name(messageFromHttpException, "messageFromHttpException");
|
|
321
|
+
SentryAdminController = class {
|
|
322
|
+
static {
|
|
323
|
+
__name(this, "SentryAdminController");
|
|
324
|
+
}
|
|
325
|
+
sentryApi;
|
|
326
|
+
constructor(sentryApi) {
|
|
327
|
+
this.sentryApi = sentryApi;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* GET /sentry/admin/stats
|
|
331
|
+
* Returns error metrics for the dashboard: totals, unresolved count, 24h errors.
|
|
332
|
+
* Optional ?project= overrides the configured SENTRY_PROJECT.
|
|
333
|
+
* Returns zeroes with isConfigured:false when auth token is not set.
|
|
334
|
+
*/
|
|
335
|
+
async getStats(project) {
|
|
336
|
+
if (!this.sentryApi.isOrgConfigured()) {
|
|
337
|
+
return {
|
|
338
|
+
isConfigured: false,
|
|
339
|
+
totalErrors: 0,
|
|
340
|
+
unresolvedIssues: 0,
|
|
341
|
+
errorsLast24h: 0
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
const stats = project ? await this.sentryApi.getProjectStats(project) : await this.sentryApi.getOrgStats();
|
|
346
|
+
return {
|
|
347
|
+
isConfigured: true,
|
|
348
|
+
...stats
|
|
349
|
+
};
|
|
350
|
+
} catch (e) {
|
|
351
|
+
if (e instanceof common.BadGatewayException) {
|
|
352
|
+
return {
|
|
353
|
+
isConfigured: true,
|
|
354
|
+
apiError: messageFromHttpException(e),
|
|
355
|
+
totalErrors: 0,
|
|
356
|
+
unresolvedIssues: 0,
|
|
357
|
+
errorsLast24h: 0
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
throw e;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* GET /sentry/admin/issues
|
|
365
|
+
* Returns list of Sentry issues. Optional ?query= for search, ?project= to override project.
|
|
366
|
+
*/
|
|
367
|
+
async getIssues(query, project) {
|
|
368
|
+
if (!this.sentryApi.isOrgConfigured()) {
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
return project ? await this.sentryApi.getIssues(query, project) : await this.sentryApi.getOrgIssues(query);
|
|
373
|
+
} catch (e) {
|
|
374
|
+
if (e instanceof common.BadGatewayException) {
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
throw e;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* GET /sentry/admin/status
|
|
382
|
+
* Returns API connectivity status for the settings page.
|
|
383
|
+
*/
|
|
384
|
+
async getStatus() {
|
|
385
|
+
const connected = this.sentryApi.isConfigured();
|
|
386
|
+
return {
|
|
387
|
+
connected,
|
|
388
|
+
organization: this.sentryApi.orgSlug,
|
|
389
|
+
project: this.sentryApi.projectSlug,
|
|
390
|
+
lastSync: connected ? (/* @__PURE__ */ new Date()).toISOString() : null
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* GET /sentry/admin/projects
|
|
395
|
+
* Returns all projects in the organization, with isActive flag for the
|
|
396
|
+
* configured project and errorCount from 24h stats.
|
|
397
|
+
* Returns empty array when org is not configured or Sentry API errors.
|
|
398
|
+
*/
|
|
399
|
+
async getProjects() {
|
|
400
|
+
if (!this.sentryApi.isOrgConfigured()) {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
return await this.sentryApi.getOrganizationProjects();
|
|
405
|
+
} catch (e) {
|
|
406
|
+
if (e instanceof common.BadGatewayException) {
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
throw e;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* GET /sentry/admin/scopes
|
|
414
|
+
* Returns detected token scope availability by probing known Sentry endpoints.
|
|
415
|
+
* Results are cached for 5 minutes on the service.
|
|
416
|
+
* Returns all-false when org is not configured.
|
|
417
|
+
*/
|
|
418
|
+
async getScopes() {
|
|
419
|
+
const allFalse = {
|
|
420
|
+
orgRead: false,
|
|
421
|
+
projectRead: false,
|
|
422
|
+
eventRead: false,
|
|
423
|
+
alertsRead: false
|
|
424
|
+
};
|
|
425
|
+
if (!this.sentryApi.isOrgConfigured()) {
|
|
426
|
+
return allFalse;
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
return await this.sentryApi.probeTokenScopes();
|
|
430
|
+
} catch (e) {
|
|
431
|
+
if (e instanceof common.BadGatewayException) {
|
|
432
|
+
return allFalse;
|
|
433
|
+
}
|
|
434
|
+
throw e;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
_ts_decorate2([
|
|
439
|
+
common.Get("admin/stats"),
|
|
440
|
+
core$1.RestrictedRoute(),
|
|
441
|
+
_ts_param2(0, common.Query("project")),
|
|
442
|
+
_ts_metadata2("design:type", Function),
|
|
443
|
+
_ts_metadata2("design:paramtypes", [
|
|
444
|
+
String
|
|
445
|
+
]),
|
|
446
|
+
_ts_metadata2("design:returntype", Promise)
|
|
447
|
+
], SentryAdminController.prototype, "getStats", null);
|
|
448
|
+
_ts_decorate2([
|
|
449
|
+
common.Get("admin/issues"),
|
|
450
|
+
core$1.RestrictedRoute(),
|
|
451
|
+
_ts_param2(0, common.Query("query")),
|
|
452
|
+
_ts_param2(1, common.Query("project")),
|
|
453
|
+
_ts_metadata2("design:type", Function),
|
|
454
|
+
_ts_metadata2("design:paramtypes", [
|
|
455
|
+
Object,
|
|
456
|
+
String
|
|
457
|
+
]),
|
|
458
|
+
_ts_metadata2("design:returntype", Promise)
|
|
459
|
+
], SentryAdminController.prototype, "getIssues", null);
|
|
460
|
+
_ts_decorate2([
|
|
461
|
+
common.Get("admin/status"),
|
|
462
|
+
core$1.RestrictedRoute(),
|
|
463
|
+
_ts_metadata2("design:type", Function),
|
|
464
|
+
_ts_metadata2("design:paramtypes", []),
|
|
465
|
+
_ts_metadata2("design:returntype", Promise)
|
|
466
|
+
], SentryAdminController.prototype, "getStatus", null);
|
|
467
|
+
_ts_decorate2([
|
|
468
|
+
common.Get("admin/projects"),
|
|
469
|
+
core$1.RestrictedRoute(),
|
|
470
|
+
_ts_metadata2("design:type", Function),
|
|
471
|
+
_ts_metadata2("design:paramtypes", []),
|
|
472
|
+
_ts_metadata2("design:returntype", Promise)
|
|
473
|
+
], SentryAdminController.prototype, "getProjects", null);
|
|
474
|
+
_ts_decorate2([
|
|
475
|
+
common.Get("admin/scopes"),
|
|
476
|
+
core$1.RestrictedRoute(),
|
|
477
|
+
_ts_metadata2("design:type", Function),
|
|
478
|
+
_ts_metadata2("design:paramtypes", []),
|
|
479
|
+
_ts_metadata2("design:returntype", Promise)
|
|
480
|
+
], SentryAdminController.prototype, "getScopes", null);
|
|
481
|
+
SentryAdminController = _ts_decorate2([
|
|
482
|
+
common.Controller("sentry"),
|
|
483
|
+
_ts_metadata2("design:type", Function),
|
|
484
|
+
_ts_metadata2("design:paramtypes", [
|
|
485
|
+
typeof SentryApiService === "undefined" ? Object : SentryApiService
|
|
486
|
+
])
|
|
487
|
+
], SentryAdminController);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
491
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
492
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
493
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
494
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
495
|
+
}
|
|
496
|
+
function _ts_metadata3(k, v) {
|
|
497
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
498
|
+
}
|
|
499
|
+
function _ts_param3(paramIndex, decorator) {
|
|
500
|
+
return function(target, key) {
|
|
501
|
+
decorator(target, key, paramIndex);
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
var SentryConfigController;
|
|
505
|
+
var init_sentry_config_controller = __esm({
|
|
506
|
+
"src/backend/controllers/sentry-config.controller.ts"() {
|
|
507
|
+
init_constants();
|
|
508
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
509
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
510
|
+
__name(_ts_param3, "_ts_param");
|
|
511
|
+
SentryConfigController = class {
|
|
512
|
+
static {
|
|
513
|
+
__name(this, "SentryConfigController");
|
|
514
|
+
}
|
|
515
|
+
options;
|
|
516
|
+
constructor(options) {
|
|
517
|
+
this.options = options;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Return the public Sentry config needed to initialize the Browser SDK
|
|
521
|
+
* in the admin UI feedback widget.
|
|
522
|
+
*
|
|
523
|
+
* Requires authentication — uses the standard Magnet admin guard.
|
|
524
|
+
*/
|
|
525
|
+
getConfig() {
|
|
526
|
+
return {
|
|
527
|
+
dsn: this.options.dsn ?? "",
|
|
528
|
+
enabled: this.options.enabled ?? true,
|
|
529
|
+
environment: this.options.environment ?? process.env.NODE_ENV ?? "production"
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
_ts_decorate3([
|
|
534
|
+
common.Get("config"),
|
|
535
|
+
core$1.RestrictedRoute(),
|
|
536
|
+
_ts_metadata3("design:type", Function),
|
|
537
|
+
_ts_metadata3("design:paramtypes", []),
|
|
538
|
+
_ts_metadata3("design:returntype", typeof SentryClientConfig === "undefined" ? Object : SentryClientConfig)
|
|
539
|
+
], SentryConfigController.prototype, "getConfig", null);
|
|
540
|
+
SentryConfigController = _ts_decorate3([
|
|
541
|
+
common.Controller("sentry"),
|
|
542
|
+
_ts_param3(0, common.Inject(exports.SENTRY_OPTIONS)),
|
|
543
|
+
_ts_metadata3("design:type", Function),
|
|
544
|
+
_ts_metadata3("design:paramtypes", [
|
|
545
|
+
typeof SentryPluginConfig === "undefined" ? Object : SentryPluginConfig
|
|
546
|
+
])
|
|
547
|
+
], SentryConfigController);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
551
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
552
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
553
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
554
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
555
|
+
}
|
|
556
|
+
var SentryContextInterceptor;
|
|
557
|
+
var init_sentry_context_interceptor = __esm({
|
|
558
|
+
"src/backend/interceptors/sentry-context.interceptor.ts"() {
|
|
559
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
560
|
+
SentryContextInterceptor = class {
|
|
561
|
+
static {
|
|
562
|
+
__name(this, "SentryContextInterceptor");
|
|
563
|
+
}
|
|
564
|
+
intercept(context, next) {
|
|
565
|
+
if (context.getType() !== "http") {
|
|
566
|
+
return next.handle();
|
|
567
|
+
}
|
|
568
|
+
const request = context.switchToHttp().getRequest();
|
|
569
|
+
try {
|
|
570
|
+
const Sentry = __require("@sentry/nestjs");
|
|
571
|
+
if (!Sentry.getClient()) return next.handle();
|
|
572
|
+
if (request.user?.id) {
|
|
573
|
+
Sentry.setUser({
|
|
574
|
+
id: request.user.id,
|
|
575
|
+
ip_address: request.ip
|
|
576
|
+
});
|
|
577
|
+
} else {
|
|
578
|
+
Sentry.setUser({
|
|
579
|
+
ip_address: request.ip
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
const { getEventContext } = __require("@magnet-cms/core");
|
|
584
|
+
const ctx = getEventContext();
|
|
585
|
+
if (ctx?.requestId) {
|
|
586
|
+
Sentry.setTag("requestId", ctx.requestId);
|
|
587
|
+
}
|
|
588
|
+
} catch {
|
|
589
|
+
}
|
|
590
|
+
} catch {
|
|
591
|
+
}
|
|
592
|
+
return next.handle();
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
SentryContextInterceptor = _ts_decorate4([
|
|
596
|
+
common.Injectable()
|
|
597
|
+
], SentryContextInterceptor);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// src/backend/sentry.module.ts
|
|
602
|
+
var sentry_module_exports = {};
|
|
603
|
+
__export(sentry_module_exports, {
|
|
604
|
+
SENTRY_OPTIONS: () => exports.SENTRY_OPTIONS,
|
|
605
|
+
SentryModule: () => exports.SentryModule
|
|
606
|
+
});
|
|
607
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
608
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
609
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
610
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
611
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
612
|
+
}
|
|
613
|
+
exports.SentryModule = void 0;
|
|
614
|
+
var init_sentry_module = __esm({
|
|
615
|
+
"src/backend/sentry.module.ts"() {
|
|
616
|
+
init_constants();
|
|
617
|
+
init_sentry_admin_controller();
|
|
618
|
+
init_sentry_config_controller();
|
|
619
|
+
init_sentry_context_interceptor();
|
|
620
|
+
init_sentry_api_service();
|
|
621
|
+
init_constants();
|
|
622
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
623
|
+
exports.SentryModule = class {
|
|
624
|
+
static {
|
|
625
|
+
__name(this, "SentryModule");
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
exports.SentryModule = _ts_decorate5([
|
|
629
|
+
common.Module({
|
|
630
|
+
controllers: [
|
|
631
|
+
SentryConfigController,
|
|
632
|
+
SentryAdminController
|
|
633
|
+
],
|
|
634
|
+
providers: [
|
|
635
|
+
{
|
|
636
|
+
provide: exports.SENTRY_OPTIONS,
|
|
637
|
+
// useFactory (not useValue) — factory is called lazily during DI resolution,
|
|
638
|
+
// after SentryPlugin.forRoot() has stored the config on the static field.
|
|
639
|
+
useFactory: /* @__PURE__ */ __name(() => {
|
|
640
|
+
const { SentryPlugin: SentryPlugin2 } = (init_plugin(), __toCommonJS(plugin_exports));
|
|
641
|
+
return SentryPlugin2._resolvedConfig ?? {};
|
|
642
|
+
}, "useFactory")
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
provide: core.APP_INTERCEPTOR,
|
|
646
|
+
useClass: SentryContextInterceptor
|
|
647
|
+
},
|
|
648
|
+
SentryApiService
|
|
649
|
+
],
|
|
650
|
+
exports: [
|
|
651
|
+
SentryApiService
|
|
652
|
+
]
|
|
653
|
+
})
|
|
654
|
+
], exports.SentryModule);
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
// src/backend/plugin.ts
|
|
659
|
+
var plugin_exports = {};
|
|
660
|
+
__export(plugin_exports, {
|
|
661
|
+
SentryPlugin: () => exports.SentryPlugin
|
|
662
|
+
});
|
|
663
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
664
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
665
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
666
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
667
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
668
|
+
}
|
|
669
|
+
exports.SentryPlugin = void 0;
|
|
670
|
+
var init_plugin = __esm({
|
|
671
|
+
"src/backend/plugin.ts"() {
|
|
672
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
673
|
+
exports.SentryPlugin = class _SentryPlugin {
|
|
674
|
+
static {
|
|
675
|
+
__name(this, "SentryPlugin");
|
|
676
|
+
}
|
|
677
|
+
/** Resolved configuration — stored statically so lifecycle hooks can access it */
|
|
678
|
+
static _resolvedConfig;
|
|
679
|
+
/** Environment variables used by this plugin */
|
|
680
|
+
static envVars = [
|
|
681
|
+
{
|
|
682
|
+
name: "SENTRY_DSN",
|
|
683
|
+
required: true,
|
|
684
|
+
description: "Sentry Data Source Name (DSN) for error reporting"
|
|
685
|
+
}
|
|
686
|
+
];
|
|
687
|
+
/**
|
|
688
|
+
* Create a configured plugin provider for MagnetModule.forRoot().
|
|
689
|
+
*
|
|
690
|
+
* Auto-resolves DSN from the SENTRY_DSN environment variable if not provided.
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* ```typescript
|
|
694
|
+
* SentryPlugin.forRoot({
|
|
695
|
+
* tracesSampleRate: 0.1,
|
|
696
|
+
* environment: 'production',
|
|
697
|
+
* })
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
700
|
+
static forRoot(config) {
|
|
701
|
+
const resolvedConfig = {
|
|
702
|
+
dsn: config?.dsn ?? process.env.SENTRY_DSN,
|
|
703
|
+
tracesSampleRate: config?.tracesSampleRate ?? 0.1,
|
|
704
|
+
profileSessionSampleRate: config?.profileSessionSampleRate ?? 1,
|
|
705
|
+
environment: config?.environment ?? process.env.SENTRY_ENVIRONMENT ?? process.env.NODE_ENV ?? "development",
|
|
706
|
+
release: config?.release ?? process.env.SENTRY_RELEASE,
|
|
707
|
+
debug: config?.debug ?? false,
|
|
708
|
+
enabled: config?.enabled ?? true,
|
|
709
|
+
attachStacktrace: config?.attachStacktrace ?? true,
|
|
710
|
+
maxBreadcrumbs: config?.maxBreadcrumbs ?? 100,
|
|
711
|
+
authToken: config?.authToken ?? process.env.SENTRY_AUTH_TOKEN,
|
|
712
|
+
organization: config?.organization ?? process.env.SENTRY_ORG,
|
|
713
|
+
project: config?.project ?? process.env.SENTRY_PROJECT,
|
|
714
|
+
sentryUrl: config?.sentryUrl ?? process.env.SENTRY_URL
|
|
715
|
+
};
|
|
716
|
+
_SentryPlugin._resolvedConfig = resolvedConfig;
|
|
717
|
+
return {
|
|
718
|
+
type: "plugin",
|
|
719
|
+
plugin: _SentryPlugin,
|
|
720
|
+
options: {
|
|
721
|
+
...resolvedConfig
|
|
722
|
+
},
|
|
723
|
+
envVars: _SentryPlugin.envVars
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Called by the plugin lifecycle service after NestJS module initialization.
|
|
728
|
+
* Initializes the Sentry SDK if not already initialized (e.g., via instrument.ts).
|
|
729
|
+
*
|
|
730
|
+
* NOTE: For full performance auto-instrumentation (HTTP, DB query tracing),
|
|
731
|
+
* initialize Sentry before NestJS bootstrap by importing `instrument.ts`
|
|
732
|
+
* at the top of main.ts and calling `initSentryInstrumentation()`.
|
|
733
|
+
* Late initialization still captures errors and breadcrumbs correctly.
|
|
734
|
+
*/
|
|
735
|
+
async onPluginInit() {
|
|
736
|
+
const config = _SentryPlugin._resolvedConfig;
|
|
737
|
+
if (!config?.enabled) return;
|
|
738
|
+
try {
|
|
739
|
+
const Sentry = __require("@sentry/nestjs");
|
|
740
|
+
if (Sentry.getClient()) return;
|
|
741
|
+
Sentry.init({
|
|
742
|
+
dsn: config.dsn,
|
|
743
|
+
tracesSampleRate: config.tracesSampleRate,
|
|
744
|
+
environment: config.environment,
|
|
745
|
+
release: config.release,
|
|
746
|
+
debug: config.debug,
|
|
747
|
+
enabled: config.enabled,
|
|
748
|
+
attachStacktrace: config.attachStacktrace,
|
|
749
|
+
maxBreadcrumbs: config.maxBreadcrumbs
|
|
750
|
+
});
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Called when the application shuts down.
|
|
756
|
+
* Flushes pending Sentry events before exit.
|
|
757
|
+
*/
|
|
758
|
+
async onPluginDestroy() {
|
|
759
|
+
try {
|
|
760
|
+
const Sentry = __require("@sentry/nestjs");
|
|
761
|
+
await Sentry.close(2e3);
|
|
762
|
+
} catch {
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
exports.SentryPlugin = _ts_decorate6([
|
|
767
|
+
core$1.Plugin({
|
|
768
|
+
name: "sentry",
|
|
769
|
+
description: "Sentry error tracking and performance monitoring for Magnet CMS",
|
|
770
|
+
version: "0.1.0",
|
|
771
|
+
module: /* @__PURE__ */ __name(() => (
|
|
772
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
773
|
+
(init_sentry_module(), __toCommonJS(sentry_module_exports)).SentryModule
|
|
774
|
+
), "module"),
|
|
775
|
+
frontend: {
|
|
776
|
+
routes: [
|
|
777
|
+
{
|
|
778
|
+
path: "sentry",
|
|
779
|
+
componentId: "SentryDashboard",
|
|
780
|
+
requiresAuth: true,
|
|
781
|
+
children: [
|
|
782
|
+
{
|
|
783
|
+
path: "",
|
|
784
|
+
componentId: "SentryDashboard"
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
path: "issues",
|
|
788
|
+
componentId: "SentryIssues"
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
path: "settings",
|
|
792
|
+
componentId: "SentrySettings"
|
|
793
|
+
}
|
|
794
|
+
]
|
|
795
|
+
}
|
|
796
|
+
],
|
|
797
|
+
sidebar: [
|
|
798
|
+
{
|
|
799
|
+
id: "sentry",
|
|
800
|
+
title: "Sentry",
|
|
801
|
+
url: "/sentry",
|
|
802
|
+
icon: "AlertTriangle",
|
|
803
|
+
order: 80,
|
|
804
|
+
items: [
|
|
805
|
+
{
|
|
806
|
+
id: "sentry-dashboard",
|
|
807
|
+
title: "Dashboard",
|
|
808
|
+
url: "/sentry",
|
|
809
|
+
icon: "BarChart3"
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
id: "sentry-issues",
|
|
813
|
+
title: "Issues",
|
|
814
|
+
url: "/sentry/issues",
|
|
815
|
+
icon: "Bug"
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
id: "sentry-projects",
|
|
819
|
+
title: "Projects",
|
|
820
|
+
url: "/sentry/projects",
|
|
821
|
+
icon: "FolderKanban"
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
id: "sentry-settings",
|
|
825
|
+
title: "Settings",
|
|
826
|
+
url: "/sentry/settings",
|
|
827
|
+
icon: "Settings"
|
|
828
|
+
}
|
|
829
|
+
]
|
|
830
|
+
}
|
|
831
|
+
]
|
|
832
|
+
}
|
|
833
|
+
})
|
|
834
|
+
], exports.SentryPlugin);
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// src/backend/index.ts
|
|
839
|
+
init_plugin();
|
|
840
|
+
init_sentry_module();
|
|
841
|
+
init_constants();
|
|
842
|
+
|
|
843
|
+
// src/backend/instrumentation.ts
|
|
844
|
+
function initSentryInstrumentation(config) {
|
|
845
|
+
try {
|
|
846
|
+
const Sentry = __require("@sentry/nestjs");
|
|
847
|
+
if (Sentry.getClient()) return;
|
|
848
|
+
Sentry.init({
|
|
849
|
+
dsn: config?.dsn ?? process.env.SENTRY_DSN,
|
|
850
|
+
tracesSampleRate: config?.tracesSampleRate ?? 0.1,
|
|
851
|
+
profileSessionSampleRate: config?.profileSessionSampleRate ?? 1,
|
|
852
|
+
environment: config?.environment ?? process.env.SENTRY_ENVIRONMENT ?? process.env.NODE_ENV ?? "development",
|
|
853
|
+
release: config?.release ?? process.env.SENTRY_RELEASE,
|
|
854
|
+
debug: config?.debug ?? false,
|
|
855
|
+
enabled: config?.enabled ?? true,
|
|
856
|
+
attachStacktrace: config?.attachStacktrace ?? true,
|
|
857
|
+
maxBreadcrumbs: config?.maxBreadcrumbs ?? 100
|
|
858
|
+
});
|
|
859
|
+
} catch {
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
__name(initSentryInstrumentation, "initSentryInstrumentation");
|
|
863
|
+
|
|
864
|
+
// src/backend/helpers/span.ts
|
|
865
|
+
async function withSentrySpan(name, op, fn) {
|
|
866
|
+
try {
|
|
867
|
+
const Sentry = __require("@sentry/nestjs");
|
|
868
|
+
if (!Sentry.getClient()) return fn();
|
|
869
|
+
return await Sentry.startSpan({
|
|
870
|
+
name,
|
|
871
|
+
op
|
|
872
|
+
}, () => fn());
|
|
873
|
+
} catch {
|
|
874
|
+
return fn();
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
__name(withSentrySpan, "withSentrySpan");
|
|
878
|
+
|
|
879
|
+
// src/backend/decorators/sentry-span.decorator.ts
|
|
880
|
+
function SentrySpan(name, op = "function") {
|
|
881
|
+
return /* @__PURE__ */ __name(function sentrySpanDecorator(targetOrMethod, propertyKeyOrContext, descriptor) {
|
|
882
|
+
if (descriptor !== void 0 && typeof descriptor.value === "function") {
|
|
883
|
+
const originalMethod = descriptor.value;
|
|
884
|
+
const spanName = name ?? String(propertyKeyOrContext ?? "unknown");
|
|
885
|
+
descriptor.value = async function(...args) {
|
|
886
|
+
return withSentrySpan(spanName, op, () => originalMethod.apply(this, args));
|
|
887
|
+
};
|
|
888
|
+
return descriptor;
|
|
889
|
+
}
|
|
890
|
+
if (typeof targetOrMethod === "function") {
|
|
891
|
+
const originalMethod = targetOrMethod;
|
|
892
|
+
const ctx = propertyKeyOrContext;
|
|
893
|
+
const spanName = name ?? String(ctx?.name ?? "unknown");
|
|
894
|
+
return async function(...args) {
|
|
895
|
+
return withSentrySpan(spanName, op, () => originalMethod.apply(this, args));
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
}, "sentrySpanDecorator");
|
|
899
|
+
}
|
|
900
|
+
__name(SentrySpan, "SentrySpan");
|
|
901
|
+
function MagnetSentryCron(slug, options) {
|
|
902
|
+
return (target, propertyKey, descriptor) => {
|
|
903
|
+
const monitorSlug = slug ?? `${target.constructor.name}-${String(propertyKey)}`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
904
|
+
try {
|
|
905
|
+
const { SentryCron: SentryCron2 } = __require("@sentry/nestjs");
|
|
906
|
+
return SentryCron2(monitorSlug, options)(target, propertyKey, descriptor) ?? descriptor;
|
|
907
|
+
} catch {
|
|
908
|
+
return descriptor;
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
__name(MagnetSentryCron, "MagnetSentryCron");
|
|
913
|
+
|
|
914
|
+
Object.defineProperty(exports, "SentryCron", {
|
|
915
|
+
enumerable: true,
|
|
916
|
+
get: function () { return nestjs.SentryCron; }
|
|
917
|
+
});
|
|
918
|
+
exports.MagnetSentryCron = MagnetSentryCron;
|
|
919
|
+
exports.SentrySpan = SentrySpan;
|
|
920
|
+
exports.initSentryInstrumentation = initSentryInstrumentation;
|
|
921
|
+
exports.withSentrySpan = withSentrySpan;
|