@meshed-monitor/sdk
Version:
Official JavaScript/TypeScript SDK for MeshedMonitor - All-in-one monitoring platform. Supports both CommonJS and ES Modules.
352 lines (351 loc) • 11.5 kB
JavaScript
// src/index.ts
var MeshedMonitor = class {
constructor(config) {
this.logQueue = [];
this.config = {
apiUrl: "https://api.meshedmonitor.com",
environment: "production",
enableErrorTracking: true,
enableLogStreaming: true,
enableSupport: true,
maxBatchSize: 50,
flushInterval: 5e3,
// 5 seconds
...config
};
this.initialize();
}
initialize() {
if (this.config.enableErrorTracking && typeof window !== "undefined") {
this.setupGlobalErrorHandling();
}
if (this.config.enableLogStreaming) {
this.startLogFlushTimer();
}
}
setupGlobalErrorHandling() {
if (typeof window === "undefined") return;
window.addEventListener("error", (event) => {
this.captureError({
message: event.message,
stack: event.error?.stack,
level: "ERROR",
file: event.filename,
line: event.lineno,
column: event.colno,
url: window.location.href,
userAgent: navigator.userAgent
});
});
window.addEventListener("unhandledrejection", (event) => {
this.captureError({
message: event.reason?.message || "Unhandled Promise Rejection",
stack: event.reason?.stack,
level: "ERROR",
url: window.location.href,
userAgent: navigator.userAgent,
context: {
type: "unhandledrejection",
reason: event.reason
}
});
});
}
startLogFlushTimer() {
if (this.flushTimer) {
clearInterval(this.flushTimer);
}
this.flushTimer = setInterval(() => {
this.flushLogs();
}, this.config.flushInterval);
}
// Error tracking methods
async captureError(error) {
if (!this.config.enableErrorTracking) return;
try {
const errorData = {
...error,
environment: error.context?.environment || this.config.environment,
release: error.context?.release || this.config.release,
userId: error.userId || this.config.userId,
timestamp: (/* @__PURE__ */ new Date()).toISOString()
};
await this.sendRequest("/api/sdk/errors", errorData);
} catch (err) {
if (this.config.debug) {
console.error("Failed to capture error:", err);
}
}
}
captureException(error, context) {
return this.captureError({
message: error.message,
stack: error.stack,
level: "ERROR",
context,
url: typeof window !== "undefined" ? window.location.href : void 0,
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
});
}
// Logging methods
log(level, message, context) {
if (!this.config.enableLogStreaming) return;
const logData = {
level,
message,
userId: this.config.userId,
context,
timestamp: /* @__PURE__ */ new Date()
};
this.logQueue.push(logData);
if (this.logQueue.length >= this.config.maxBatchSize) {
this.flushLogs();
}
}
debug(message, context) {
this.log("DEBUG", message, context);
}
info(message, context) {
this.log("INFO", message, context);
}
warn(message, context) {
this.log("WARN", message, context);
}
error(message, context) {
this.log("ERROR", message, context);
}
// Manually flush all queued logs
async flush() {
await this.flushLogs();
}
async flushLogs() {
if (this.logQueue.length === 0) return;
const logs = this.logQueue.splice(0, this.config.maxBatchSize);
try {
await this.sendRequest("/api/sdk/logs/batch", { logs });
} catch (err) {
if (this.config.debug) {
console.error("Failed to flush logs:", err);
}
this.logQueue.unshift(...logs);
}
}
// Support ticket methods
async createSupportTicket(ticket) {
if (!this.config.enableSupport) {
throw new Error("Support is not enabled");
}
const ticketData = {
...ticket,
userId: ticket.userId || this.config.userId,
environment: ticket.metadata?.environment || this.config.environment,
userAgent: ticket.userAgent || (typeof navigator !== "undefined" ? navigator.userAgent : void 0),
url: ticket.url || (typeof window !== "undefined" ? window.location.href : void 0)
};
return this.sendRequest("/api/sdk/support/tickets", ticketData);
}
// Metric tracking methods
async trackMetric(metric) {
try {
await this.sendRequest("/api/metrics/track", metric);
} catch (err) {
if (this.config.debug) {
console.error("Failed to track metric:", err);
}
}
}
async trackMetrics(metrics) {
try {
await this.sendRequest("/api/metrics/track/batch", { metrics });
} catch (err) {
if (this.config.debug) {
console.error("Failed to track metrics:", err);
}
}
}
// User context methods
setUser(userId) {
this.config.userId = userId;
}
setEnvironment(environment) {
this.config.environment = environment;
}
setRelease(release) {
this.config.release = release;
}
// Utility methods
async health() {
return this.sendRequest("/api/sdk/health", {}, "GET");
}
async sendRequest(endpoint, data = {}, method = "POST") {
const url = `${this.config.apiUrl}${endpoint}`;
const options = {
method,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${this.config.apiKey}`
}
};
if (method === "POST") {
options.body = JSON.stringify(data);
}
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
// Cleanup
destroy() {
if (this.flushTimer) {
clearInterval(this.flushTimer);
}
this.flushLogs();
}
};
var SupportWidget = class {
constructor(monitor) {
this.monitor = monitor;
}
show(options) {
if (typeof document === "undefined") return;
this.hide();
const position = options?.position || "bottom-right";
const theme = options?.theme || "light";
this.container = document.createElement("div");
this.container.id = "meshed-support-widget";
this.container.innerHTML = this.getWidgetHTML(theme);
this.container.style.cssText = this.getWidgetCSS(position);
document.body.appendChild(this.container);
this.attachEventListeners();
}
hide() {
if (this.container) {
this.container.remove();
this.container = void 0;
}
}
getWidgetHTML(theme) {
return `
<div class="widget-button ${theme}">
<span>\u{1F4AC}</span>
</div>
<div class="widget-panel ${theme}" style="display: none;">
<div class="widget-header">
<h3>Need Help?</h3>
<button class="close-btn">\xD7</button>
</div>
<div class="widget-content">
<div class="ticket-type-selection">
<h4>What can we help you with?</h4>
<button class="ticket-type-btn" data-type="SUPPORT_REQUEST">\u{1F4AC} Get Support</button>
<button class="ticket-type-btn" data-type="BUG_REPORT">\u{1F41B} Report Bug</button>
<button class="ticket-type-btn" data-type="FEATURE_REQUEST">\u{1F4A1} Request Feature</button>
</div>
<div class="ticket-form" style="display: none;">
<form id="support-form">
<input type="hidden" id="ticket-type" />
<div class="form-group">
<label for="user-name">Name</label>
<input type="text" id="user-name" required />
</div>
<div class="form-group">
<label for="user-email">Email</label>
<input type="email" id="user-email" required />
</div>
<div class="form-group">
<label for="ticket-title">Title</label>
<input type="text" id="ticket-title" required />
</div>
<div class="form-group">
<label for="ticket-description">Description</label>
<textarea id="ticket-description" rows="4" required></textarea>
</div>
<div class="form-actions">
<button type="button" class="back-btn">Back</button>
<button type="submit">Submit</button>
</div>
</form>
</div>
</div>
</div>
`;
}
getWidgetCSS(position) {
const positions = {
"bottom-right": "bottom: 20px; right: 20px;",
"bottom-left": "bottom: 20px; left: 20px;",
"top-right": "top: 20px; right: 20px;",
"top-left": "top: 20px; left: 20px;"
};
return `
position: fixed;
${positions[position]}
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
`;
}
attachEventListeners() {
if (!this.container) return;
const button = this.container.querySelector(".widget-button");
const panel = this.container.querySelector(".widget-panel");
const closeBtn = this.container.querySelector(".close-btn");
const typeButtons = this.container.querySelectorAll(".ticket-type-btn");
const backBtn = this.container.querySelector(".back-btn");
const form = this.container.querySelector("#support-form");
button?.addEventListener("click", () => {
if (panel) {
const panelElement = panel;
panelElement.style.display = panelElement.style.display === "none" ? "block" : "none";
}
});
closeBtn?.addEventListener("click", () => {
if (panel) panel.style.display = "none";
});
typeButtons.forEach((btn) => {
btn.addEventListener("click", () => {
const type = btn.getAttribute("data-type");
const typeInput = this.container?.querySelector("#ticket-type");
const typeSelection = this.container?.querySelector(".ticket-type-selection");
const ticketForm = this.container?.querySelector(".ticket-form");
if (typeInput) typeInput.value = type || "";
if (typeSelection) typeSelection.style.display = "none";
if (ticketForm) ticketForm.style.display = "block";
});
});
backBtn?.addEventListener("click", () => {
const typeSelection = this.container?.querySelector(".ticket-type-selection");
const ticketForm = this.container?.querySelector(".ticket-form");
if (typeSelection) typeSelection.style.display = "block";
if (ticketForm) ticketForm.style.display = "none";
});
form?.addEventListener("submit", async (e) => {
e.preventDefault();
await this.handleFormSubmit();
});
}
async handleFormSubmit() {
if (!this.container) return;
const form = this.container.querySelector("#support-form");
const ticket = {
type: this.container.querySelector("#ticket-type").value,
title: this.container.querySelector("#ticket-title").value,
description: this.container.querySelector("#ticket-description").value,
userName: this.container.querySelector("#user-name").value,
userEmail: this.container.querySelector("#user-email").value
};
try {
await this.monitor.createSupportTicket(ticket);
alert("Thank you! Your ticket has been submitted successfully.");
this.hide();
} catch (error) {
alert("Sorry, there was an error submitting your ticket. Please try again.");
}
}
};
var index_default = MeshedMonitor;
export {
MeshedMonitor,
SupportWidget,
index_default as default
};