2020-12-07 16:21:46 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
# Procesa los errores de JavaScript
|
|
|
|
class BacktraceJob < ApplicationJob
|
2021-03-19 22:41:03 +00:00
|
|
|
class BacktraceException < RuntimeError; end
|
|
|
|
|
2021-03-22 19:47:01 +00:00
|
|
|
EMPTY_SOURCEMAP = { 'mappings' => '' }.freeze
|
|
|
|
|
2024-04-10 14:00:18 +00:00
|
|
|
attr_reader :params
|
2020-12-07 16:21:46 +00:00
|
|
|
|
2024-04-10 14:00:18 +00:00
|
|
|
def perform(site:, params:)
|
2020-12-07 18:27:24 +00:00
|
|
|
@params = params
|
2020-12-07 16:21:46 +00:00
|
|
|
|
2021-03-20 16:43:31 +00:00
|
|
|
unless sources.empty?
|
2021-03-19 23:50:11 +00:00
|
|
|
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
|
|
|
|
|
2021-03-20 16:43:31 +00:00
|
|
|
data = data(backtrace['file'])
|
|
|
|
|
2021-03-19 23:50:11 +00:00
|
|
|
backtrace['file'] = mapping.source
|
|
|
|
backtrace['line'] = mapping.original.line
|
|
|
|
backtrace['column'] = mapping.original.column
|
2021-03-20 16:43:31 +00:00
|
|
|
|
|
|
|
# Encuentra el código fuente del error
|
|
|
|
source = data.dig('sourcesContent', data['sources']&.index(backtrace['file']))&.split("\n")
|
2021-04-27 14:25:37 +00:00
|
|
|
# XXX: Elimina la sangría aunque cambie las columnas porque
|
|
|
|
# eso lo vamos a ver en el archivo fuente directo.
|
|
|
|
backtrace['function'] = source[backtrace['line'] - 1].strip if source.present?
|
2021-03-19 23:50:11 +00:00
|
|
|
end
|
2020-12-07 16:21:46 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
2021-04-27 14:27:17 +00:00
|
|
|
raise BacktraceException, "#{origin}: #{message}"
|
2021-03-19 22:41:03 +00:00
|
|
|
rescue BacktraceException => e
|
2021-05-29 20:42:45 +00:00
|
|
|
ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params, javascript_backtrace: true })
|
2020-12-07 16:21:46 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2021-03-20 16:43:31 +00:00
|
|
|
# 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)
|
2021-03-22 19:47:01 +00:00
|
|
|
return EMPTY_SOURCEMAP unless map.start_with? 'https://'
|
2021-03-22 19:25:18 +00:00
|
|
|
|
2021-03-20 16:43:31 +00:00
|
|
|
map += '.map' unless map.end_with? '.map'
|
|
|
|
|
|
|
|
@data ||= {}
|
2021-03-22 19:47:01 +00:00
|
|
|
# TODO: Soportar ETags para la descarga, probablemente pasar a
|
|
|
|
# Faraday con caché para esto.
|
2021-03-20 16:43:31 +00:00
|
|
|
@data[map] ||= FastJsonparser.parse(Rails.cache.fetch(map, expires_in: 12.hours) do
|
|
|
|
Down.open(map).read
|
|
|
|
end, symbolize_keys: false)
|
2021-03-22 19:47:01 +00:00
|
|
|
rescue Down::Error, FastJsonparser::Error
|
|
|
|
EMPTY_SOURCEMAP
|
2020-12-07 16:21:46 +00:00
|
|
|
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.
|
2021-03-20 16:43:31 +00:00
|
|
|
#
|
|
|
|
# @return [SourceMap::Map]
|
2020-12-07 16:21:46 +00:00
|
|
|
def sourcemap
|
2021-03-22 19:47:01 +00:00
|
|
|
@sourcemap ||= sources.map do |map|
|
|
|
|
SourceMap::Map.from_hash data(map)
|
|
|
|
end.reduce(&:+)
|
2020-12-07 16:21:46 +00:00
|
|
|
end
|
2021-04-21 13:24:16 +00:00
|
|
|
|
|
|
|
# @return [String]
|
|
|
|
def origin
|
|
|
|
URI.parse(params.dig('context', 'url')).host
|
|
|
|
rescue URI::Error
|
|
|
|
params.dig('context', 'url')
|
|
|
|
end
|
2021-04-27 14:27:17 +00:00
|
|
|
|
|
|
|
# @return [String,Nil]
|
|
|
|
def message
|
|
|
|
@message ||= params['errors']&.first&.dig('message')
|
|
|
|
end
|
2020-12-07 16:21:46 +00:00
|
|
|
end
|