perkins 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/Gemfile +1 -0
  4. data/README.md +11 -8
  5. data/Rakefile +1 -18
  6. data/config/database.yml +19 -0
  7. data/db/migrate/20150130143050_create_builds.rb +1 -1
  8. data/db/migrate/20150220143051_add_build_status_to_build_reports.rb +10 -0
  9. data/db/migrate/20150220143053_add_commit_to_build_reports.rb +10 -0
  10. data/db/migrate/20150220143054_add_hook_id_to_repo.rb +10 -0
  11. data/db/schema.rb +4 -2
  12. data/examples/Capfile +17 -0
  13. data/examples/Gemfile +14 -1
  14. data/examples/Gemfile.lock +45 -19
  15. data/examples/Procfile +8 -3
  16. data/examples/README.md +12 -0
  17. data/examples/boot.rb +5 -4
  18. data/examples/config.ru +2 -11
  19. data/examples/{database.yml → config/database.example.yml} +0 -0
  20. data/examples/config/database.yml +27 -0
  21. data/examples/config/deploy.rb +78 -0
  22. data/examples/config/deploy/staging.rb +61 -0
  23. data/examples/{rainbows.rb → config/rainbows/development.rb} +1 -1
  24. data/examples/config/rainbows/production.rb +56 -0
  25. data/examples/{sidekiq.yml → config/sidekiq.yml} +0 -0
  26. data/examples/db/schema.rb +43 -0
  27. data/examples/rakefile +8 -0
  28. data/lib/perkins/application.rb +1 -1
  29. data/lib/perkins/assets.rb +3 -3
  30. data/lib/perkins/assets/javascripts/app.js +1 -0
  31. data/lib/perkins/assets/javascripts/perkings.js.coffee +14 -9
  32. data/lib/perkins/assets/javascripts/perkins/helpers.js.coffee +2 -2
  33. data/lib/perkins/assets/javascripts/perkins/m/models.js.coffee +15 -9
  34. data/lib/perkins/assets/javascripts/perkins/router.js.coffee +1 -0
  35. data/lib/perkins/assets/javascripts/perkins/v/my_repos.js.coffee +1 -1
  36. data/lib/perkins/assets/javascripts/perkins/v/orgs.js.coffee +1 -1
  37. data/lib/perkins/assets/javascripts/perkins/v/repo.js.coffee +38 -5
  38. data/lib/perkins/assets/javascripts/templates/repo.hamlc +5 -14
  39. data/lib/perkins/assets/javascripts/templates/repos/build_row.hamlc +10 -5
  40. data/lib/perkins/assets/javascripts/templates/repos/config.hamlc +23 -13
  41. data/lib/perkins/assets/javascripts/templates/repos/report_detail.hamlc +6 -6
  42. data/lib/perkins/assets/javascripts/vendor/eventsource.polyfill.js +512 -0
  43. data/lib/perkins/build/script.rb +2 -1
  44. data/lib/perkins/build/script/go.rb +1 -1
  45. data/lib/perkins/build/script/ruby.rb +1 -1
  46. data/lib/perkins/build/script/stages.rb +0 -1
  47. data/lib/perkins/build_report.rb +27 -7
  48. data/lib/perkins/build_worker.rb +10 -15
  49. data/lib/perkins/git_loader_worker.rb +1 -14
  50. data/lib/perkins/repo.rb +106 -21
  51. data/lib/perkins/runner.rb +57 -12
  52. data/lib/perkins/server.rb +42 -46
  53. data/lib/perkins/socket_server.rb +38 -0
  54. data/lib/perkins/version.rb +1 -1
  55. data/lib/tasks/db_tasks.rake +35 -0
  56. data/perkins.gemspec +16 -19
  57. data/spec/README.md +3 -0
  58. data/spec/lib/build/build_spec.rb +7 -3
  59. data/spec/lib/repo_spec.rb +23 -32
  60. data/spec/lib/runner_spec.rb +52 -2
  61. data/spec/lib/server_spec.rb +15 -6
  62. data/spec/spec_helper.rb +6 -5
  63. metadata +21 -57
  64. data/.DS_Store +0 -0
  65. data/lib/perkins/views/builds.haml +0 -43
  66. data/lib/perkins/views/menu.haml +0 -18
  67. data/lib/perkins/views/orgs.haml +0 -101
  68. data/lib/perkins/views/profile.haml +0 -31
  69. data/lib/perkins/views/repos/config.haml +0 -72
  70. data/lib/perkins/views/repos/github.haml +0 -45
  71. data/lib/perkins/views/repos/menu.haml +0 -17
  72. data/lib/perkins/views/repos/repo.haml +0 -64
  73. data/lib/perkins/views/repos/spinner.haml +0 -3
@@ -6,5 +6,6 @@ class Perkins.Routers.main extends Backbone.Router
6
6
  "repos/:login/:name/builds/:id": "getRepoWithBuild"
7
7
  "repos/:login/:name/builds": "getRepoBuilds"
8
8
  "repos/:login/:name/config": "getRepoConfig"
9
+ "repos/:login/:name/add_hook": "getRepoConfig"
9
10
  "orgs/:name": "getOrg"
10
11
  "myrepos": "getMyRepos"
@@ -22,7 +22,7 @@ class Perkins.Views.MyRepos extends Backbone.View
22
22
  target.text("adding...")
23
23
  target.addClass("btn-diabled")
24
24
  repo_id = target.data('gb-id')
25
- url = "/repos/add/#{repo_id}"
25
+ url = "/repos/add/#{repo_id}.json"
26
26
 
27
27
  #add repo & refresh sidebar on success.
28
28
  $.ajax
@@ -20,7 +20,7 @@ class Perkins.Views.Org extends Backbone.View
20
20
  target.text("adding...")
21
21
  target.addClass("btn-diabled")
22
22
  repo_id = target.data('gb-id')
23
- url = "/repos/add/#{repo_id}"
23
+ url = "/repos/add/#{repo_id}.json"
24
24
 
25
25
  #add repo & refresh sidebar on success.
26
26
  $.ajax
@@ -11,19 +11,24 @@ class Perkins.Views.Repo extends Backbone.View
11
11
  initialize: (opts)->
12
12
  @model = opts.model
13
13
 
14
+ window.current_view = @
15
+
14
16
  if opts.build_id
15
17
  @build = new Perkins.Models.BuildReport({id: opts.build_id, repo: @model})
16
18
  else if @model.get('last_report_id')
17
19
  @build = new Perkins.Models.BuildReport({id: @model.get('last_report_id'), repo: @model})
18
20
 
19
21
  @listenTo(@build, "change", @renderBuildReport) if @build
22
+ @listenTo(@build, "change:build_status", @pulseReport) if @build
23
+
20
24
  @listenTo(@model, "change:build_status", @handleSpinnerBtn)
25
+ @listenTo(@model, "change:response", @renderLogView)
21
26
 
22
27
  template: ()->
23
28
  JST["repo"](@model.toJSON())
24
29
 
25
30
  replaceUri: (ev)->
26
- console.log ev
31
+ #console.log ev
27
32
  t = ev.currentTarget
28
33
  num = $(t.parentNode).prevAll('p').length + 1
29
34
  url = window.location + ''
@@ -49,6 +54,9 @@ class Perkins.Views.Repo extends Backbone.View
49
54
  @build.fetch()
50
55
  @displayBadge()
51
56
 
57
+ renderLogView: ->
58
+ @build_detail.render()
59
+
52
60
  displayBadge: ->
53
61
  name = @model.get('name')
54
62
  url = "#{window.location.origin}/badge?repo=#{name}"
@@ -62,8 +70,11 @@ class Perkins.Views.Repo extends Backbone.View
62
70
  return if $(ev.currentTarget).find("i").hasClass("fa-spin")
63
71
  @build_detail.restart()
64
72
 
73
+ pulseReport: ->
74
+ @build_detail.pulse()
75
+
65
76
  runCommit: ->
66
- url = "/repos/#{@model.get('name')}/run_commit"
77
+ url = "/repos/#{@model.get('name')}/run_commit.json"
67
78
  $.ajax
68
79
  url: url
69
80
  beforeSend: ->
@@ -113,8 +124,15 @@ class Perkins.Views.BuildDetail extends Backbone.View
113
124
  restart: ->
114
125
  $("#restart-build-btn").find("i").addClass("fa-spin")
115
126
  @build.restart()
127
+ @pulse
116
128
  false
117
129
 
130
+ pulse: ->
131
+ setTimeout ()=>
132
+ @build.fetch()
133
+ @pulse() unless @build.isStopped()
134
+ , 700
135
+
118
136
  close: ()->
119
137
  $(@el).html("")
120
138
  console.log "CLOSED VIEW CALLED"
@@ -161,6 +179,12 @@ class Perkins.Views.RepoBuildCollection extends Backbone.Marionette.CollectionVi
161
179
  #REPO CONFIG
162
180
 
163
181
  class Perkins.Views.RepoConfig extends Backbone.View
182
+
183
+ el: "#main-content"
184
+
185
+ events:
186
+ "submit #add-hook" : "submitForm"
187
+
164
188
  initialize: (opts={})->
165
189
  @repo = opts.model
166
190
  window.current_repo = @repo
@@ -169,17 +193,26 @@ class Perkins.Views.RepoConfig extends Backbone.View
169
193
  @listenTo(@hook, "error", @defaultErrorHandler)
170
194
 
171
195
 
172
- el: "#main-content"
173
-
174
196
  template: ->
175
197
  JST["repos/config"]({repo: @model.toJSON(), hook: @hook.toJSON() })
176
198
 
177
199
  render: ->
178
200
  console.log("render repo builds")
179
201
  @hook.fetch
180
- success: =>
202
+ success: (res)=>
181
203
  $(@el).html(@template())
182
204
 
205
+
206
+ submitForm: (e)->
207
+ target = $(e.currentTarget)
208
+
209
+ $.ajax
210
+ url: target.attr("action")
211
+ method: "POST"
212
+ .success ()=>
213
+ Backbone.history.navigate("#{@repo.get('name')}/config")
214
+
215
+ false
183
216
  close: ()->
184
217
  $(@el).html("")
185
218
  console.log "CLOSED VIEW CALLED"
@@ -16,21 +16,12 @@
16
16
 
17
17
  #build-report-container
18
18
 
19
+ #no-build-warning.alert.alert-warning
20
+ %p
21
+ %span.no-build No builds yet.
19
22
 
20
- - if @download_status isnt "downloaded"
21
- %hr
22
- %div{style: "margin:0 auto;width:255px"}
23
- %h3 we are cloning this repo
24
- %hr
25
- %img{:src => "/assets/working.gif", :alt => "downloading..."}
26
- - else
27
-
28
- #no-build-warning.alert.alert-warning
29
- %p
30
- %span.no-build No builds yet.
31
-
32
- %a.run-commit{:href => "#"}
33
- Run latest commit in master branch
23
+ %a.run-commit{:href => "#"}
24
+ Run latest commit in master branch
34
25
 
35
26
 
36
27
 
@@ -2,18 +2,23 @@
2
2
  %td
3
3
  %a{href: "/repos/#{current_repo.get('name')}/builds/#{@.id}"}
4
4
  %span{class: Perkins.Helpers.status_class(@status) }
5
- -#= build['_id'].to_s[0..7]
6
5
 
7
6
  %td
8
- = @.commit.message.split("\n")[0]
7
+ = @.commit.commit.message.split("\n")[0]
9
8
  %td
10
- %a{href: Perkins.Helpers.commit_url(current_repo.toJSON(), @.commit.sha), target: "blank"}
9
+
10
+ %a{href: Perkins.Helpers.commit_url(@.commit.sha), target: "blank"}
11
11
  #{@.commit.sha[0..7]}
12
12
  - if @.branch.present?
13
13
  %strong
14
14
  = "(#{@.branch})"
15
15
 
16
+
16
17
  %td
17
- = "#{Perkins.Helpers.round(@.duration)} secs"
18
+ - if @.build_status is "stopped"
19
+ = "#{Perkins.Helpers.round(@.duration)} secs"
20
+ - else
21
+ \- running
18
22
  %td
19
- %abbr.timeago{title:@.build_time}
23
+ - if @.build_status is "stopped"
24
+ %abbr.timeago{title:@.build_time}
@@ -19,7 +19,7 @@
19
19
  .panel-body
20
20
 
21
21
 
22
- - if @hook
22
+ - if @hook.config
23
23
  .col-sm-12
24
24
 
25
25
  .pull-right
@@ -44,6 +44,9 @@
44
44
  %li
45
45
  %strong content type:
46
46
  = @hook.config.content_type
47
+ %li
48
+ %strong events:
49
+ = @hook.events
47
50
  %li
48
51
  %strong ssl:
49
52
  = @hook.config.insecure_ssl
@@ -52,6 +55,10 @@
52
55
  %p
53
56
  = @hook.test_url
54
57
 
58
+ %h4 Ping url:
59
+ %p
60
+ = @hook.ping_url
61
+
55
62
  %h4 Last response:
56
63
  %p
57
64
 
@@ -63,15 +70,18 @@
63
70
  %strong Message:
64
71
  = @hook.last_response.message
65
72
 
66
- .col-sm-12
67
-
68
- %h3
69
- = if @hook then "Will update exiting (#{@hook.id}) hook." else "Will create new hook."
70
- %form{:action => "/repos/#{@repo.name}/add_hook", method: "post"}
71
- %input{:type => "text", name: "webhook_url", placeholder: "type the receive hook url...", style:"width:250px"}
72
- %button Send
73
-
74
- %p.small
75
- we are going to send the hook to
76
- %strong
77
- = @repo.url
73
+ - else
74
+
75
+ .col-sm-12
76
+ %h2 No hay hook
77
+ %h3
78
+ = "Will create new hook."
79
+
80
+ %form#add-hook{:action => "/repos/#{@repo.name}/add_hook", method: "post"}
81
+ %input{:type => "text", name: "webhook_url", placeholder: "type the receive hook url...", style:"width:250px"}
82
+ %button Send
83
+
84
+ %p.small
85
+ we are going to send the hook to
86
+ %strong
87
+ = @repo.url
@@ -10,7 +10,7 @@
10
10
 
11
11
  %strong= @build.branch
12
12
  \-
13
- = @build.commit.message
13
+ = @build.commit.commit.message
14
14
 
15
15
  .pull-right.stats
16
16
  %p
@@ -26,17 +26,17 @@
26
26
 
27
27
 
28
28
  .col-sm-12
29
- %img.avatar{ src: Perkins.Helpers.gravatar_url( @build.commit.email, 35) }
30
- %strong= @build.commit.author
31
- = @build.commit.email
29
+ %img.avatar{ src: Perkins.Helpers.gravatar_url( @build.commit.commit.author.email, 35) }
30
+ %strong= @build.commit.commit.author.name
31
+ = @build.commit.commit.author.email
32
32
 
33
33
  .pull-right
34
- %a{href: Perkins.Helpers.commit_url(@, @build.sha), target: "blank"}
34
+ %a{href: Perkins.Helpers.commit_url(@build.sha), target: "blank"}
35
35
  %img{src: "/assets/github.svg", width:"20px"}
36
36
  = "commit ... #{@build.sha.substr(0, 6)}"
37
37
 
38
38
  %strong
39
- %abbr.timeago{title @build.commit.created_at}
39
+ %abbr.timeago{title @build.commit.commit.author.date}
40
40
 
41
41
  %ul.list-group
42
42
  %li.list-group-item.build-item
@@ -0,0 +1,512 @@
1
+ /** @license
2
+ * eventsource.js
3
+ * Available under MIT License (MIT)
4
+ * https://github.com/Yaffle/EventSource/
5
+ */
6
+
7
+ /*jslint indent: 2, vars: true, plusplus: true */
8
+ /*global setTimeout, clearTimeout */
9
+
10
+ (function (global) {
11
+ "use strict";
12
+
13
+ function Map() {
14
+ this.data = {};
15
+ }
16
+
17
+ Map.prototype.get = function (key) {
18
+ return this.data[key + "~"];
19
+ };
20
+ Map.prototype.set = function (key, value) {
21
+ this.data[key + "~"] = value;
22
+ };
23
+ Map.prototype["delete"] = function (key) {
24
+ delete this.data[key + "~"];
25
+ };
26
+
27
+ function EventTarget() {
28
+ this.listeners = new Map();
29
+ }
30
+
31
+ function throwError(e) {
32
+ setTimeout(function () {
33
+ throw e;
34
+ }, 0);
35
+ }
36
+
37
+ EventTarget.prototype.dispatchEvent = function (event) {
38
+ event.target = this;
39
+ var type = event.type.toString();
40
+ var listeners = this.listeners;
41
+ var typeListeners = listeners.get(type);
42
+ if (typeListeners == undefined) {
43
+ return;
44
+ }
45
+ var length = typeListeners.length;
46
+ var i = -1;
47
+ var listener = undefined;
48
+ while (++i < length) {
49
+ listener = typeListeners[i];
50
+ try {
51
+ listener.call(this, event);
52
+ } catch (e) {
53
+ throwError(e);
54
+ }
55
+ }
56
+ };
57
+ EventTarget.prototype.addEventListener = function (type, callback) {
58
+ type = type.toString();
59
+ var listeners = this.listeners;
60
+ var typeListeners = listeners.get(type);
61
+ if (typeListeners == undefined) {
62
+ typeListeners = [];
63
+ listeners.set(type, typeListeners);
64
+ }
65
+ var i = typeListeners.length;
66
+ while (--i >= 0) {
67
+ if (typeListeners[i] === callback) {
68
+ return;
69
+ }
70
+ }
71
+ typeListeners.push(callback);
72
+ };
73
+ EventTarget.prototype.removeEventListener = function (type, callback) {
74
+ type = type.toString();
75
+ var listeners = this.listeners;
76
+ var typeListeners = listeners.get(type);
77
+ if (typeListeners == undefined) {
78
+ return;
79
+ }
80
+ var length = typeListeners.length;
81
+ var filtered = [];
82
+ var i = -1;
83
+ while (++i < length) {
84
+ if (typeListeners[i] !== callback) {
85
+ filtered.push(typeListeners[i]);
86
+ }
87
+ }
88
+ if (filtered.length === 0) {
89
+ listeners["delete"](type);
90
+ } else {
91
+ listeners.set(type, filtered);
92
+ }
93
+ };
94
+
95
+ function Event(type) {
96
+ this.type = type;
97
+ this.target = undefined;
98
+ }
99
+
100
+ function MessageEvent(type, options) {
101
+ Event.call(this, type);
102
+ this.data = options.data;
103
+ this.lastEventId = options.lastEventId;
104
+ }
105
+
106
+ MessageEvent.prototype = Event.prototype;
107
+
108
+ var XHR = global.XMLHttpRequest;
109
+ var XDR = global.XDomainRequest;
110
+ var isCORSSupported = XHR != undefined && (new XHR()).withCredentials != undefined;
111
+ var isXHR = isCORSSupported || (XHR != undefined && XDR == undefined);
112
+ var Transport = isXHR ? XHR : XDR;
113
+
114
+ var WAITING = -1;
115
+ var CONNECTING = 0;
116
+ var OPEN = 1;
117
+ var CLOSED = 2;
118
+ var AFTER_CR = 3;
119
+ var FIELD_START = 4;
120
+ var FIELD = 5;
121
+ var VALUE_START = 6;
122
+ var VALUE = 7;
123
+ var contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i;
124
+
125
+ var MINIMUM_DURATION = 1000;
126
+ var MAXIMUM_DURATION = 18000000;
127
+
128
+ function getDuration(value, def) {
129
+ var n = value;
130
+ if (n !== n) {
131
+ n = def;
132
+ }
133
+ return (n < MINIMUM_DURATION ? MINIMUM_DURATION : (n > MAXIMUM_DURATION ? MAXIMUM_DURATION : n));
134
+ }
135
+
136
+ function fire(that, f, event) {
137
+ try {
138
+ if (typeof f === "function") {
139
+ f.call(that, event);
140
+ }
141
+ } catch (e) {
142
+ throwError(e);
143
+ }
144
+ }
145
+
146
+ function EventSource(url, options) {
147
+ url = url.toString();
148
+
149
+ var withCredentials = isCORSSupported && options != undefined && Boolean(options.withCredentials);
150
+ var initialRetry = getDuration(1000, 0);
151
+ var heartbeatTimeout = getDuration(45000, 0);
152
+
153
+ var lastEventId = "";
154
+ var that = this;
155
+ var retry = initialRetry;
156
+ var wasActivity = false;
157
+ var xhr = options != undefined && options.Transport != undefined ? new options.Transport() : new Transport();
158
+ var timeout = 0;
159
+ var timeout0 = 0;
160
+ var charOffset = 0;
161
+ var currentState = WAITING;
162
+ var dataBuffer = [];
163
+ var lastEventIdBuffer = "";
164
+ var eventTypeBuffer = "";
165
+ var onTimeout = undefined;
166
+
167
+ var state = FIELD_START;
168
+ var field = "";
169
+ var value = "";
170
+
171
+ function close() {
172
+ currentState = CLOSED;
173
+ if (xhr != undefined) {
174
+ xhr.abort();
175
+ xhr = undefined;
176
+ }
177
+ if (timeout !== 0) {
178
+ clearTimeout(timeout);
179
+ timeout = 0;
180
+ }
181
+ if (timeout0 !== 0) {
182
+ clearTimeout(timeout0);
183
+ timeout0 = 0;
184
+ }
185
+ that.readyState = CLOSED;
186
+ }
187
+
188
+ function onEvent(type) {
189
+ var responseText = currentState === OPEN || currentState === CONNECTING ? xhr.responseText : "";
190
+ var event = undefined;
191
+ var isWrongStatusCodeOrContentType = false;
192
+
193
+ if (currentState === CONNECTING) {
194
+ var status = 0;
195
+ var statusText = "";
196
+ var contentType = undefined;
197
+ if (isXHR) {
198
+ try {
199
+ status = xhr.status;
200
+ statusText = xhr.statusText;
201
+ contentType = xhr.getResponseHeader("Content-Type");
202
+ } catch (error) {
203
+ // https://bugs.webkit.org/show_bug.cgi?id=29121
204
+ status = 0;
205
+ statusText = "";
206
+ contentType = undefined;
207
+ // FF < 14, WebKit
208
+ // https://bugs.webkit.org/show_bug.cgi?id=29658
209
+ // https://bugs.webkit.org/show_bug.cgi?id=77854
210
+ }
211
+ } else if (type !== "" && type !== "error") {
212
+ status = 200;
213
+ statusText = "OK";
214
+ contentType = xhr.contentType;
215
+ }
216
+ if (contentType == undefined) {
217
+ contentType = "";
218
+ }
219
+ if (status === 0 && statusText === "" && type === "load" && responseText !== "") {
220
+ status = 200;
221
+ statusText = "OK";
222
+ if (contentType === "") { // Opera 12
223
+ var tmp = (/^data\:([^,]*?)(?:;base64)?,[\S]*$/).exec(url);
224
+ if (tmp != undefined) {
225
+ contentType = tmp[1];
226
+ }
227
+ }
228
+ }
229
+ if (status === 200 && contentTypeRegExp.test(contentType)) {
230
+ currentState = OPEN;
231
+ wasActivity = true;
232
+ retry = initialRetry;
233
+ that.readyState = OPEN;
234
+ event = new Event("open");
235
+ that.dispatchEvent(event);
236
+ fire(that, that.onopen, event);
237
+ if (currentState === CLOSED) {
238
+ return;
239
+ }
240
+ } else {
241
+ // Opera 12
242
+ if (status !== 0 && (status !== 200 || contentType !== "")) {
243
+ var message = "";
244
+ if (status !== 200) {
245
+ message = "EventSource's response has a status " + status + " " + statusText.replace(/\s+/g, " ") + " that is not 200. Aborting the connection.";
246
+ } else {
247
+ message = "EventSource's response has a Content-Type specifying an unsupported type: " + contentType.replace(/\s+/g, " ") + ". Aborting the connection.";
248
+ }
249
+ setTimeout(function () {
250
+ throw new Error(message);
251
+ }, 0);
252
+ isWrongStatusCodeOrContentType = true;
253
+ }
254
+ }
255
+ }
256
+
257
+ if (currentState === OPEN) {
258
+ if (responseText.length > charOffset) {
259
+ wasActivity = true;
260
+ }
261
+ var i = charOffset - 1;
262
+ var length = responseText.length;
263
+ var c = "\n";
264
+ while (++i < length) {
265
+ c = responseText.charAt(i);
266
+ if (state === AFTER_CR && c === "\n") {
267
+ state = FIELD_START;
268
+ } else {
269
+ if (state === AFTER_CR) {
270
+ state = FIELD_START;
271
+ }
272
+ if (c === "\r" || c === "\n") {
273
+ if (field === "data") {
274
+ dataBuffer.push(value);
275
+ } else if (field === "id") {
276
+ lastEventIdBuffer = value;
277
+ } else if (field === "event") {
278
+ eventTypeBuffer = value;
279
+ } else if (field === "retry") {
280
+ initialRetry = getDuration(Number(value), initialRetry);
281
+ retry = initialRetry;
282
+ } else if (field === "heartbeatTimeout") {
283
+ heartbeatTimeout = getDuration(Number(value), heartbeatTimeout);
284
+ if (timeout !== 0) {
285
+ clearTimeout(timeout);
286
+ timeout = setTimeout(onTimeout, heartbeatTimeout);
287
+ }
288
+ }
289
+ value = "";
290
+ field = "";
291
+ if (state === FIELD_START) {
292
+ if (dataBuffer.length !== 0) {
293
+ lastEventId = lastEventIdBuffer;
294
+ if (eventTypeBuffer === "") {
295
+ eventTypeBuffer = "message";
296
+ }
297
+ event = new MessageEvent(eventTypeBuffer, {
298
+ data: dataBuffer.join("\n"),
299
+ lastEventId: lastEventIdBuffer
300
+ });
301
+ that.dispatchEvent(event);
302
+ if (eventTypeBuffer === "message") {
303
+ fire(that, that.onmessage, event);
304
+ }
305
+ if (currentState === CLOSED) {
306
+ return;
307
+ }
308
+ }
309
+ dataBuffer.length = 0;
310
+ eventTypeBuffer = "";
311
+ }
312
+ state = c === "\r" ? AFTER_CR : FIELD_START;
313
+ } else {
314
+ if (state === FIELD_START) {
315
+ state = FIELD;
316
+ }
317
+ if (state === FIELD) {
318
+ if (c === ":") {
319
+ state = VALUE_START;
320
+ } else {
321
+ field += c;
322
+ }
323
+ } else if (state === VALUE_START) {
324
+ if (c !== " ") {
325
+ value += c;
326
+ }
327
+ state = VALUE;
328
+ } else if (state === VALUE) {
329
+ value += c;
330
+ }
331
+ }
332
+ }
333
+ }
334
+ charOffset = length;
335
+ }
336
+
337
+ if ((currentState === OPEN || currentState === CONNECTING) &&
338
+ (type === "load" || type === "error" || isWrongStatusCodeOrContentType || (charOffset > 1024 * 1024) || (timeout === 0 && !wasActivity))) {
339
+ if (isWrongStatusCodeOrContentType) {
340
+ close();
341
+ } else {
342
+ if (type === "" && timeout === 0 && !wasActivity) {
343
+ setTimeout(function () {
344
+ throw new Error("No activity within " + heartbeatTimeout + " milliseconds. Reconnecting.");
345
+ }, 0);
346
+ }
347
+ currentState = WAITING;
348
+ xhr.abort();
349
+ if (timeout !== 0) {
350
+ clearTimeout(timeout);
351
+ timeout = 0;
352
+ }
353
+ if (retry > initialRetry * 16) {
354
+ retry = initialRetry * 16;
355
+ }
356
+ if (retry > MAXIMUM_DURATION) {
357
+ retry = MAXIMUM_DURATION;
358
+ }
359
+ timeout = setTimeout(onTimeout, retry);
360
+ retry = retry * 2 + 1;
361
+
362
+ that.readyState = CONNECTING;
363
+ }
364
+ event = new Event("error");
365
+ that.dispatchEvent(event);
366
+ fire(that, that.onerror, event);
367
+ } else {
368
+ if (timeout === 0) {
369
+ wasActivity = false;
370
+ timeout = setTimeout(onTimeout, heartbeatTimeout);
371
+ }
372
+ }
373
+ }
374
+
375
+ function onProgress() {
376
+ onEvent("progress");
377
+ }
378
+
379
+ function onLoad() {
380
+ onEvent("load");
381
+ }
382
+
383
+ function onError() {
384
+ onEvent("error");
385
+ }
386
+
387
+ if (isXHR && global.opera != undefined) {
388
+ // workaround for Opera issue with "progress" events
389
+ timeout0 = setTimeout(function f() {
390
+ if (xhr.readyState === 3) {
391
+ onEvent("progress");
392
+ }
393
+ timeout0 = setTimeout(f, 500);
394
+ }, 0);
395
+ }
396
+
397
+ onTimeout = function () {
398
+ timeout = 0;
399
+ if (currentState !== WAITING) {
400
+ onEvent("");
401
+ return;
402
+ }
403
+
404
+ // loading indicator in Safari, Chrome < 14
405
+ if (isXHR && !("onloadend" in xhr) && global.document != undefined && global.document.readyState != undefined && global.document.readyState !== "complete") {
406
+ timeout = setTimeout(onTimeout, 4);
407
+ return;
408
+ }
409
+
410
+ // XDomainRequest#abort removes onprogress, onerror, onload
411
+ xhr.onload = onLoad;
412
+ xhr.onerror = onError;
413
+
414
+ if (isXHR) {
415
+ // improper fix to match Firefox behaviour, but it is better than just ignore abort
416
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=768596
417
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=880200
418
+ // https://code.google.com/p/chromium/issues/detail?id=153570
419
+ xhr.onabort = onError;
420
+
421
+ // Firefox 3.5 - 3.6 - ? < 9.0
422
+ // onprogress is not fired sometimes or delayed
423
+ xhr.onreadystatechange = onProgress;
424
+ }
425
+
426
+ // loading indicator in Firefox
427
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=736723
428
+ if (xhr.sendAsBinary == undefined) {
429
+ xhr.onprogress = onProgress;
430
+ }
431
+
432
+ wasActivity = false;
433
+ timeout = setTimeout(onTimeout, heartbeatTimeout);
434
+
435
+ charOffset = 0;
436
+ currentState = CONNECTING;
437
+ dataBuffer.length = 0;
438
+ eventTypeBuffer = "";
439
+ lastEventIdBuffer = lastEventId;
440
+ value = "";
441
+ field = "";
442
+ state = FIELD_START;
443
+
444
+ var s = url.slice(0, 5);
445
+ if (s !== "data:" && s !== "blob:") {
446
+ s = url + ((url.indexOf("?", 0) === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(lastEventId) + "&r=" + (Math.random() + 1).toString().slice(2));
447
+ } else {
448
+ s = url;
449
+ }
450
+ xhr.open("GET", s, true);
451
+
452
+ if (isXHR) {
453
+ // withCredentials should be set after "open" for Safari and Chrome (< 19 ?)
454
+ xhr.withCredentials = withCredentials;
455
+
456
+ xhr.responseType = "text";
457
+
458
+ // Request header field Cache-Control is not allowed by Access-Control-Allow-Headers.
459
+ // "Cache-control: no-cache" are not honored in Chrome and Firefox
460
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=428916
461
+ //xhr.setRequestHeader("Cache-Control", "no-cache");
462
+ xhr.setRequestHeader("Accept", "text/event-stream");
463
+ // Request header field Last-Event-ID is not allowed by Access-Control-Allow-Headers.
464
+ //xhr.setRequestHeader("Last-Event-ID", lastEventId);
465
+ }
466
+
467
+ xhr.send(undefined);
468
+ };
469
+
470
+ EventTarget.call(this);
471
+ this.close = close;
472
+ this.url = url;
473
+ this.readyState = CONNECTING;
474
+ this.withCredentials = withCredentials;
475
+
476
+ this.onopen = undefined;
477
+ this.onmessage = undefined;
478
+ this.onerror = undefined;
479
+
480
+ onTimeout();
481
+ }
482
+
483
+ function F() {
484
+ this.CONNECTING = CONNECTING;
485
+ this.OPEN = OPEN;
486
+ this.CLOSED = CLOSED;
487
+ }
488
+ F.prototype = EventTarget.prototype;
489
+
490
+ EventSource.prototype = new F();
491
+ F.call(EventSource);
492
+ if (isCORSSupported) {
493
+ EventSource.prototype.withCredentials = undefined;
494
+ }
495
+
496
+ var isEventSourceSupported = function () {
497
+ // Opera 12 fails this test, but this is fine.
498
+ return global.EventSource != undefined && ("withCredentials" in global.EventSource.prototype);
499
+ };
500
+
501
+ if (Transport != undefined && (global.EventSource == undefined || (isCORSSupported && !isEventSourceSupported()))) {
502
+ // Why replace a native EventSource ?
503
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=444328
504
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=831392
505
+ // https://code.google.com/p/chromium/issues/detail?id=260144
506
+ // https://code.google.com/p/chromium/issues/detail?id=225654
507
+ // ...
508
+ global.NativeEventSource = global.EventSource;
509
+ global.EventSource = EventSource;
510
+ }
511
+
512
+ }(this));