New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

toucan-js

Package Overview
Dependencies
Maintainers
1
Versions
39
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

toucan-js - npm Package Compare versions

Comparing version 2.1.0 to 2.2.0

116

dist/index.cjs.js

@@ -11,2 +11,3 @@ 'use strict';

constructor(options) {
this.options = options;
if (!options.dsn || options.dsn.length === 0) {

@@ -16,2 +17,3 @@ // If an empty DSN is passed, we should treat it as valid option which signifies disabling the SDK.

this.disabled = true;
this.debug(() => this.log("dsn missing, SDK is disabled"));
}

@@ -21,4 +23,4 @@ else {

this.disabled = false;
this.debug(() => this.log(`dsn parsed, full store endpoint: ${this.url}`));
}
this.options = options;
this.user = undefined;

@@ -45,3 +47,3 @@ this.request = this.toSentryRequest(options.event.request);

catch (err) {
console.warn(err);
this.debug(() => this.error(err));
}

@@ -116,6 +118,9 @@ };

* @param exception An exception-like object.
* @returns The generated eventId.
* @returns The generated eventId, or undefined if event wasn't scheduled.
*/
captureException(exception) {
this.debug(() => this.log(`calling captureException`));
const event = this.buildEvent({});
if (!event)
return;
this.options.event.waitUntil(this.reportException(event, exception));

@@ -129,6 +134,9 @@ return event.event_id;

* @param level Define the level of the message.
* @returns The generated eventId.
* @returns The generated eventId, or undefined if event wasn't scheduled.
*/
captureMessage(message, level = "info") {
this.debug(() => this.log(`calling captureMessage`));
const event = this.buildEvent({ level, message });
if (!event)
return;
this.options.event.waitUntil(this.reportMessage(event));

@@ -156,12 +164,14 @@ return event.event_id;

/**
* Builds a Sentry Event and calls waitUntil on the current worker event.
* Send data to Sentry.
*
* @param data Custom Event data
* @param data Event data
*/
postEvent(data) {
async postEvent(data) {
var _a, _b;
// We are sending User-Agent for backwards compatibility with older Sentry
let headers = {
"Content-Type": "application/json",
"User-Agent": "toucan-js/2.1.0",
"User-Agent": "toucan-js/2.2.0",
};
// Build headers
if ((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.transportOptions) === null || _b === void 0 ? void 0 : _b.headers) {

@@ -173,7 +183,20 @@ headers = {

}
return fetch(this.url, {
// Build body string
const body = JSON.stringify(data);
// Log the outgoing request
this.debug(() => {
this.log(`sending request to Sentry with headers: ${JSON.stringify(headers)} and body: ${body}`);
});
// Send to Sentry and wait for Response
const response = await fetch(this.url, {
method: "POST",
body: JSON.stringify(data),
body,
headers,
});
// Log the response
await this.debug(() => {
return this.logResponse(response);
});
// Resolve with response
return response;
}

@@ -188,2 +211,9 @@ /**

var _a;
const sampleRate = this.options.sampleRate;
// 1.0 === 100% events are sent
// 0.0 === 0% events are sent
if (typeof sampleRate === "number" && Math.random() > sampleRate) {
this.debug(() => this.log(`skipping this event (sampleRate === ${sampleRate})`));
return;
}
const pkg = this.options.pkg;

@@ -220,3 +250,3 @@ // 'release' option takes precedence, if not present - try to derive from package.json

name: "toucan-js",
version: "2.1.0",
version: "2.2.0",
},

@@ -322,3 +352,3 @@ };

else {
console.warn("allowlist must be an array of strings, or a regular expression.");
this.debug(() => this.warn("allowlist must be an array of strings, or a regular expression."));
return {};

@@ -429,5 +459,6 @@ }

getBreadcrumbs() {
const MAX_BREADCRUMBS = 100;
if (this.breadcrumbs.length > MAX_BREADCRUMBS) {
return this.breadcrumbs.slice(this.breadcrumbs.length - MAX_BREADCRUMBS);
var _a;
const maxBreadcrumbs = (_a = this.options.maxBreadcrumbs) !== null && _a !== void 0 ? _a : 100;
if (this.breadcrumbs.length > maxBreadcrumbs) {
return this.breadcrumbs.slice(this.breadcrumbs.length - maxBreadcrumbs);
}

@@ -438,4 +469,59 @@ else {

}
/**
* Runs a callback if debug === true.
* Use this to delay execution of debug logic, to ensure toucan doesn't burn I/O in non-debug mode.
*
* @param callback
*/
debug(callback) {
if (this.options.debug) {
return callback();
}
}
log(message) {
console.log(`toucan-js: ${message}`);
}
warn(message) {
console.warn(`toucan-js: ${message}`);
}
error(message) {
console.error(`toucan-js: ${message}`);
}
/**
* Reads and logs Response object from Sentry. Uses a clone, not the original, so the body can be used elsewhere.
* Do not use without this.debug wrapper.
*
* @param originalResponse Response
*/
async logResponse(originalResponse) {
var _a;
// Make a copy of original response so the body can still be read elsewhere
const response = originalResponse.clone();
let responseText = "";
// Read response body, set to empty if fails
try {
responseText = await response.text();
}
catch (e) {
responseText += "";
}
// Parse origin from response.url, but at least give some string if parsing fails.
let origin = "Sentry";
try {
const originUrl = new URL(response.url);
origin = originUrl.origin;
}
catch (e) {
origin = (_a = response.url) !== null && _a !== void 0 ? _a : "Sentry";
}
const msg = `${origin} responded with [${response.status} ${response.statusText}]: ${responseText}`;
if (response.ok) {
this.log(msg);
}
else {
this.error(msg);
}
}
}
module.exports = Toucan;

@@ -88,3 +88,3 @@ /**

* @param exception An exception-like object.
* @returns The generated eventId.
* @returns The generated eventId, or undefined if event wasn't scheduled.
*/

@@ -97,3 +97,3 @@ captureException(exception: unknown): string | undefined;

* @param level Define the level of the message.
* @returns The generated eventId.
* @returns The generated eventId, or undefined if event wasn't scheduled.
*/

@@ -116,5 +116,5 @@ captureMessage(message: string, level?: Level): string | undefined;

/**
* Builds a Sentry Event and calls waitUntil on the current worker event.
* Send data to Sentry.
*
* @param data Custom Event data
* @param data Event data
*/

@@ -187,3 +187,20 @@ private postEvent;

private getBreadcrumbs;
/**
* Runs a callback if debug === true.
* Use this to delay execution of debug logic, to ensure toucan doesn't burn I/O in non-debug mode.
*
* @param callback
*/
private debug;
private log;
private warn;
private error;
/**
* Reads and logs Response object from Sentry. Uses a clone, not the original, so the body can be used elsewhere.
* Do not use without this.debug wrapper.
*
* @param originalResponse Response
*/
private logResponse;
}
//# sourceMappingURL=index.d.ts.map

@@ -9,2 +9,3 @@ import { API } from '@sentry/core';

constructor(options) {
this.options = options;
if (!options.dsn || options.dsn.length === 0) {

@@ -14,2 +15,3 @@ // If an empty DSN is passed, we should treat it as valid option which signifies disabling the SDK.

this.disabled = true;
this.debug(() => this.log("dsn missing, SDK is disabled"));
}

@@ -19,4 +21,4 @@ else {

this.disabled = false;
this.debug(() => this.log(`dsn parsed, full store endpoint: ${this.url}`));
}
this.options = options;
this.user = undefined;

@@ -43,3 +45,3 @@ this.request = this.toSentryRequest(options.event.request);

catch (err) {
console.warn(err);
this.debug(() => this.error(err));
}

@@ -114,6 +116,9 @@ };

* @param exception An exception-like object.
* @returns The generated eventId.
* @returns The generated eventId, or undefined if event wasn't scheduled.
*/
captureException(exception) {
this.debug(() => this.log(`calling captureException`));
const event = this.buildEvent({});
if (!event)
return;
this.options.event.waitUntil(this.reportException(event, exception));

@@ -127,6 +132,9 @@ return event.event_id;

* @param level Define the level of the message.
* @returns The generated eventId.
* @returns The generated eventId, or undefined if event wasn't scheduled.
*/
captureMessage(message, level = "info") {
this.debug(() => this.log(`calling captureMessage`));
const event = this.buildEvent({ level, message });
if (!event)
return;
this.options.event.waitUntil(this.reportMessage(event));

@@ -154,12 +162,14 @@ return event.event_id;

/**
* Builds a Sentry Event and calls waitUntil on the current worker event.
* Send data to Sentry.
*
* @param data Custom Event data
* @param data Event data
*/
postEvent(data) {
async postEvent(data) {
var _a, _b;
// We are sending User-Agent for backwards compatibility with older Sentry
let headers = {
"Content-Type": "application/json",
"User-Agent": "toucan-js/2.1.0",
"User-Agent": "toucan-js/2.2.0",
};
// Build headers
if ((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.transportOptions) === null || _b === void 0 ? void 0 : _b.headers) {

@@ -171,7 +181,20 @@ headers = {

}
return fetch(this.url, {
// Build body string
const body = JSON.stringify(data);
// Log the outgoing request
this.debug(() => {
this.log(`sending request to Sentry with headers: ${JSON.stringify(headers)} and body: ${body}`);
});
// Send to Sentry and wait for Response
const response = await fetch(this.url, {
method: "POST",
body: JSON.stringify(data),
body,
headers,
});
// Log the response
await this.debug(() => {
return this.logResponse(response);
});
// Resolve with response
return response;
}

@@ -186,2 +209,9 @@ /**

var _a;
const sampleRate = this.options.sampleRate;
// 1.0 === 100% events are sent
// 0.0 === 0% events are sent
if (typeof sampleRate === "number" && Math.random() > sampleRate) {
this.debug(() => this.log(`skipping this event (sampleRate === ${sampleRate})`));
return;
}
const pkg = this.options.pkg;

@@ -218,3 +248,3 @@ // 'release' option takes precedence, if not present - try to derive from package.json

name: "toucan-js",
version: "2.1.0",
version: "2.2.0",
},

@@ -320,3 +350,3 @@ };

else {
console.warn("allowlist must be an array of strings, or a regular expression.");
this.debug(() => this.warn("allowlist must be an array of strings, or a regular expression."));
return {};

@@ -427,5 +457,6 @@ }

getBreadcrumbs() {
const MAX_BREADCRUMBS = 100;
if (this.breadcrumbs.length > MAX_BREADCRUMBS) {
return this.breadcrumbs.slice(this.breadcrumbs.length - MAX_BREADCRUMBS);
var _a;
const maxBreadcrumbs = (_a = this.options.maxBreadcrumbs) !== null && _a !== void 0 ? _a : 100;
if (this.breadcrumbs.length > maxBreadcrumbs) {
return this.breadcrumbs.slice(this.breadcrumbs.length - maxBreadcrumbs);
}

@@ -436,4 +467,59 @@ else {

}
/**
* Runs a callback if debug === true.
* Use this to delay execution of debug logic, to ensure toucan doesn't burn I/O in non-debug mode.
*
* @param callback
*/
debug(callback) {
if (this.options.debug) {
return callback();
}
}
log(message) {
console.log(`toucan-js: ${message}`);
}
warn(message) {
console.warn(`toucan-js: ${message}`);
}
error(message) {
console.error(`toucan-js: ${message}`);
}
/**
* Reads and logs Response object from Sentry. Uses a clone, not the original, so the body can be used elsewhere.
* Do not use without this.debug wrapper.
*
* @param originalResponse Response
*/
async logResponse(originalResponse) {
var _a;
// Make a copy of original response so the body can still be read elsewhere
const response = originalResponse.clone();
let responseText = "";
// Read response body, set to empty if fails
try {
responseText = await response.text();
}
catch (e) {
responseText += "";
}
// Parse origin from response.url, but at least give some string if parsing fails.
let origin = "Sentry";
try {
const originUrl = new URL(response.url);
origin = originUrl.origin;
}
catch (e) {
origin = (_a = response.url) !== null && _a !== void 0 ? _a : "Sentry";
}
const msg = `${origin} responded with [${response.status} ${response.statusText}]: ${responseText}`;
if (response.ok) {
this.log(msg);
}
else {
this.error(msg);
}
}
}
export default Toucan;

17

dist/types.d.ts

@@ -7,14 +7,17 @@ import { Options as SentryOptions, Event as SentryEvent, Breadcrumb as SentryBreadcrumb, StackFrame } from "@sentry/types";

export declare type Options = {
event: FetchEvent;
dsn?: SentryOptions["dsn"];
event: FetchEvent;
environment?: SentryOptions["environment"];
release?: SentryOptions["release"];
beforeSend?: (event: Event) => Event;
pkg?: Record<string, any>;
allowedCookies?: string[] | RegExp;
allowedHeaders?: string[] | RegExp;
allowedCookies?: string[] | RegExp;
allowedSearchParams?: string[] | RegExp;
attachStacktrace?: SentryOptions["attachStacktrace"];
beforeSend?: (event: Event) => Event;
debug?: SentryOptions["debug"];
environment?: SentryOptions["environment"];
maxBreadcrumbs?: SentryOptions["maxBreadcrumbs"];
pkg?: Record<string, any>;
release?: SentryOptions["release"];
rewriteFrames?: RewriteFrames;
sampleRate?: SentryOptions["sampleRate"];
transportOptions?: Compute<Pick<NonNullable<SentryOptions["transportOptions"]>, "headers">>;
rewriteFrames?: RewriteFrames;
};

@@ -21,0 +24,0 @@ export declare type Level = "fatal" | "error" | "warning" | "info" | "debug";

{
"name": "toucan-js",
"version": "2.1.0",
"version": "2.2.0",
"description": "Cloudflare Workers client for Sentry",

@@ -24,2 +24,3 @@ "main": "dist/index.cjs.js",

"toucan",
"toucan-js",
"workers"

@@ -26,0 +27,0 @@ ],

@@ -15,5 +15,5 @@ <p align="center">

In Cloudflare Workers isolate model, it is inadvisable to [set or mutate global state within the event handler](https://developers.cloudflare.com/workers/about/how-it-works). The most of JavaScript SDKs use static methods that mutate global state with request metadata, breadcrumbs, tags... This is reasonable, because they were implemented for environments where concurrency does not inherently exist. However, using these SDKs in Workers leads to race conditions, such as logging breadcrumbs and request metadata of interleaved events.
In Cloudflare Workers isolate model, it is inadvisable to [set or mutate global state within the event handler](https://developers.cloudflare.com/workers/about/how-it-works). The most of JavaScript SDKs use static methods that mutate global state with request metadata, breadcrumbs, tags, and other extra properties. This is reasonable, because they were implemented for environments where concurrency does not inherently exist. However, using these SDKs in Workers leads to race conditions, such as logging breadrumbs, request data, and other metadata of interleaved events.
Toucan was created with Workers concurrent model in mind. Being a JavaScript class instantiated per-event, this kind of race-conditions do not exist, because all request metadata are scoped to a particular fetch event.
Toucan was created with Workers concurrent model in mind. It is a class that is instantiated per-event rather than globally, meaning this kind of race-conditions do not exist, because all request metadata are scoped to a particular fetch event.

@@ -77,18 +77,26 @@ ## Usage

## Options
## Minimal options
| Option | Type | Description |
| ------ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| event | \*FetchEvent | Workers fetch event. Toucan needs this to be able to call [waitUntil](https://developers.cloudflare.com/workers/about/tips/fetch-event-lifecycle/). |
| dsn | string | Sentry [Data Source Name](https://docs.sentry.io/error-reporting/quickstart/?platform=javascript#configure-the-sdk). If an empty DSN is passed, we treat it as valid option which signifies disabling the SDK. |
## Other options
| Option | Type | Description |
| ------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| event | \*FetchEvent | Workers fetch event. Toucan needs this to be able to call [waitUntil](https://developers.cloudflare.com/workers/about/tips/fetch-event-lifecycle/). |
| dsn | string | Sentry [Data Source Name](https://docs.sentry.io/error-reporting/quickstart/?platform=javascript#configure-the-sdk). If an empty DSN is passed, we treat it as valid option which signifies disabling the SDK. |
| environment | string | Your application's environment (production/staging/...). |
| release | string | Release tag. |
| pkg | object | Essentially your package.json. Toucan will use it to read project name, version, dependencies, and devDependencies. |
| allowedCookies | string[] \| RegExp | Array of allowed cookies, or a regular expression used to explicitly allow cookies of incoming request. If not provided, cookies will not be logged. |
| allowedHeaders | string[] \| RegExp | Array of allowed headers, or a regular expression used to explicitly allow headers of incoming request. If not provided, headers will not be logged. |
| allowedCookies | string[] \| RegExp | Array of allowed cookies, or a regular expression used to explicitly allow cookies of incoming request. If not provided, cookies will not be logged. |
| allowedSearchParams | string[] \| RegExp | Array of allowed search params, or a regular expression used to explicitly allow search params of incoming request. If not provided, search params will not be logged. |
| attachStacktrace | boolean | Attaches stacktraces to capture message. Default true. |
| beforeSend | (event: Event) => Event | This function is applied to all events before sending to Sentry. If provided, all allowlists are ignored. |
| debug | boolean | Turns debug mode on or off. If debug is enabled, toucan-js will attempt to print out useful debugging information. |
| environment | string | Your application's environment (production/staging/...). |
| maxBreadcrumbs | number | This variable controls the total amount of breadcrumbs that should be captured. This defaults to 100. |
| pkg | object | Essentially your package.json. Toucan will use it to read project name, version, dependencies, and devDependencies. |
| release | string | Release tag. |
| rewriteFrames | { root?: string, iteratee?: (frame: StackFrame) => StackFrame } | Allows you to apply a transformation to each frame of the stack trace. `root` path will be appended to the basename of the current frame's url. `iteratee` is a function that takes the frame, applies any transformation on it and returns it back. |
| sampleRate | number | Configures the sample rate as a percentage of events to be sent in the range of 0.0 to 1.0. The default is 1.0 which means that 100% of events are sent. If set to 0.1 only 10% of events will be sent. Events are picked randomly. |
| transportOptions | { headers?: Record<string, string> } | Custom headers to be passed to Sentry. |
| attachStacktrace | boolean | Attaches stacktraces to capture message. Default true. |
| rewriteFrames | { root?: string, iteratee?: (frame: StackFrame) => StackFrame } | Allows you to apply a transformation to each frame of the stack trace. `root` path will be appended to the basename of the current frame's url. `iteratee` is a function that takes the frame, applies any transformation on it and returns it back. |

@@ -95,0 +103,0 @@ ## Sensitive data

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc