perkins 0.0.3 → 0.0.5

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.
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));