# frozen_string_literal: true # Procesa los errores de JavaScript class BacktraceJob < ApplicationJob class BacktraceException < RuntimeError; end queue_as :low_priority attr_reader :params, :site_id def perform(site_id:, params:) @site_id = site_id @params = params unless sources.empty? params['errors'].each do |error| error['backtrace'].each do |backtrace| offset = SourceMap::Offset.new(backtrace['line'], backtrace['column']) mapping = sourcemap.bsearch(offset) next unless mapping data = data(backtrace['file']) backtrace['file'] = mapping.source backtrace['line'] = mapping.original.line backtrace['column'] = mapping.original.column # Encuentra el código fuente del error source = data.dig('sourcesContent', data['sources']&.index(backtrace['file']))&.split("\n") backtrace['function'] = source.dig(backtrace['line'] - 1) if source.present? end end end begin raise BacktraceException, params['errors']&.first&.dig('message') rescue BacktraceException => e ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params, _backtrace: true }) end end private def site @site ||= Site.find_by_id(site_id) end # Obtiene todos los archivos del backtrace solo si los puede descargar # desde fuentes seguras. # # XXX: Idealmente no trabajamos con recursos externos, pero en este # momento no podemos saber todas las URLs de un sitio y quizás nunca # lo sabremos :B def sources @sources ||= params['errors'].map do |x| x['backtrace'] end.flatten.map do |x| x['file'].split('@').last end.uniq.select do |x| %r{\Ahttps://} =~ x end end # Descarga y devuelve los datos de un archivo # # @param [String] La URL del map # @return [Hash] def data(map) map += '.map' unless map.end_with? '.map' @data ||= {} @data[map] ||= FastJsonparser.parse(Rails.cache.fetch(map, expires_in: 12.hours) do Down.open(map).read end, symbolize_keys: false) end # Asume que todos los sourcemaps comparten la misma URL, lo # correcto sería buscarlo en sourceMappingURL al final de cada # archivo. # # Descarga los archivos y obtiene el backtrace original. # # @return [SourceMap::Map] def sourcemap @sourcemap ||= begin sources.map { |x| "#{x}.map" }.map do |map| SourceMap::Map.from_hash data(map) rescue Down::Error, FastJsonparser::Error SourceMap::Map.new end.reduce(&:+) end end end