squash_javascript 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE.txt +202 -0
- data/README.md +282 -0
- data/bin/upload_source_map +75 -0
- data/lib/squash/javascript/engine.rb +20 -0
- data/lib/squash/javascript/source_map.rb +181 -0
- data/lib/squash/javascript.rb +26 -0
- data/vendor/assets/javascripts/squash_javascript/client.coffee +219 -0
- data/vendor/assets/javascripts/squash_javascript/index.js +16 -0
- data/vendor/assets/javascripts/squash_javascript/tracekit.js +1132 -0
- data/vendor/assets/javascripts/squash_javascript.min.js +24 -0
- data/vendor/assets/javascripts/squash_javascript.orig.js +260 -0
- metadata +175 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Copyright 2012 Square Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
require 'json'
|
|
16
|
+
|
|
17
|
+
# An in-memory representation of a JavaScript source map.
|
|
18
|
+
|
|
19
|
+
class Squash::Javascript::SourceMap
|
|
20
|
+
# @private
|
|
21
|
+
VLQ_CONTINUATION_BIT = 1 << 5
|
|
22
|
+
|
|
23
|
+
# @return [Array<Squash::Javascript::SourceMap::Mapping>] The individual
|
|
24
|
+
# mappings in this source map.
|
|
25
|
+
attr_reader :mappings
|
|
26
|
+
|
|
27
|
+
# @private
|
|
28
|
+
def initialize(entries=[])
|
|
29
|
+
@mappings = entries
|
|
30
|
+
sort!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Parses a JSON version-3 source map file and generates a SourceMap from it.
|
|
34
|
+
#
|
|
35
|
+
# @param [String] source_file A path to a source map file.
|
|
36
|
+
# @param [String] route The URL where the compiled JavaScript asset is
|
|
37
|
+
# accessed.
|
|
38
|
+
# @param [Hash] options Additional options.
|
|
39
|
+
# @option options [String] :root If set, overrides the project root specified
|
|
40
|
+
# in the `sourceRoot` field with this root. Absolute paths will be converted
|
|
41
|
+
# into project-local paths (suitable for Git-blaming) using this root.
|
|
42
|
+
|
|
43
|
+
def self.from_sourcemap(source_file, route, options={})
|
|
44
|
+
fields = JSON.parse(File.read(source_file))
|
|
45
|
+
raise "Only version 3 source maps are supported" unless fields['version'] == 3
|
|
46
|
+
|
|
47
|
+
project_root = options[:root] || fields['sourceRoot'] or raise("Must specify a project root in the source map or method options")
|
|
48
|
+
project_root << '/' unless project_root[-2..-1] == '/'
|
|
49
|
+
|
|
50
|
+
route = route
|
|
51
|
+
sources = fields['sources'].map { |source| source.sub(/^#{Regexp.escape project_root}/, '') }
|
|
52
|
+
names = fields['names']
|
|
53
|
+
|
|
54
|
+
mappings = fields['mappings'].split(';').map { |group| group.split ',' }
|
|
55
|
+
entries = Array.new
|
|
56
|
+
|
|
57
|
+
source_file_counter = 0
|
|
58
|
+
source_line_counter = 0
|
|
59
|
+
source_column_counter = 0
|
|
60
|
+
symbol_counter = 0
|
|
61
|
+
mappings.each_with_index do |group, compiled_line_number|
|
|
62
|
+
compiled_column_counter = 0
|
|
63
|
+
group.each do |segment|
|
|
64
|
+
values = decode64vlq(segment)
|
|
65
|
+
compiled_column_counter += values[0]
|
|
66
|
+
source_file_counter += values[1] if values[1]
|
|
67
|
+
source_line_counter += values[2] if values[2]
|
|
68
|
+
source_column_counter += values[3] if values[3]
|
|
69
|
+
symbol_counter += values[4] if values[4]
|
|
70
|
+
entries << Mapping.new(
|
|
71
|
+
route, compiled_line_number, compiled_column_counter,
|
|
72
|
+
sources[source_file_counter], source_line_counter, source_column_counter,
|
|
73
|
+
names[symbol_counter]
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
return new(entries)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @private
|
|
82
|
+
def <<(obj)
|
|
83
|
+
@mappings << obj
|
|
84
|
+
sort!
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Given the URL of a minified JavaScript asset, and a line and column number,
|
|
88
|
+
# attempts to locate the original source file and line number.
|
|
89
|
+
#
|
|
90
|
+
# @param [String] route The URL of the JavaScript file.
|
|
91
|
+
# @param [Fixnum] line The line number.
|
|
92
|
+
# @param [Fixnum] column The character number within the line.
|
|
93
|
+
|
|
94
|
+
def resolve(route, line, column)
|
|
95
|
+
index = 0
|
|
96
|
+
mapping = nil
|
|
97
|
+
while index < mappings.length
|
|
98
|
+
entry = mappings[index]
|
|
99
|
+
mapping = entry if entry.route == route && entry.compiled_line == line && entry.compiled_column <= column
|
|
100
|
+
break if entry.route > route || entry.compiled_line > line
|
|
101
|
+
index += 1
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
return mapping
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def sort!
|
|
110
|
+
@mappings.sort_by! { |m| [m.route, m.compiled_line, m.compiled_column] }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.decode64vlq(string)
|
|
114
|
+
bytes = string.bytes.to_a
|
|
115
|
+
byte_index = 0
|
|
116
|
+
result = []
|
|
117
|
+
|
|
118
|
+
begin
|
|
119
|
+
raise "Base-64 VLQ-encoded string unexpectedly terminated" if byte_index >= string.bytesize
|
|
120
|
+
|
|
121
|
+
number = 0
|
|
122
|
+
continuation = false
|
|
123
|
+
shift = 0
|
|
124
|
+
begin
|
|
125
|
+
# each character corresponds to a 6-bit word
|
|
126
|
+
digit = decode64(bytes[byte_index])
|
|
127
|
+
byte_index += 1
|
|
128
|
+
# the most significant bit is the continuation bit
|
|
129
|
+
continuation = (digit & VLQ_CONTINUATION_BIT) != 0
|
|
130
|
+
# the remaining bits are the number
|
|
131
|
+
digit = digit & (VLQ_CONTINUATION_BIT - 1)
|
|
132
|
+
# continuations are little-endian
|
|
133
|
+
number += (digit << shift)
|
|
134
|
+
shift += 5
|
|
135
|
+
end while continuation
|
|
136
|
+
|
|
137
|
+
# the LSB is the sign bit
|
|
138
|
+
number = (number & 1 > 0) ? -(number >> 1) : (number >> 1)
|
|
139
|
+
result << number
|
|
140
|
+
end while byte_index < string.bytesize
|
|
141
|
+
|
|
142
|
+
return result
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def self.decode64(byte)
|
|
146
|
+
case byte
|
|
147
|
+
when 65..90 then byte - 65 # A..Z => 0..25
|
|
148
|
+
when 97..122 then byte - 71 # a..z => 26..51
|
|
149
|
+
when 48..57 then byte + 4 # 0..9 => 52..61
|
|
150
|
+
when 43 then 62 # + => 62
|
|
151
|
+
when 47 then 63 # / => 63
|
|
152
|
+
else raise "Invalid byte #{byte} in base-64 VLQ string"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
class Mapping
|
|
157
|
+
attr_accessor :route, :compiled_line, :compiled_column, :source_file, :source_line, :source_column, :symbol
|
|
158
|
+
|
|
159
|
+
def self.from_json(obj)
|
|
160
|
+
new *obj
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def initialize(route, compiled_line, compiled_column, source_file, source_line, source_column, symbol)
|
|
164
|
+
@route = route
|
|
165
|
+
@compiled_line = compiled_line
|
|
166
|
+
@compiled_column = compiled_column
|
|
167
|
+
@source_file = source_file
|
|
168
|
+
@source_line = source_line
|
|
169
|
+
@source_column = source_column
|
|
170
|
+
@symbol = symbol
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def as_json(options=nil)
|
|
174
|
+
[route, compiled_line, compiled_column, source_file, source_line, source_column, symbol]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def to_json(options=nil)
|
|
178
|
+
as_json.to_json
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Copyright 2012 Square Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# Container module for classes relating to Squash.
|
|
16
|
+
|
|
17
|
+
module Squash
|
|
18
|
+
|
|
19
|
+
# Container module for classes relating to the JavaScript client library.
|
|
20
|
+
|
|
21
|
+
module Javascript
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
require 'squash/javascript/source_map'
|
|
26
|
+
require 'squash/javascript/engine'
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Copyright 2012 Square Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
root = exports ? this
|
|
16
|
+
|
|
17
|
+
# Singleton class for the client library. See the README file for usage
|
|
18
|
+
# examples. The singleton instance is accessed using the {.instance} method.
|
|
19
|
+
#
|
|
20
|
+
class root.SquashJavascript
|
|
21
|
+
_instance = undefined
|
|
22
|
+
|
|
23
|
+
# @return [_SquashJavascript] The singleton instance.
|
|
24
|
+
#
|
|
25
|
+
@instance: -> _instance ?= new _SquashJavascript()
|
|
26
|
+
|
|
27
|
+
# See {root.SquashJavascript}.
|
|
28
|
+
#
|
|
29
|
+
class _SquashJavascript
|
|
30
|
+
# @private
|
|
31
|
+
constructor: ->
|
|
32
|
+
TraceKit.report.subscribe (error) -> SquashJavascript.instance().report(error)
|
|
33
|
+
|
|
34
|
+
# Sets configuration options. See the README file for a list of accepted
|
|
35
|
+
# configuration options. Multiple calls will merge new options in.
|
|
36
|
+
#
|
|
37
|
+
# @param [Object] options New options to apply.
|
|
38
|
+
#
|
|
39
|
+
configure: (options) ->
|
|
40
|
+
@options ||= {
|
|
41
|
+
disabled: false
|
|
42
|
+
notifyPath: '/api/1.0/notify'
|
|
43
|
+
transmitTimeout: 15000
|
|
44
|
+
ignoredExceptionClasses: []
|
|
45
|
+
ignoredExceptionMessages: {}
|
|
46
|
+
}
|
|
47
|
+
for own key, value of options
|
|
48
|
+
@options[key] = value
|
|
49
|
+
|
|
50
|
+
# Notify Squash of an error. The client must be configured first. The error
|
|
51
|
+
# must have a valid stack trace (i.e., it must have been thrown). Does not
|
|
52
|
+
# re-throw the error; that's your responsibility.
|
|
53
|
+
#
|
|
54
|
+
# You shouldn't normally need to call this method. Squash automatically
|
|
55
|
+
# installs a listener that notifies the server for thrown exceptions.
|
|
56
|
+
#
|
|
57
|
+
# @param [Error] error The error to record.
|
|
58
|
+
#
|
|
59
|
+
notify: (error) ->
|
|
60
|
+
if error instanceof Error
|
|
61
|
+
TraceKit.report(error)
|
|
62
|
+
else
|
|
63
|
+
throw error
|
|
64
|
+
|
|
65
|
+
# @private
|
|
66
|
+
report: (error) ->
|
|
67
|
+
try
|
|
68
|
+
return false if @options?.disabled
|
|
69
|
+
if !@options?.APIKey || !@options?.environment ||
|
|
70
|
+
!@options?.revision || !@options?.APIHost
|
|
71
|
+
console.error "Missing required Squash configuration keys"
|
|
72
|
+
return false
|
|
73
|
+
|
|
74
|
+
return false if this.shouldIgnoreError(error)
|
|
75
|
+
return false unless error.stack
|
|
76
|
+
|
|
77
|
+
fields = arguments[1] || new Object()
|
|
78
|
+
fields.api_key = @options.APIKey
|
|
79
|
+
fields.environment = @options.environment
|
|
80
|
+
fields.client = "javascript"
|
|
81
|
+
fields.revision = @options.revision
|
|
82
|
+
|
|
83
|
+
fields.class_name = error.type || error.name
|
|
84
|
+
# errors that make it up to window.onerror get stupidly rewritten: their
|
|
85
|
+
# class is set to "Error" and the ACTUAL class is integrated into the
|
|
86
|
+
# message (e.g., "Uncaught TypeError: [message]")
|
|
87
|
+
if !error.name && (matches = error.message.match(/^(Uncaught )?(\w+): (.+)/))
|
|
88
|
+
fields.class_name = matches[2]
|
|
89
|
+
fields.message = matches[3]
|
|
90
|
+
else
|
|
91
|
+
fields.message = error.message
|
|
92
|
+
fields.class_name ?= 'Error' # when all else fails
|
|
93
|
+
|
|
94
|
+
fields.backtraces = buildBacktrace(error.stack)
|
|
95
|
+
fields.capture_method = error.mode
|
|
96
|
+
fields.occurred_at = ISODateString(new Date())
|
|
97
|
+
|
|
98
|
+
fields.schema = window.location.protocol.replace(/:$/, '')
|
|
99
|
+
fields.host = window.location.hostname
|
|
100
|
+
fields.port = window.location.port if window.location.port.length > 0
|
|
101
|
+
fields.path = window.location.pathname
|
|
102
|
+
fields.query = window.location.search
|
|
103
|
+
fields.fragment = window.location.hash if window.location.hash != ''
|
|
104
|
+
|
|
105
|
+
fields.user_agent = navigator.userAgent
|
|
106
|
+
|
|
107
|
+
fields.screen_width = screen.width
|
|
108
|
+
fields.screen_height = screen.height
|
|
109
|
+
fields.window_width = window.innerWidth
|
|
110
|
+
fields.window_height = window.innerHeight
|
|
111
|
+
fields.color_depth = screen.colorDepth
|
|
112
|
+
|
|
113
|
+
body = JSON.stringify(fields)
|
|
114
|
+
this.HTTPTransmit (@options.APIHost + @options.notifyPath),
|
|
115
|
+
[ ['Content-Type', 'application/json'] ],
|
|
116
|
+
body
|
|
117
|
+
|
|
118
|
+
return true
|
|
119
|
+
catch internal_error
|
|
120
|
+
console.error "Error while trying to notify Squash:", internal_error.stack
|
|
121
|
+
console.error "-- original error:", error
|
|
122
|
+
|
|
123
|
+
# Runs the given `block`. If an exception is thrown within the function, adds
|
|
124
|
+
# the given user data to the exception and re-throws it.
|
|
125
|
+
#
|
|
126
|
+
# @param [Object] data The user data to add to any error.
|
|
127
|
+
# @param [function] block The code to run.
|
|
128
|
+
# @return [Object] The return value of `block`.
|
|
129
|
+
# @see #addingUserData
|
|
130
|
+
#
|
|
131
|
+
addUserData: (data, block) ->
|
|
132
|
+
try
|
|
133
|
+
block()
|
|
134
|
+
catch err
|
|
135
|
+
err._squash_user_data ?= {}
|
|
136
|
+
mergeBang err._squash_user_data, data
|
|
137
|
+
throw err
|
|
138
|
+
|
|
139
|
+
# Wraps `block` in a call to {#addUserData} and returns it. Any arguments are
|
|
140
|
+
# passed through.
|
|
141
|
+
#
|
|
142
|
+
# @see #addUserData
|
|
143
|
+
#
|
|
144
|
+
addingUserData: (data, block) ->
|
|
145
|
+
(args...) -> SquashJavascript.instance().addUserData(data, -> block(args...))
|
|
146
|
+
|
|
147
|
+
# Runs the given `block`. If an exception is thrown within the function of one
|
|
148
|
+
# of the types given, does not report the exception to Squash. Re-throws _all_
|
|
149
|
+
# exceptions.
|
|
150
|
+
#
|
|
151
|
+
# @param [class..., String...] exceptions A list of error types to ignore.
|
|
152
|
+
# @param [function] block The code to run.
|
|
153
|
+
# @return [Object] The return value of `block`.
|
|
154
|
+
# @see #ignoringExceptions
|
|
155
|
+
#
|
|
156
|
+
ignoreExceptions: (exceptions..., block) ->
|
|
157
|
+
try
|
|
158
|
+
block()
|
|
159
|
+
catch err
|
|
160
|
+
err._squash_ignored_exceptions = (err._squash_ignored_exceptions || []).concat(exceptions)
|
|
161
|
+
throw err
|
|
162
|
+
|
|
163
|
+
# Wraps `block` in a call to {#ignoreExceptions} and returns it. Any arguments
|
|
164
|
+
# are passed through.
|
|
165
|
+
#
|
|
166
|
+
# @see #ignoreExceptions
|
|
167
|
+
#
|
|
168
|
+
ignoringExceptions: (exceptions..., block) ->
|
|
169
|
+
(args...) -> SquashJavascript.instance().ignoreExceptions(exceptions..., -> block(args...))
|
|
170
|
+
|
|
171
|
+
################################## PRIVATES ##################################
|
|
172
|
+
|
|
173
|
+
# @private
|
|
174
|
+
HTTPTransmit: (url, headers, body) ->
|
|
175
|
+
request = new XMLHttpRequest()
|
|
176
|
+
request.timeout = @options.transmitTimeout
|
|
177
|
+
request.open "POST", url, true
|
|
178
|
+
for header in headers
|
|
179
|
+
request.setRequestHeader header[0], header[1]
|
|
180
|
+
request.send body
|
|
181
|
+
request
|
|
182
|
+
|
|
183
|
+
# @private
|
|
184
|
+
shouldIgnoreError: (error) ->
|
|
185
|
+
ignored_classes = @options.ignoredExceptionClasses.concat(error._squash_ignored_exceptions || [])
|
|
186
|
+
|
|
187
|
+
return true if any(ignored_classes, (klass) -> error.name == klass)
|
|
188
|
+
|
|
189
|
+
return any(@options.ignoredExceptionMessages, (class_name, messages) ->
|
|
190
|
+
if error.name == class_name
|
|
191
|
+
return any(messages, (message) -> error.message.match(message))
|
|
192
|
+
else
|
|
193
|
+
return false
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
buildBacktrace = (stack) ->
|
|
197
|
+
backtraces = []
|
|
198
|
+
for line in stack
|
|
199
|
+
context = line.context
|
|
200
|
+
context = null if context && any(context, (cline) -> cline.length > 200)
|
|
201
|
+
backtraces.push ['_JS_ASSET_', line.url, line.line, line.column, line.func, context]
|
|
202
|
+
return [ ["Active Thread", true, backtraces] ]
|
|
203
|
+
|
|
204
|
+
ISODateString = (d) ->
|
|
205
|
+
pad = (n) -> if n < 10 then '0' + n else n
|
|
206
|
+
"#{d.getUTCFullYear()}-#{pad(d.getUTCMonth() + 1)}-#{pad d.getUTCDate()}T#{pad d.getUTCHours()}:#{pad d.getUTCMinutes()}:#{pad d.getUTCSeconds()}Z"
|
|
207
|
+
|
|
208
|
+
any = (obj, condition) ->
|
|
209
|
+
if obj instanceof Array
|
|
210
|
+
for element in obj
|
|
211
|
+
return true if condition(element)
|
|
212
|
+
else
|
|
213
|
+
for own key, value of obj
|
|
214
|
+
return true if condition(key, value)
|
|
215
|
+
return false
|
|
216
|
+
|
|
217
|
+
mergeBang = (modified, constant) ->
|
|
218
|
+
for own key, value of constant
|
|
219
|
+
modified[key] = value
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Copyright 2012 Square Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
//= require ./tracekit
|
|
16
|
+
//= require ./client
|