-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathpavlova.js
143 lines (125 loc) · 3.7 KB
/
pavlova.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Pavlova dispatcher
// Copyright 2016-2017, 2021 Pierre Ynard
// Licensed under GPLv3+
// RX class: this is meant to probe and handle a KiwiSDR instance, but
// the same interface could be implemented by other classes to support
// other types of online radio receivers.
var RX = function(url, freq, timeout) {
this.root = url;
this.redirect = this.root + (freq ? '?f=' + freq : '');
this.timeout = timeout;
this.status = null;
this.dsc = 'Receiver at ' + url;
};
RX.prototype = {
// Whether probing has not been completed yet and is still pending
pending: function() {
return (this.status == null);
},
// The redirection URL if this RX is available, or null otherwise
redirection: function() {
return this.status ? this.redirect : null;
},
// A description string for this RX
description: function() {
return this.dsc;
},
// Probe RX for availability and call cb() when completed
probe: function(cb) {
var xhr = new XMLHttpRequest();
xhr.open('GET', this.root + 'status', true);
xhr.timeout = this.timeout;
var rx = this;
xhr.ontimeout = function(evt) {
rx.status = false;
cb();
};
xhr.onreadystatechange = function(evt) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
var result = xhr.responseText.match(/\n\s*name=\s*([^\n]+?)\s*\n/);
if (result != null)
rx.dsc = result[1];
if (/(^|\s+)status=(inactive|offline|private)\s*\n/.test(xhr.responseText))
rx.status = false;
else {
result = xhr.responseText.match(/\s+users=(\d+)\s+users_max=(\d+)\s+/);
rx.status = (result != null && Number(result[1]) < Number(result[2]));
}
} else
rx.status = false;
cb();
}
};
xhr.send(null);
},
};
// Pavlova dispatcher class: asynchronously probes receivers and
// redirects to the best available one
var Pavlova = function(url, receivers) {
// Parse parameters from URL or passed string, see README.md
var result = url.match(/(?:^|[\/\?#])([^\/\?#]+)(?:\/([^\/\?#]*))?(?:[\?#][^\/]*)?$/);
if (result == null)
return;
var area = result[1];
var freq = result[2] ? result[2] : null;
var timeout = receivers.timeout != null ? receivers.timeout : 0;
if (receivers.area[area] == null)
return;
this.rxs = receivers.area[area].map(function(rx) {
return new RX(rx, freq, timeout);
});
this.redirected = false;
};
Pavlova.prototype = {
// Redirect to the chosen receiver. null url means no receiver could
// be found. Monkey-patch this function to implement alternative
// output.
redirect: function(url, description) {
if (url)
window.location.href = url;
else
alert("No receiver available! Sorry, try again later.");
},
// Called back after each receiver has completed probing, checks
// whether a redirection decision can be made
check_all: function() {
// Shortcut
if (this.redirected)
return;
var failed = this.rxs.every(function(rx) {
var pending = rx.pending();
if (pending)
return false;
var url = rx.redirection();
if (url && ! this.redirected) {
this.redirected = true;
this.redirect(url, rx.description());
}
return (! url);
}, this);
if (failed && ! this.redirected) {
this.redirected = true;
this.redirect(null, null);
}
},
// Launch the probing and redirection process
dispatch: function() {
this.rxs.forEach(function(rx) {
var dispatcher = this;
rx.probe(function() {
dispatcher.check_all();
});
}, this);
},
};
// Static method - this is the only function call you need
Pavlova.handle = function(arg, redirect_cb) {
var dispatcher = new Pavlova(arg ? arg : window.location.href, receivers);
if (dispatcher.rxs == null)
return false;
if (redirect_cb)
dispatcher.redirect = redirect_cb;
dispatcher.dispatch();
return true;
};