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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/Gemfile +1 -0
- data/README.md +11 -8
- data/Rakefile +1 -18
- data/config/database.yml +19 -0
- data/db/migrate/20150130143050_create_builds.rb +1 -1
- data/db/migrate/20150220143051_add_build_status_to_build_reports.rb +10 -0
- data/db/migrate/20150220143053_add_commit_to_build_reports.rb +10 -0
- data/db/migrate/20150220143054_add_hook_id_to_repo.rb +10 -0
- data/db/schema.rb +4 -2
- data/examples/Capfile +17 -0
- data/examples/Gemfile +14 -1
- data/examples/Gemfile.lock +45 -19
- data/examples/Procfile +8 -3
- data/examples/README.md +12 -0
- data/examples/boot.rb +5 -4
- data/examples/config.ru +2 -11
- data/examples/{database.yml → config/database.example.yml} +0 -0
- data/examples/config/database.yml +27 -0
- data/examples/config/deploy.rb +78 -0
- data/examples/config/deploy/staging.rb +61 -0
- data/examples/{rainbows.rb → config/rainbows/development.rb} +1 -1
- data/examples/config/rainbows/production.rb +56 -0
- data/examples/{sidekiq.yml → config/sidekiq.yml} +0 -0
- data/examples/db/schema.rb +43 -0
- data/examples/rakefile +8 -0
- data/lib/perkins/application.rb +1 -1
- data/lib/perkins/assets.rb +3 -3
- data/lib/perkins/assets/javascripts/app.js +1 -0
- data/lib/perkins/assets/javascripts/perkings.js.coffee +14 -9
- data/lib/perkins/assets/javascripts/perkins/helpers.js.coffee +2 -2
- data/lib/perkins/assets/javascripts/perkins/m/models.js.coffee +15 -9
- data/lib/perkins/assets/javascripts/perkins/router.js.coffee +1 -0
- data/lib/perkins/assets/javascripts/perkins/v/my_repos.js.coffee +1 -1
- data/lib/perkins/assets/javascripts/perkins/v/orgs.js.coffee +1 -1
- data/lib/perkins/assets/javascripts/perkins/v/repo.js.coffee +38 -5
- data/lib/perkins/assets/javascripts/templates/repo.hamlc +5 -14
- data/lib/perkins/assets/javascripts/templates/repos/build_row.hamlc +10 -5
- data/lib/perkins/assets/javascripts/templates/repos/config.hamlc +23 -13
- data/lib/perkins/assets/javascripts/templates/repos/report_detail.hamlc +6 -6
- data/lib/perkins/assets/javascripts/vendor/eventsource.polyfill.js +512 -0
- data/lib/perkins/build/script.rb +2 -1
- data/lib/perkins/build/script/go.rb +1 -1
- data/lib/perkins/build/script/ruby.rb +1 -1
- data/lib/perkins/build/script/stages.rb +0 -1
- data/lib/perkins/build_report.rb +27 -7
- data/lib/perkins/build_worker.rb +10 -15
- data/lib/perkins/git_loader_worker.rb +1 -14
- data/lib/perkins/repo.rb +106 -21
- data/lib/perkins/runner.rb +57 -12
- data/lib/perkins/server.rb +42 -46
- data/lib/perkins/socket_server.rb +38 -0
- data/lib/perkins/version.rb +1 -1
- data/lib/tasks/db_tasks.rake +35 -0
- data/perkins.gemspec +16 -19
- data/spec/README.md +3 -0
- data/spec/lib/build/build_spec.rb +7 -3
- data/spec/lib/repo_spec.rb +23 -32
- data/spec/lib/runner_spec.rb +52 -2
- data/spec/lib/server_spec.rb +15 -6
- data/spec/spec_helper.rb +6 -5
- metadata +21 -57
- data/.DS_Store +0 -0
- data/lib/perkins/views/builds.haml +0 -43
- data/lib/perkins/views/menu.haml +0 -18
- data/lib/perkins/views/orgs.haml +0 -101
- data/lib/perkins/views/profile.haml +0 -31
- data/lib/perkins/views/repos/config.haml +0 -72
- data/lib/perkins/views/repos/github.haml +0 -45
- data/lib/perkins/views/repos/menu.haml +0 -17
- data/lib/perkins/views/repos/repo.haml +0 -64
- 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
|
-
|
21
|
-
|
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
|
-
|
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
|
-
|
18
|
+
- if @.build_status is "stopped"
|
19
|
+
= "#{Perkins.Helpers.round(@.duration)} secs"
|
20
|
+
- else
|
21
|
+
\- running
|
18
22
|
%td
|
19
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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(
|
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.
|
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));
|