node-red-zenbus 1.0.13 → 1.0.14

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 (2) hide show
  1. package/package.json +2 -2
  2. package/zenbus-next-bus.js +7 -110
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-zenbus",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "Real-time next bus ETA from Zenbus networks via direct API (protobuf)",
5
5
  "type": "commonjs",
6
6
  "main": "zenbus-next-bus.js",
@@ -46,6 +46,6 @@
46
46
  "node": ">=18"
47
47
  },
48
48
  "dependencies": {
49
- "protobufjs": "^8.0.1"
49
+ "zenbus": "^1.0.0"
50
50
  }
51
51
  }
@@ -1,99 +1,4 @@
1
- var protobuf = require('protobufjs');
2
-
3
- var BASE = 'https://zenbus.net';
4
-
5
- function fetchProto(url, Type) {
6
- return fetch(url)
7
- .then(function (r) { return r.arrayBuffer(); })
8
- .then(function (ab) { return Type.decode(Buffer.from(ab)); });
9
- }
10
-
11
- function secsToHHMM(secs) {
12
- var s = ((secs % 86400) + 86400) % 86400;
13
- var h = Math.floor(s / 3600);
14
- var m = Math.floor((s % 3600) / 60);
15
- return h + 'h' + String(m).padStart(2, '0');
16
- }
17
-
18
- function createClient(opts) {
19
- var alias = opts.alias, itinerary = opts.itinerary, stop = opts.stop;
20
- return fetch(BASE + '/poll/cdn/zenbus.proto')
21
- .then(function (r) { return r.text(); })
22
- .then(function (protoText) {
23
- var root = protobuf.parse(protoText).root;
24
- var StaticMessage = root.lookupType('zenbus_realtime.StaticMessage');
25
- var LiveMessage = root.lookupType('zenbus_realtime.LiveMessage');
26
-
27
- return fetchProto(BASE + '/publicapp/static-data?alias=' + alias, StaticMessage)
28
- .then(function (staticData) {
29
- var shape = (staticData.shape || []).find(function (s) { return s.itineraryId && s.itineraryId.toString() === itinerary; });
30
- var stopAnchor = shape && (shape.anchor || []).find(function (a) { return a.stopId && a.stopId.toString() === stop; });
31
- var stopIndex = stopAnchor ? (stopAnchor.stopIndexInItinerary != null ? stopAnchor.stopIndexInItinerary : -1) : -1;
32
- var stopDistanceM = stopAnchor ? (stopAnchor.distanceTravelled || 0) : 0;
33
- var stopEntry = (staticData.stop || []).find(function (s) { return s.stopId && s.stopId.toString() === stop; });
34
- var stopName = stopEntry ? stopEntry.name : 'Unknown';
35
- var itin = (staticData.itinerary || []).find(function (i) { return i.itineraryId && i.itineraryId.toString() === itinerary; });
36
- var lineEntry = (staticData.line || []).find(function (l) { return itin && l.lineId && l.lineId.toString() === (itin.lineId && itin.lineId.toString()); });
37
- var lineCode = lineEntry ? lineEntry.code : 'Unknown';
38
- var pollUrl = BASE + '/publicapp/poll?alias=' + alias + '&itinerary=' + itinerary;
39
-
40
- return {
41
- stopName: stopName,
42
- lineCode: lineCode,
43
- poll: function () {
44
- return fetchProto(pollUrl, LiveMessage).then(function (liveData) {
45
- var now = new Date();
46
- var midnightUtcSecs = (liveData.timetable && liveData.timetable[0] && liveData.timetable[0].midnight && liveData.timetable[0].midnight.toNumber)
47
- ? liveData.timetable[0].midnight.toNumber()
48
- : Math.floor(new Date(now).setHours(0, 0, 0, 0) / 1000);
49
- var nowSecs = Math.floor(now.getTime() / 1000) - midnightUtcSecs;
50
-
51
- var allColumns = (liveData.tripColumn || []).slice();
52
- (liveData.timetable || []).forEach(function (tt) {
53
- (tt.column || []).forEach(function (col) { allColumns.push(col); });
54
- });
55
-
56
- var candidates = [];
57
- allColumns.forEach(function (tc) {
58
- var est = (tc.estimactual || []).find(function (s) { return s.stopIndexInItinerary === stopIndex; });
59
- var etaSecs = est ? (est.arrival || est.departure || 0) : 0;
60
- if (!etaSecs || etaSecs <= nowSecs) return;
61
-
62
- var vehicleDist = tc.distanceTravelled || 0;
63
- var hasStarted = tc.previousIndexInItinerary >= 0 && tc.pos && tc.pos.length > 0;
64
- if (hasStarted && vehicleDist > stopDistanceM) return;
65
-
66
- var remainingDist = hasStarted ? Math.max(0, stopDistanceM - vehicleDist) : stopDistanceM;
67
- var aimed = (tc.aimed || []).find(function (s) { return s.stopIndexInItinerary === stopIndex; });
68
- var schedSecs = aimed ? (aimed.arrival || aimed.departure || aimed.arriparture || 0) : 0;
69
-
70
- candidates.push({
71
- etaMinutes: Math.round((etaSecs - nowSecs) / 60),
72
- distanceM: Math.round(remainingDist),
73
- estimatedArrival: secsToHHMM(etaSecs),
74
- scheduledTime: schedSecs ? secsToHHMM(schedSecs) : null,
75
- isLive: hasStarted
76
- });
77
- });
78
-
79
- candidates.sort(function (a, b) { return a.etaMinutes - b.etaMinutes; });
80
- var best = candidates[0] || null;
81
- var second = candidates.find(function (c, i) { return i > 0 && c.isLive; }) || null;
82
-
83
- return {
84
- stop: stopName,
85
- line: lineCode,
86
- timestamp: now.toISOString(),
87
- now: secsToHHMM(nowSecs),
88
- next: best,
89
- secondBus: second
90
- };
91
- });
92
- }
93
- };
94
- });
95
- });
96
- }
1
+ var zenbusReady = import('zenbus/core');
97
2
 
98
3
  module.exports = function (RED) {
99
4
 
@@ -107,10 +12,12 @@ module.exports = function (RED) {
107
12
 
108
13
  node.status({ fill: 'yellow', shape: 'ring', text: 'initializing...' });
109
14
 
110
- createClient({
111
- alias: config.alias,
112
- itinerary: config.itinerary,
113
- stop: config.stop
15
+ zenbusReady.then(function (mod) {
16
+ return mod.createClient({
17
+ alias: config.alias,
18
+ itinerary: config.itinerary,
19
+ stop: config.stop
20
+ });
114
21
  }).then(function (c) {
115
22
  if (closing) return;
116
23
  client = c;
@@ -148,16 +55,6 @@ module.exports = function (RED) {
148
55
  });
149
56
  }
150
57
 
151
- node.on('input', function (msg, send, done) {
152
- send = send || function () { node.send.apply(node, arguments); };
153
- done = done || function (err) { if (err) node.error(err, msg); };
154
- if (!client) { done(new Error('Client not initialized')); return; }
155
- client.poll().then(function (data) {
156
- send({ payload: data });
157
- done();
158
- }).catch(function (e) { done(e); });
159
- });
160
-
161
58
  node.on('close', function (removed, done) {
162
59
  closing = true;
163
60
  if (timer) { clearTimeout(timer); timer = null; }