Add Sentry instrumentation for JavaScript

This commit is contained in:
Sebastian Serth
2023-05-09 21:15:23 +02:00
parent aa05fcadf8
commit 240fbc5a3b
12 changed files with 166 additions and 59 deletions

View File

@ -45,27 +45,23 @@ $(document).on('turbolinks:load', function() {
// Initialize Sentry // Initialize Sentry
const sentrySettings = $('meta[name="sentry"]') const sentrySettings = $('meta[name="sentry"]')
if (sentrySettings.data()['enabled']) { if (sentrySettings.data()['enabled']) {
// Workaround for Turbolinks: We must not re-initialize the Relay object when visiting another page
window.SentryReplay ||= new Sentry.Replay();
Sentry.init({ Sentry.init({
dsn: sentrySettings.data('dsn'), dsn: sentrySettings.data('dsn'),
attachStacktrace: true, attachStacktrace: true,
release: sentrySettings.data('release'), release: sentrySettings.data('release'),
environment: sentrySettings.data('environment'), environment: sentrySettings.data('environment'),
autoSessionTracking: false, autoSessionTracking: true,
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.0, replaysSessionSampleRate: 0.0,
replaysOnErrorSampleRate: 1.0, replaysOnErrorSampleRate: 1.0,
integrations: [ integrations: window.SentryIntegrations,
SentryReplay, initialScope: scope =>{
], const user = $('meta[name="current-user"]').attr('content');
});
Sentry.configureScope(function (scope) { if (user) {
const user = $('meta[name="current-user"]').attr('content'); scope.setUser(JSON.parse(user));
}
if (user) { return scope;
scope.setUser(JSON.parse(user));
} }
}); });
} }

View File

@ -212,6 +212,16 @@ var CodeOceanEditor = {
$('button i.fa-spin').removeClass('d-inline-block').addClass('d-none'); $('button i.fa-spin').removeClass('d-inline-block').addClass('d-none');
}, },
startSentryTransaction: function (initiator) {
const cause = initiator.data('cause') || initiator.prop('id');
this.sentryTransaction = window.SentryUtils.startIdleTransaction(
Sentry.getCurrentHub(),
{ name: cause, op: "transaction" },
0, // Idle Timeout
window.SentryUtils.TRACING_DEFAULTS.finalTimeout,
true); // onContext
Sentry.getCurrentHub().configureScope(scope => scope.setSpan(this.sentryTransaction));
},
resizeAceEditors: function (own_solution = false) { resizeAceEditors: function (own_solution = false) {
let editorSelector; let editorSelector;

View File

@ -3,16 +3,19 @@ CodeOceanEditorEvaluation = {
// A list of non-printable characters that are not allowed in the code output. // A list of non-printable characters that are not allowed in the code output.
// Taken from https://stackoverflow.com/a/69024306 // Taken from https://stackoverflow.com/a/69024306
nonPrintableRegEx: /[\u0000-\u0008\u000B\u000C\u000F-\u001F\u007F-\u009F\u2000-\u200F\u2028-\u202F\u205F-\u206F\u3000\uFEFF]/g, nonPrintableRegEx: /[\u0000-\u0008\u000B\u000C\u000F-\u001F\u007F-\u009F\u2000-\u200F\u2028-\u202F\u205F-\u206F\u3000\uFEFF]/g,
sentryTransaction: null,
/** /**
* Scoring-Functions * Scoring-Functions
*/ */
scoreCode: function (event) { scoreCode: function (event) {
const cause = $('#assess');
this.startSentryTransaction(cause);
event.preventDefault(); event.preventDefault();
this.stopCode(event); this.stopCode(event);
this.clearScoringOutput(); this.clearScoringOutput();
$('#submit').addClass("d-none"); $('#submit').addClass("d-none");
this.createSubmission('#assess', null, function (response) { this.createSubmission(cause, null, function (response) {
this.showSpinner($('#assess')); this.showSpinner($('#assess'));
$('#score_div').removeClass('d-none'); $('#score_div').removeClass('d-none');
var url = response.score_url; var url = response.score_url;

View File

@ -1,7 +1,7 @@
CodeOceanEditorWebsocket = { CodeOceanEditorWebsocket = {
websocket: null, websocket: null,
createSocketUrl: function(url) { createSocketUrl: function(url, span) {
const sockURL = new URL(url, window.location); const sockURL = new URL(url, window.location);
// not needed any longer, we put it directly into the url: sockURL.pathname = url; // not needed any longer, we put it directly into the url: sockURL.pathname = url;
@ -11,17 +11,27 @@ CodeOceanEditorWebsocket = {
// strip anchor if it is in the url // strip anchor if it is in the url
sockURL.hash = ''; sockURL.hash = '';
sockURL.searchParams.set('HTTP_SENTRY_TRACE', span.toTraceparent());
const dynamicContext = this.sentryTransaction.getDynamicSamplingContext();
const baggage = SentryUtils.dynamicSamplingContextToSentryBaggageHeader(dynamicContext);
sockURL.searchParams.set('HTTP_BAGGAGE', baggage);
return sockURL.toString(); return sockURL.toString();
}, },
initializeSocket: function(url) { initializeSocket: function(url) {
this.websocket = new CommandSocket(this.createSocketUrl(url), const cleanedPath = url.replace(/\/\d+\//, '/*/').replace(/\/[^\/]+$/, '/*');
const websocketHost = window.location.origin.replace(/^http/, 'ws');
const sentryDescription = `WebSocket ${websocketHost}${cleanedPath}`;
const span = this.sentryTransaction.startChild({op: 'websocket.client', description: sentryDescription})
this.websocket = new CommandSocket(this.createSocketUrl(url, span),
function (evt) { function (evt) {
this.resetOutputTab(); this.resetOutputTab();
}.bind(this) }.bind(this)
); );
CodeOceanEditorWebsocket.websocket = this.websocket; CodeOceanEditorWebsocket.websocket = this.websocket;
this.websocket.onError(this.showWebsocketError.bind(this)); this.websocket.onError(this.showWebsocketError.bind(this));
this.websocket.onClose(span.finish.bind(span));
}, },
initializeSocketForTesting: function(url) { initializeSocketForTesting: function(url) {

View File

@ -112,6 +112,7 @@ CodeOceanEditorSubmissions = {
}, },
resetCode: function(initiator, onlyActiveFile = false) { resetCode: function(initiator, onlyActiveFile = false) {
this.startSentryTransaction(initiator);
this.showSpinner(initiator); this.showSpinner(initiator);
this.ajax({ this.ajax({
method: 'GET', method: 'GET',
@ -131,9 +132,11 @@ CodeOceanEditorSubmissions = {
}, },
renderCode: function(event) { renderCode: function(event) {
const cause = $('#render');
this.startSentryTransaction(cause);
event.preventDefault(); event.preventDefault();
if ($('#render').is(':visible')) { if ($('#render').is(':visible')) {
this.createSubmission('#render', null, function (response) { this.createSubmission(cause, null, function (response) {
if (response.render_url === undefined) return; if (response.render_url === undefined) return;
const active_file = CodeOceanEditor.active_file.filename.replace(/#$/,''); // remove # if it is the last character, this is not part of the filename and just an anchor const active_file = CodeOceanEditor.active_file.filename.replace(/#$/,''); // remove # if it is the last character, this is not part of the filename and just an anchor
@ -162,10 +165,12 @@ CodeOceanEditorSubmissions = {
* Execution-Logic * Execution-Logic
*/ */
runCode: function(event) { runCode: function(event) {
const cause = $('#run');
this.startSentryTransaction(cause);
event.preventDefault(); event.preventDefault();
this.stopCode(event); this.stopCode(event);
if ($('#run').is(':visible')) { if ($('#run').is(':visible')) {
this.createSubmission('#run', null, this.runSubmission.bind(this)); this.createSubmission(cause, null, this.runSubmission.bind(this));
} }
}, },
@ -189,9 +194,11 @@ CodeOceanEditorSubmissions = {
}, },
testCode: function(event) { testCode: function(event) {
const cause = $('#test');
this.startSentryTransaction(cause);
event.preventDefault(); event.preventDefault();
if ($('#test').is(':visible')) { if ($('#test').is(':visible')) {
this.createSubmission('#test', null, function(response) { this.createSubmission(cause, null, function(response) {
this.showSpinner($('#test')); this.showSpinner($('#test'));
$('#score_div').addClass('d-none'); $('#score_div').addClass('d-none');
var url = response.test_url.replace(this.FILENAME_URL_PLACEHOLDER, CodeOceanEditor.active_file.filename.replace(/#$/,'')); // remove # if it is the last character, this is not part of the filename and just an anchor var url = response.test_url.replace(this.FILENAME_URL_PLACEHOLDER, CodeOceanEditor.active_file.filename.replace(/#$/,'')); // remove # if it is the last character, this is not part of the filename and just an anchor
@ -202,6 +209,7 @@ CodeOceanEditorSubmissions = {
submitCode: function(event) { submitCode: function(event) {
const button = $(event.target) || $('#submit'); const button = $(event.target) || $('#submit');
this.startSentryTransaction(button);
this.teardownEventHandlers(); this.teardownEventHandlers();
this.createSubmission(button, null, function (response) { this.createSubmission(button, null, function (response) {
if (response.redirect) { if (response.redirect) {

View File

@ -14,6 +14,10 @@ CommandSocket.prototype.onError = function(callback){
this.websocket.onerror = callback this.websocket.onerror = callback
}; };
CommandSocket.prototype.onClose = function(callback){
this.websocket.onclose = callback
};
/** /**
* Allows it to register an event-handler on the given cmd. * Allows it to register an event-handler on the given cmd.
* The handler needs to accept one argument, the message. * The handler needs to accept one argument, the message.

View File

@ -16,11 +16,22 @@ import 'jstree';
import * as _ from 'underscore'; import * as _ from 'underscore';
import * as d3 from 'd3'; import * as d3 from 'd3';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import * as SentryIntegration from '@sentry/integrations';
import { startIdleTransaction, TRACING_DEFAULTS } from '@sentry/core';
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
import 'sorttable'; import 'sorttable';
window.bootstrap = bootstrap; // Publish bootstrap in global namespace window.bootstrap = bootstrap; // Publish bootstrap in global namespace
window._ = _; // Publish underscore's `_` in global namespace window._ = _; // Publish underscore's `_` in global namespace
window.d3 = d3; // Publish d3 in global namespace window.d3 = d3; // Publish d3 in global namespace
window.Sentry = Sentry; // Publish sentry in global namespace window.Sentry = Sentry; // Publish sentry in global namespace
window.SentryIntegrations = [ // Publish sentry integration in global namespace
new SentryIntegration.ReportingObserver(),
new SentryIntegration.ExtraErrorData(),
new SentryIntegration.HttpClient(),
new Sentry.BrowserTracing(),
new Sentry.Replay(),
];
window.SentryUtils = { dynamicSamplingContextToSentryBaggageHeader, startIdleTransaction, TRACING_DEFAULTS };
// CSS // CSS
import 'chosen-js/chosen.css'; import 'chosen-js/chosen.css';

View File

@ -9,6 +9,7 @@ require 'rails/all'
Bundler.require(*Rails.groups) Bundler.require(*Rails.groups)
require 'telegraf/rails' require 'telegraf/rails'
require_relative '../lib/middleware/websocket_sentry_headers'
module CodeOcean module CodeOcean
class Application < Rails::Application class Application < Rails::Application
@ -56,5 +57,8 @@ module CodeOcean
# Allow tables in addition to existing default tags # Allow tables in addition to existing default tags
config.action_view.sanitized_allowed_tags = ActionView::Base.sanitized_allowed_tags + %w[table thead tbody tfoot td tr] config.action_view.sanitized_allowed_tags = ActionView::Base.sanitized_allowed_tags + %w[table thead tbody tfoot td tr]
# Extract Sentry-related parameters from WebSocket connection
config.middleware.insert_before 0, Middleware::WebSocketSentryHeaders
end end
end end

View File

@ -14,4 +14,5 @@
ActiveSupport::Inflector.inflections(:en) do |inflect| ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'IO' inflect.acronym 'IO'
inflect.acronym 'WebSocket'
end end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Middleware
class WebSocketSentryHeaders
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
extract_sentry_parameters(request) if websocket_upgrade?(request)
@app.call(env)
end
private
def websocket_upgrade?(request)
request.get_header('HTTP_CONNECTION')&.casecmp?('Upgrade') &&
request.get_header('HTTP_UPGRADE')&.casecmp?('websocket')
end
def extract_sentry_parameters(request)
%w[HTTP_SENTRY_TRACE HTTP_BAGGAGE].each do |param|
request.add_header(param, request.delete_param(param))
end
end
end
end

View File

@ -9,7 +9,10 @@
"@egjs/hammerjs": "^2.0.17", "@egjs/hammerjs": "^2.0.17",
"@fortawesome/fontawesome-free": "^6.4.0", "@fortawesome/fontawesome-free": "^6.4.0",
"@popperjs/core": "^2.11.7", "@popperjs/core": "^2.11.7",
"@sentry/browser": "^7.11.1", "@sentry/browser": "^7.51.2",
"@sentry/core": "^7.51.2",
"@sentry/integrations": "^7.51.2",
"@sentry/utils": "^7.51.2",
"@webpack-cli/serve": "^2.0.3", "@webpack-cli/serve": "^2.0.3",
"babel-loader": "^9.1.2", "babel-loader": "^9.1.2",
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",

107
yarn.lock
View File

@ -1026,57 +1026,67 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7"
integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw== integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==
"@sentry-internal/tracing@7.47.0": "@sentry-internal/tracing@7.51.2":
version "7.47.0" version "7.51.2"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.47.0.tgz#45e92eb4c8d049d93bd4fab961eaa38a4fb680f3" resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.51.2.tgz#17833047646426ca71445327018ffcb33506a699"
integrity sha512-udpHnCzF8DQsWf0gQwd0XFGp6Y8MOiwnl8vGt2ohqZGS3m1+IxoRLXsSkD8qmvN6KKDnwbaAvYnK0z0L+AW95g== integrity sha512-OBNZn7C4CyocmlSMUPfkY9ORgab346vTHu5kX35PgW5XR51VD2nO5iJCFbyFcsmmRWyCJcZzwMNARouc2V4V8A==
dependencies: dependencies:
"@sentry/core" "7.47.0" "@sentry/core" "7.51.2"
"@sentry/types" "7.47.0" "@sentry/types" "7.51.2"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.51.2"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/browser@^7.11.1": "@sentry/browser@^7.51.2":
version "7.47.0" version "7.51.2"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.47.0.tgz#c0d10f348d1fb9336c3ef8fa2f6638f26d4c17a8" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.51.2.tgz#c01758a54c613be45df58ab503805737256f51a4"
integrity sha512-L0t07kS/G1UGVZ9fpD6HLuaX8vVBqAGWgu+1uweXthYozu/N7ZAsakjU/Ozu6FSXj1mO3NOJZhOn/goIZLSj5A== integrity sha512-FQFEaTFbvYHPQE2emFjNoGSy+jXplwzoM/XEUBRjrGo62lf8BhMvWnPeG3H3UWPgrWA1mq0amvHRwXUkwofk0g==
dependencies: dependencies:
"@sentry-internal/tracing" "7.47.0" "@sentry-internal/tracing" "7.51.2"
"@sentry/core" "7.47.0" "@sentry/core" "7.51.2"
"@sentry/replay" "7.47.0" "@sentry/replay" "7.51.2"
"@sentry/types" "7.47.0" "@sentry/types" "7.51.2"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.51.2"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/core@7.47.0": "@sentry/core@7.51.2", "@sentry/core@^7.51.2":
version "7.47.0" version "7.51.2"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.47.0.tgz#6a723d96f64009a9c1b9bc44e259956b7eca0a3f" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.51.2.tgz#f2c938de334f9bf26f4416079168275832423964"
integrity sha512-EFhZhKdMu7wKmWYZwbgTi8FNZ7Fq+HdlXiZWNz51Bqe3pHmfAkdHtAEs0Buo0v623MKA0CA4EjXIazGUM34XTg== integrity sha512-p8ZiSBxpKe+rkXDMEcgmdoyIHM/1bhpINLZUFPiFH8vzomEr7sgnwRhyrU8y/ADnkPeNg/2YF3QpDpk0OgZJUA==
dependencies: dependencies:
"@sentry/types" "7.47.0" "@sentry/types" "7.51.2"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.51.2"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/replay@7.47.0": "@sentry/integrations@^7.51.2":
version "7.47.0" version "7.51.2"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.47.0.tgz#d2fc8fd3be2360950497426035d1ba0bd8a97b8f" resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.51.2.tgz#fce58b9ced601c7f93344b508c67c69a9c883f3d"
integrity sha512-BFpVZVmwlezZ83y0L43TCTJY142Fxh+z+qZSwTag5HlhmIpBKw/WKg06ajOhrYJbCBkhHmeOvyKkxX0jnc39ZA== integrity sha512-ZnSptbuDQOoQ13mFX9vvLDfXlbMGjenW2fMIssi9+08B7fD6qxmetkYnWmBK+oEipjoGA//0240Fj8FUvZr0Qg==
dependencies: dependencies:
"@sentry/core" "7.47.0" "@sentry/types" "7.51.2"
"@sentry/types" "7.47.0" "@sentry/utils" "7.51.2"
"@sentry/utils" "7.47.0" localforage "^1.8.1"
tslib "^1.9.3"
"@sentry/types@7.47.0": "@sentry/replay@7.51.2":
version "7.47.0" version "7.51.2"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.47.0.tgz#fd07dbec11a26ae861532a9abe75bd31663ca09b" resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.51.2.tgz#1f54e92b472ab87dfdb4e8cd6b8c8252600fe7b0"
integrity sha512-GxXocplN0j1+uczovHrfkykl9wvkamDtWxlPUQgyGlbLGZn+UH1Y79D4D58COaFWGEZdSNKr62gZAjfEYu9nQA== integrity sha512-W8YnSxkK9LTUXDaYciM7Hn87u57AX9qvH8jGcxZZnvpKqHlDXOpSV8LRtBkARsTwgLgswROImSifY0ic0lyCWg==
"@sentry/utils@7.47.0":
version "7.47.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.47.0.tgz#e62fdede15e45387b40c9fa135feba48f0960826"
integrity sha512-A89SaOLp6XeZfByeYo2C8Ecye/YAtk/gENuyOUhQEdMulI6mZdjqtHAp7pTMVgkBc/YNARVuoa+kR/IdRrTPkQ==
dependencies: dependencies:
"@sentry/types" "7.47.0" "@sentry/core" "7.51.2"
"@sentry/types" "7.51.2"
"@sentry/utils" "7.51.2"
"@sentry/types@7.51.2":
version "7.51.2"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.51.2.tgz#cb742f374d9549195f62c462c915adeafed31d65"
integrity sha512-/hLnZVrcK7G5BQoD/60u9Qak8c9AvwV8za8TtYPJDUeW59GrqnqOkFji7RVhI7oH1OX4iBxV+9pAKzfYE6A6SA==
"@sentry/utils@7.51.2", "@sentry/utils@^7.51.2":
version "7.51.2"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.51.2.tgz#2a52ac2cfb00ffd128248981279c0a561b39eccb"
integrity sha512-EcjBU7qG4IG+DpIPvdgIBcdIofROMawKoRUNKraeKzH/waEYH9DzCaqp/mzc5/rPBhpDB4BShX9xDDSeH+8c0A==
dependencies:
"@sentry/types" "7.51.2"
tslib "^1.9.3" tslib "^1.9.3"
"@sinclair/typebox@^0.25.16": "@sinclair/typebox@^0.25.16":
@ -2793,6 +2803,11 @@ icss-utils@^5.0.0, icss-utils@^5.1.0:
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
immutable@^4.0.0: immutable@^4.0.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be"
@ -3039,6 +3054,13 @@ launch-editor@^2.6.0:
picocolors "^1.0.0" picocolors "^1.0.0"
shell-quote "^1.7.3" shell-quote "^1.7.3"
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==
dependencies:
immediate "~3.0.5"
lilconfig@^2.1.0: lilconfig@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
@ -3058,6 +3080,13 @@ loader-utils@^2.0.0:
emojis-list "^3.0.0" emojis-list "^3.0.0"
json5 "^2.1.2" json5 "^2.1.2"
localforage@^1.8.1:
version "1.10.0"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4"
integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==
dependencies:
lie "3.1.1"
locate-path@^5.0.0: locate-path@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"