bowser 0.4.3 → 0.5.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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -9
- data/CHANGELOG.md +7 -0
- data/lib/bowser/version.rb +1 -1
- data/opal/bowser.rb +0 -3
- data/opal/bowser/delegate_native.rb +9 -1
- data/opal/bowser/document.rb +8 -0
- data/opal/bowser/element.rb +57 -4
- data/opal/bowser/event.rb +30 -0
- data/opal/bowser/event_target.rb +10 -0
- data/opal/bowser/file_list.rb +25 -0
- data/opal/bowser/http.rb +30 -0
- data/opal/bowser/http/form_data.rb +4 -0
- data/opal/bowser/http/request.rb +19 -0
- data/opal/bowser/http/response.rb +8 -0
- data/opal/bowser/indexed_db.rb +244 -0
- data/opal/bowser/service_worker.rb +43 -0
- data/opal/bowser/service_worker/cache_storage.rb +49 -0
- data/opal/bowser/service_worker/clients.rb +19 -0
- data/opal/bowser/service_worker/context.rb +71 -0
- data/opal/bowser/service_worker/extendable_event.rb +19 -0
- data/opal/bowser/service_worker/fetch_event.rb +28 -0
- data/opal/bowser/service_worker/promise.rb +101 -0
- data/opal/bowser/service_worker/request.rb +33 -0
- data/opal/bowser/service_worker/response.rb +41 -0
- data/opal/bowser/websocket.rb +18 -0
- data/opal/bowser/window.rb +28 -0
- metadata +13 -3
data/opal/bowser/http.rb
CHANGED
@@ -8,6 +8,13 @@ module Bowser
|
|
8
8
|
module HTTP
|
9
9
|
module_function
|
10
10
|
|
11
|
+
# Send a request to the given URL
|
12
|
+
#
|
13
|
+
# @param url [String] the URL to send the request to
|
14
|
+
# @keywordparam method [String] the HTTP method, defaults to GET
|
15
|
+
# @keywordparam headers [Hash] the HTTP headers to send with the request
|
16
|
+
# @keywordparam data [Hash, String, nil] the data to send with the request,
|
17
|
+
# only useful for POST, PATCH, or PUT requests
|
11
18
|
def fetch(url, method: :get, headers: {}, data: nil, &block)
|
12
19
|
promise = Promise.new
|
13
20
|
request = Request.new(method, url)
|
@@ -20,6 +27,13 @@ module Bowser
|
|
20
27
|
promise
|
21
28
|
end
|
22
29
|
|
30
|
+
# Shorthand method for sending a request with JSON data
|
31
|
+
#
|
32
|
+
# @param url [String] the URL to send the request to
|
33
|
+
# @param data [Hash, String, nil] the data to send with the request,
|
34
|
+
# only useful for POST, PATCH, or PUT requests. Otherwise, use `fetch`.
|
35
|
+
# @keywordparam method [String] the HTTP method, defaults to GET
|
36
|
+
# @keywordparam content_type [String] the MIME type of the request
|
23
37
|
def upload(url, data, content_type: 'application/json', method: :post, &block)
|
24
38
|
promise = Promise.new
|
25
39
|
request = Request.new(method, url)
|
@@ -32,6 +46,15 @@ module Bowser
|
|
32
46
|
promise
|
33
47
|
end
|
34
48
|
|
49
|
+
# Upload files from a file input field
|
50
|
+
#
|
51
|
+
# @param url [String] the URL to send the request to
|
52
|
+
# @param files [Bowser::FileList] the files to attach
|
53
|
+
# @keywordparam key [String] the name to use for the POST param, defaults to `"files"`
|
54
|
+
# @keywordparam key_suffix [String] the suffix for the key, defaults to
|
55
|
+
# `"[]"`. This is how several web frameworks determine that the key should
|
56
|
+
# be treated as an array.
|
57
|
+
# @keywordparam method [String] the HTTP method to use, defaults to `"POST"`
|
35
58
|
def upload_files(url, files, key: 'files', key_suffix: '[]', method: :post, &block)
|
36
59
|
promise = Promise.new
|
37
60
|
request = Request.new(method, url)
|
@@ -49,10 +72,17 @@ module Bowser
|
|
49
72
|
promise
|
50
73
|
end
|
51
74
|
|
75
|
+
# Upload a single file from a file input field
|
76
|
+
#
|
77
|
+
# @param url [String] the URL to send the request to
|
78
|
+
# @param file [Bowser::File] the file to attach
|
79
|
+
# @keywordparam key [String] the name to use for the POST param, defaults to `"files"`
|
80
|
+
# @keywordparam method [String] the HTTP method to use, defaults to `"POST"`
|
52
81
|
def upload_file(url, file, key: 'file', method: :post, &block)
|
53
82
|
upload_files(url, [file], key: key, key_suffix: nil, method: method, &block)
|
54
83
|
end
|
55
84
|
|
85
|
+
# @api private
|
56
86
|
def connect_events_to_promise(request, promise)
|
57
87
|
request.on :load do
|
58
88
|
promise.resolve request.response
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Bowser
|
2
2
|
module HTTP
|
3
|
+
# Data to be attached to a form or sent in with a Bowser::HTTP::Request
|
3
4
|
class FormData
|
5
|
+
# @param attributes [Hash, nil] the attributes to attach
|
4
6
|
def initialize attributes={}
|
5
7
|
@native = `new FormData()`
|
6
8
|
attributes.each do |key, value|
|
@@ -14,6 +16,8 @@ module Bowser
|
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
19
|
+
# @param key [String] the name of the attribute
|
20
|
+
# @param value [String] the value of the attribute
|
17
21
|
def append key, value
|
18
22
|
data = if `!!value['native']`
|
19
23
|
`value['native']`
|
data/opal/bowser/http/request.rb
CHANGED
@@ -3,6 +3,7 @@ require 'bowser/event_target'
|
|
3
3
|
|
4
4
|
module Bowser
|
5
5
|
module HTTP
|
6
|
+
# A Ruby object representing an HTTP request to a remote server.
|
6
7
|
class Request
|
7
8
|
include EventTarget
|
8
9
|
|
@@ -15,6 +16,8 @@ module Bowser
|
|
15
16
|
LOADING = 3
|
16
17
|
DONE = 4
|
17
18
|
|
19
|
+
# @param method [String] the HTTP method to use
|
20
|
+
# @param url [String] the URL to send the request to
|
18
21
|
def initialize(method, url, native: `new XMLHttpRequest()`)
|
19
22
|
@native = native
|
20
23
|
@method = method
|
@@ -22,6 +25,11 @@ module Bowser
|
|
22
25
|
@response = Response.new(@native)
|
23
26
|
end
|
24
27
|
|
28
|
+
# Send the HTTP request
|
29
|
+
#
|
30
|
+
# @keywordparam data [Hash, Bowser::HTTP::FormData, nil] the data to send
|
31
|
+
# with the request.
|
32
|
+
# @keywordparam headers [Hash] the HTTP headers to attach to the request
|
25
33
|
def send(data: {}, headers: {})
|
26
34
|
`#@native.open(#{method}, #{url})`
|
27
35
|
@data = data
|
@@ -40,6 +48,10 @@ module Bowser
|
|
40
48
|
self
|
41
49
|
end
|
42
50
|
|
51
|
+
# Set the headers to the keys and values of the specified hash
|
52
|
+
#
|
53
|
+
# @param headers [Hash] the hash whose keys and values should be added as
|
54
|
+
# request headers
|
43
55
|
def headers= headers
|
44
56
|
@headers = headers
|
45
57
|
headers.each do |attr, value|
|
@@ -47,30 +59,37 @@ module Bowser
|
|
47
59
|
end
|
48
60
|
end
|
49
61
|
|
62
|
+
# @return [Boolean] true if this request is a POST request, false otherwise
|
50
63
|
def post?
|
51
64
|
method == :post
|
52
65
|
end
|
53
66
|
|
67
|
+
# @return [Boolean] true if this request is a GET request, false otherwise
|
54
68
|
def get?
|
55
69
|
method == :get
|
56
70
|
end
|
57
71
|
|
72
|
+
# @return [Numeric] the numeric readyState of the underlying XMLHttpRequest
|
58
73
|
def ready_state
|
59
74
|
`#@native.readyState`
|
60
75
|
end
|
61
76
|
|
77
|
+
# @return [Boolean] true if this request has been sent, false otherwise
|
62
78
|
def sent?
|
63
79
|
ready_state >= OPENED
|
64
80
|
end
|
65
81
|
|
82
|
+
# @return [Boolean] true if we have received response headers, false otherwise
|
66
83
|
def headers_received?
|
67
84
|
ready_state >= HEADERS_RECEIVED
|
68
85
|
end
|
69
86
|
|
87
|
+
# @return [Boolean] true if we are currently downloading the response body, false otherwise
|
70
88
|
def loading?
|
71
89
|
ready_state == LOADING
|
72
90
|
end
|
73
91
|
|
92
|
+
# @return [Boolean] true if the response has been completed, false otherwise
|
74
93
|
def done?
|
75
94
|
ready_state >= DONE
|
76
95
|
end
|
@@ -2,27 +2,35 @@ require 'json'
|
|
2
2
|
|
3
3
|
module Bowser
|
4
4
|
module HTTP
|
5
|
+
# Ruby class representing an HTTP response from a remote server
|
5
6
|
class Response
|
7
|
+
# @param xhr [JS] a native XMLHttpRequest object
|
6
8
|
def initialize xhr
|
7
9
|
@xhr = xhr
|
8
10
|
end
|
9
11
|
|
12
|
+
# @return [Numeric] the HTTP status code of the response
|
10
13
|
def code
|
11
14
|
`#@xhr.status`
|
12
15
|
end
|
13
16
|
|
17
|
+
# @return [String] the body of the response as a string
|
14
18
|
def body
|
15
19
|
`#@xhr.response`
|
16
20
|
end
|
17
21
|
|
22
|
+
# @return [Hash, Array, String] the response body deserialized from JSON
|
18
23
|
def json
|
19
24
|
@json ||= JSON.parse(body) if `#{body} !== undefined`
|
20
25
|
end
|
21
26
|
|
27
|
+
# @return [Boolean] true if this was a successful response (2xx-3xx), false otherwise
|
22
28
|
def success?
|
23
29
|
(200...400).cover? code
|
24
30
|
end
|
31
|
+
alias ok? success?
|
25
32
|
|
33
|
+
# @return [Boolean] true if this represents a failed response (4xx-5xx)
|
26
34
|
def fail?
|
27
35
|
!success?
|
28
36
|
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'bowser/event_target'
|
2
|
+
require 'promise'
|
3
|
+
|
4
|
+
module Bowser
|
5
|
+
class IndexedDB
|
6
|
+
name = %w(
|
7
|
+
indexedDB
|
8
|
+
mozIndexedDB
|
9
|
+
webkitIndexedDB
|
10
|
+
msIndexedDB
|
11
|
+
).find { |name| `!!window[#{name}]` }
|
12
|
+
NATIVE = `window[#{name}]`
|
13
|
+
|
14
|
+
def initialize name, version: 1
|
15
|
+
@thens = []
|
16
|
+
|
17
|
+
request = Request.new(`#{NATIVE}.open(#{name}, #{version})`)
|
18
|
+
request.on :error do |event|
|
19
|
+
`console.error(event)`
|
20
|
+
end
|
21
|
+
request.on :success do |event|
|
22
|
+
@native = event.target.result
|
23
|
+
|
24
|
+
@thens.each(&:call)
|
25
|
+
@thens.clear
|
26
|
+
end
|
27
|
+
|
28
|
+
request.on :upgradeneeded do |event|
|
29
|
+
puts 'upgradeneeded'
|
30
|
+
@native = event.target.result
|
31
|
+
|
32
|
+
if block_given?
|
33
|
+
yield self
|
34
|
+
puts 'upgraded'
|
35
|
+
else
|
36
|
+
raise ArgumentError, "You must provide a block to `#{self.class}.new` in order to set up the database if the user's browser does not have it."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_object_store name, key_path: `undefined`, unique: false
|
42
|
+
ObjectStore.new(`#@native.createObjectStore(#{name}, { keyPath: #{key_path} })`)
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete_object_store name
|
46
|
+
`#@native.deleteObjectStore(#{name})`
|
47
|
+
rescue `TypeError` => e
|
48
|
+
# If the object store doesn't exist, we do nothing. The store not existing
|
49
|
+
# is the end state we want after this method call anyway.
|
50
|
+
end
|
51
|
+
|
52
|
+
def transaction name, mode=:readonly
|
53
|
+
Transaction.new(`#@native.transaction(#{name}, #{mode})`)
|
54
|
+
end
|
55
|
+
|
56
|
+
def then &block
|
57
|
+
if @native
|
58
|
+
block.call
|
59
|
+
else
|
60
|
+
@thens << block
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def deserialize klass, native
|
65
|
+
`Object.assign(#{klass.allocate}, #{native})`
|
66
|
+
end
|
67
|
+
|
68
|
+
class ObjectStore
|
69
|
+
def initialize native
|
70
|
+
@native = native
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_index name, key_path=name, unique: false
|
74
|
+
`#@native.createIndex(#{name}, #{key_path}, { unique: #{unique} })`
|
75
|
+
end
|
76
|
+
|
77
|
+
def add object
|
78
|
+
`#@native.add(#{object})`
|
79
|
+
end
|
80
|
+
|
81
|
+
def put object
|
82
|
+
`#@native.put(#{object})`
|
83
|
+
end
|
84
|
+
|
85
|
+
def delete key
|
86
|
+
p = Promise.new
|
87
|
+
key = yield Query.new if block_given?
|
88
|
+
|
89
|
+
request = Request.new(`#@native.delete(#{key})`)
|
90
|
+
request.on(:success) { p.resolve }
|
91
|
+
request.on(:error) { |error| p.reject error }
|
92
|
+
|
93
|
+
p
|
94
|
+
end
|
95
|
+
|
96
|
+
def get key
|
97
|
+
p = Promise.new
|
98
|
+
key = yield Query.new if block_given?
|
99
|
+
|
100
|
+
request = Request.new(`#@native.get(#{key})`)
|
101
|
+
request.on :success do |event|
|
102
|
+
js_obj = `#{event.target}.result`
|
103
|
+
if `!!#{js_obj}`
|
104
|
+
p.resolve `Object.assign(#{klass.allocate}, #{js_obj})`
|
105
|
+
else
|
106
|
+
p.resolve nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
request.on(:error) { |event| p.reject event.target.result }
|
110
|
+
|
111
|
+
p
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_all klass, count: `undefined`
|
115
|
+
p = Promise.new
|
116
|
+
query = block_given? ? yield(Query.new) : `undefined`
|
117
|
+
|
118
|
+
request = Request.new(`#@native.getAll(#{query}, #{count})`)
|
119
|
+
request.on :success do |event|
|
120
|
+
p.resolve event.target.result.map { |js_obj|
|
121
|
+
`delete #{js_obj}.$$id` # Remove old Ruby runtime metadata
|
122
|
+
`Object.assign(#{klass.allocate}, #{js_obj})`
|
123
|
+
}
|
124
|
+
end
|
125
|
+
request.on :error do |event|
|
126
|
+
p.reject event.target.result
|
127
|
+
end
|
128
|
+
|
129
|
+
p
|
130
|
+
end
|
131
|
+
|
132
|
+
def index name
|
133
|
+
Index.new(`#@native.index(#{name})`)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Index
|
138
|
+
def initialize native
|
139
|
+
@native = native
|
140
|
+
end
|
141
|
+
|
142
|
+
def get klass, key
|
143
|
+
p = Promise.new
|
144
|
+
key = yield Query.new if block_given?
|
145
|
+
|
146
|
+
request = Request.new(`#@native.get(#{key})`)
|
147
|
+
request.on :success do |event|
|
148
|
+
begin
|
149
|
+
p.resolve `Object.assign(#{klass.allocate}, #{event.target.result})`
|
150
|
+
rescue NoMethodError # Happens when there is no result above :-\
|
151
|
+
p.resolve nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
request.on :error do |event|
|
156
|
+
p.reject event.target.result
|
157
|
+
end
|
158
|
+
|
159
|
+
p
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_all klass, count: `undefined`
|
163
|
+
p = Promise.new
|
164
|
+
|
165
|
+
query = if block_given?
|
166
|
+
yield Query.new
|
167
|
+
else
|
168
|
+
`undefined`
|
169
|
+
end
|
170
|
+
|
171
|
+
request = Request.new(`#@native.getAll(#{query}, #{count})`)
|
172
|
+
request.on :success do |event|
|
173
|
+
p.resolve event.target.result.map { |js_obj|
|
174
|
+
`delete #{js_obj}.$$id` # Remove old Ruby runtime metadata
|
175
|
+
`Object.assign(#{klass.allocate}, #{js_obj})`
|
176
|
+
}
|
177
|
+
end
|
178
|
+
request.on :error do |event|
|
179
|
+
p.reject event.target.result
|
180
|
+
end
|
181
|
+
|
182
|
+
p
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class Query
|
187
|
+
name = %w(
|
188
|
+
IDBKeyRange
|
189
|
+
msIDBKeyRange
|
190
|
+
mozIDBKeyRange
|
191
|
+
webkitIDBKeyRange
|
192
|
+
).find { |name| `!!window[#{name}]` }
|
193
|
+
NATIVE = `window[#{name}]`
|
194
|
+
|
195
|
+
def > value
|
196
|
+
lower_bound value, true
|
197
|
+
end
|
198
|
+
|
199
|
+
def >= value
|
200
|
+
lower_bound value
|
201
|
+
end
|
202
|
+
|
203
|
+
def < value
|
204
|
+
upper_bound value, true
|
205
|
+
end
|
206
|
+
|
207
|
+
def <= value
|
208
|
+
upper_bound value
|
209
|
+
end
|
210
|
+
|
211
|
+
def == value
|
212
|
+
`#{NATIVE}.only(#{value})`
|
213
|
+
end
|
214
|
+
|
215
|
+
def lower_bound value, exclusive=false
|
216
|
+
`#{NATIVE}.lowerBound(value, exclusive)`
|
217
|
+
end
|
218
|
+
|
219
|
+
def upper_bound value, exclusive=false
|
220
|
+
`#{NATIVE}.upperBound(value, exclusive)`
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
class Transaction
|
225
|
+
include Bowser::EventTarget
|
226
|
+
|
227
|
+
def initialize native
|
228
|
+
@native = native
|
229
|
+
end
|
230
|
+
|
231
|
+
def object_store name=`#@native.objectStoreNames[0]`
|
232
|
+
ObjectStore.new(`#@native.objectStore(#{name})`)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
class Request
|
237
|
+
include Bowser::EventTarget
|
238
|
+
|
239
|
+
def initialize native
|
240
|
+
@native = native
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'promise'
|
2
|
+
|
3
|
+
module Bowser
|
4
|
+
class ServiceWorker
|
5
|
+
NotSupported = Class.new(StandardError)
|
6
|
+
|
7
|
+
def self.register path, options={}
|
8
|
+
p = Promise.new
|
9
|
+
|
10
|
+
if supported?
|
11
|
+
%x{
|
12
|
+
navigator.serviceWorker.register(#{path}, #{options.to_n})
|
13
|
+
.then(#{proc { |reg| p.resolve new(reg) }})
|
14
|
+
.catch(#{proc { |error| p.fail error}});
|
15
|
+
}
|
16
|
+
else
|
17
|
+
p.reject NotSupported.new('Service worker is not supported in this browser')
|
18
|
+
end
|
19
|
+
|
20
|
+
p
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.ready &block
|
24
|
+
`navigator.serviceWorker.ready.then(#{proc { |sw| block.call new(sw) }})`
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.supported?
|
28
|
+
`'serviceWorker' in navigator`
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize native
|
32
|
+
@native = native
|
33
|
+
end
|
34
|
+
|
35
|
+
def scope
|
36
|
+
`#@native.scope`
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_n
|
40
|
+
@native
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|