bowser 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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']`
@@ -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