where_is_waldo 0.1.6 → 0.1.8
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/CHANGELOG.md +68 -0
- data/README.md +12 -1
- data/VERSION +1 -1
- data/app/services/where_is_waldo/roster.rb +19 -6
- data/lib/where_is_waldo.rb +17 -3
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 14d08eaaa5203c578066b674469638145e6bd1a095260ba5874a78e7299b2ab3
|
|
4
|
+
data.tar.gz: 7a96f733e8dd421884e0bd798f38b5f5de128faaf1eff0fc7126c2f29b556dd0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5a08ff00777508b95e44767dffc2bb24c0efa7a17a62dddb4386a83a07a222847da426000cc61ec166c177afdf8caec51b4fadd67351db775f96a09579e7d75
|
|
7
|
+
data.tar.gz: b6268730110e341d4f518899ccc57d1bf4f82c39cdb82ff21e6b8ea4beaa1a3739770f4d8d32995d1969174249d61815de13bb8fb1114c793727a86c723e901c
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,74 @@
|
|
|
3
3
|
Notable changes to where_is_waldo. Format loosely follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/).
|
|
5
5
|
|
|
6
|
+
## 0.1.8
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **`WhereIsWaldo.subject_online?`, `.sessions_for_subject`, `.session_status`
|
|
11
|
+
no longer raise `uninitialized constant` when called.** These three methods
|
|
12
|
+
were defined with ActiveSupport's `delegate :method, to: :PresenceService`
|
|
13
|
+
inside a `class << self` block. `delegate` generates the method body via
|
|
14
|
+
`class_eval` with a **string**, and a string-eval'd body's `Module.nesting`
|
|
15
|
+
is just the target class — for the WhereIsWaldo module's singleton class,
|
|
16
|
+
that's `[#<Class:WhereIsWaldo>]`, *without* the enclosing `WhereIsWaldo`.
|
|
17
|
+
Constant lookup for `PresenceService` inside the generated method then
|
|
18
|
+
couldn't walk up to the enclosing module and failed with:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
uninitialized constant #<Class:WhereIsWaldo>::PresenceService (NameError)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Replaced the three `delegate` calls with plain `def` methods matching the
|
|
25
|
+
pattern of the other class methods on the module (`.connect`, `.online`,
|
|
26
|
+
`.online_ids`, `.cleanup`, etc.), which inherit the correct lexical scope
|
|
27
|
+
and resolve `PresenceService` as `WhereIsWaldo::PresenceService`.
|
|
28
|
+
|
|
29
|
+
Waldo's own internal code (`PresenceChannel`, `Roster`) references
|
|
30
|
+
`PresenceService` at its fully-qualified name from correctly-scoped
|
|
31
|
+
modules, so this bug never affected the actual presence flow — only host
|
|
32
|
+
code that calls the top-level convenience wrappers. Affects every
|
|
33
|
+
released version up to and including `0.1.7`.
|
|
34
|
+
|
|
35
|
+
### Tests
|
|
36
|
+
|
|
37
|
+
- Added `spec/where_is_waldo_spec.rb` exercising every top-level convenience
|
|
38
|
+
method at the public boundary — the only place a `class_eval`-scope
|
|
39
|
+
regression in the wrapper can be caught, since internal callers bypass it.
|
|
40
|
+
|
|
41
|
+
## 0.1.7
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **`last_activity` in roster member payloads.** Each member in a
|
|
46
|
+
`roster_snapshot` / `roster_delta` message now carries a `last_activity`
|
|
47
|
+
ISO8601 timestamp — the most recent `subject_active: true` heartbeat time
|
|
48
|
+
across all of the subject's live sessions (web, mobile, whichever). `nil`
|
|
49
|
+
when the subject has no live sessions. Lets hosts derive "seconds idle"
|
|
50
|
+
client-side (`Date.now() - new Date(last_activity)`) and apply their own
|
|
51
|
+
thresholds instead of being tied to the gem's active↔idle boundary.
|
|
52
|
+
|
|
53
|
+
Example: a picker that wants "stay green until 3 minutes without input"
|
|
54
|
+
no longer needs a server-side `activityTimeout` override — it reads
|
|
55
|
+
`member.last_activity` and thresholds against its own clock. The
|
|
56
|
+
server's own active/idle enum still ships in `status` for consumers who
|
|
57
|
+
do want it, and the roster still broadcasts only on transitions (no
|
|
58
|
+
extra bandwidth beyond the added field).
|
|
59
|
+
|
|
60
|
+
Payload shape:
|
|
61
|
+
```json
|
|
62
|
+
{ "id": 7, "status": "idle",
|
|
63
|
+
"devices": { "web": "idle", "mobile": "active" },
|
|
64
|
+
"last_activity": "2026-07-27T15:23:04Z",
|
|
65
|
+
"name": "Alan Smith" }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Both adapters were already tracking `last_activity` per session — this
|
|
69
|
+
release plumbs it through the roster aggregation (max across sessions)
|
|
70
|
+
and normalizes to an ISO string in the broadcast payload, absorbing the
|
|
71
|
+
DB-adapter-returns-string / Redis-adapter-returns-Time mismatch at the
|
|
72
|
+
roster boundary.
|
|
73
|
+
|
|
6
74
|
## 0.1.6
|
|
7
75
|
|
|
8
76
|
### Security
|
data/README.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Real-time presence tracking for Rails + React using ActionCable.
|
|
4
4
|
|
|
5
|
+
[](https://github.com/byscott-io/where_is_waldo/actions/workflows/ci.yml)
|
|
6
|
+
[](https://rubygems.org/gems/where_is_waldo)
|
|
7
|
+
[](https://www.npmjs.com/package/@byscott-io/where-is-waldo)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
5
10
|
## Features
|
|
6
11
|
|
|
7
12
|
- **Presence tracking** - know who's online
|
|
@@ -233,7 +238,8 @@ See `docs/PRESENCE_ROSTER_PLAN.md` for the full mode matrix and tradeoffs.
|
|
|
233
238
|
their live sessions (multiple browser tabs, mobile, etc.):
|
|
234
239
|
|
|
235
240
|
```
|
|
236
|
-
{ id: 7, status: "active", devices: { web: "idle", mobile: "active" }
|
|
241
|
+
{ id: 7, status: "active", devices: { web: "idle", mobile: "active" },
|
|
242
|
+
last_activity: "2026-07-27T15:23:04Z" }
|
|
237
243
|
```
|
|
238
244
|
|
|
239
245
|
- `status` — highest activity across devices (the "active anywhere?" answer):
|
|
@@ -243,6 +249,11 @@ their live sessions (multiple browser tabs, mobile, etc.):
|
|
|
243
249
|
- `offline` — no live sessions
|
|
244
250
|
- `devices[platform]` — that platform's own status (answers "active on
|
|
245
251
|
**mobile**?" vs. "active at all?").
|
|
252
|
+
- `last_activity` — ISO8601 timestamp of the most recent input across **all**
|
|
253
|
+
the subject's live sessions (`null` when offline). The roster only
|
|
254
|
+
broadcasts on `status` transitions, so subtract against your own clock to
|
|
255
|
+
derive seconds-idle and apply client-side thresholds without a server
|
|
256
|
+
`timeout` change: `(Date.now() - new Date(m.last_activity)) / 1000`.
|
|
246
257
|
|
|
247
258
|
#### Configure
|
|
248
259
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.8
|
|
@@ -8,12 +8,18 @@ module WhereIsWaldo
|
|
|
8
8
|
#
|
|
9
9
|
# A member's presence is reported per device AND as an overall roll-up:
|
|
10
10
|
#
|
|
11
|
-
# { id: 7, status: "active",
|
|
11
|
+
# { id: 7, status: "active",
|
|
12
|
+
# devices: { "web" => "active", "mobile" => "idle" },
|
|
13
|
+
# last_activity: "2026-07-27T15:23:04Z" }
|
|
12
14
|
#
|
|
13
15
|
# `devices[platform]` is that platform's own aggregate status (several browser
|
|
14
16
|
# tabs roll up into one "web" status); `status` is the highest level across
|
|
15
17
|
# all platforms — the "is the user active anywhere?" answer, while
|
|
16
|
-
# `devices["mobile"]` answers "is the user active on mobile?".
|
|
18
|
+
# `devices["mobile"]` answers "is the user active on mobile?". `last_activity`
|
|
19
|
+
# is the most recent input timestamp across all of the subject's live sessions
|
|
20
|
+
# (nil when offline), letting the client subtract from its own clock to derive
|
|
21
|
+
# seconds-idle and apply its own thresholds without new broadcasts. Each
|
|
22
|
+
# status is:
|
|
17
23
|
#
|
|
18
24
|
# active - a live session on that device is tab-visible AND working
|
|
19
25
|
# idle - a live session is tab-visible but not actively using
|
|
@@ -182,15 +188,21 @@ module WhereIsWaldo
|
|
|
182
188
|
# Reduce a subject's live sessions to per-device statuses plus an overall
|
|
183
189
|
# roll-up. Sessions are grouped by platform (so several browser tabs form
|
|
184
190
|
# one "web" status); the overall status is the highest across platforms.
|
|
191
|
+
# `last_activity` is the max across all sessions (any platform), so it
|
|
192
|
+
# answers "when did any of this subject's tabs last see input?".
|
|
185
193
|
# @param sessions [Array<Hash>] presence hashes from the adapter
|
|
186
|
-
# @return [Hash] { status
|
|
194
|
+
# @return [Hash] { status:, devices:, last_activity: }
|
|
187
195
|
def aggregate(sessions)
|
|
188
196
|
devices = sessions.group_by { |s| platform(s) }
|
|
189
197
|
.transform_values { |sess| platform_level(sess) }
|
|
190
198
|
overall = devices.values.max_by { |level| RANK[level] } || :offline
|
|
199
|
+
# DB adapter returns last_activity as ISO string, Redis as Time; both sort
|
|
200
|
+
# correctly among themselves. Normalize to ISO string in the payload.
|
|
201
|
+
last = sessions.filter_map { |s| s[:last_activity] }.max
|
|
191
202
|
{
|
|
192
203
|
status: overall.to_s,
|
|
193
|
-
devices: devices.transform_values(&:to_s)
|
|
204
|
+
devices: devices.transform_values(&:to_s),
|
|
205
|
+
last_activity: last.respond_to?(:iso8601) ? last.iso8601 : last
|
|
194
206
|
}
|
|
195
207
|
end
|
|
196
208
|
|
|
@@ -229,12 +241,13 @@ module WhereIsWaldo
|
|
|
229
241
|
data.merge(
|
|
230
242
|
id: id || record&.id,
|
|
231
243
|
status: state[:status],
|
|
232
|
-
devices: state[:devices]
|
|
244
|
+
devices: state[:devices],
|
|
245
|
+
last_activity: state[:last_activity]
|
|
233
246
|
)
|
|
234
247
|
end
|
|
235
248
|
|
|
236
249
|
def offline_state
|
|
237
|
-
{ status: "offline", devices: {} }
|
|
250
|
+
{ status: "offline", devices: {}, last_activity: nil }
|
|
238
251
|
end
|
|
239
252
|
|
|
240
253
|
def find_subject(subject_id)
|
data/lib/where_is_waldo.rb
CHANGED
|
@@ -55,11 +55,25 @@ module WhereIsWaldo
|
|
|
55
55
|
PresenceService.all_online_ids(timeout: timeout)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
# NOTE: don't use ActiveSupport's `delegate` here. `delegate` generates
|
|
59
|
+
# the method via `class_eval` with a STRING, and a string-eval'd body's
|
|
60
|
+
# `Module.nesting` is just the target class — for a `class << self` inside
|
|
61
|
+
# `module WhereIsWaldo`, that's `[#<Class:WhereIsWaldo>]`, WITHOUT
|
|
62
|
+
# `WhereIsWaldo` itself. Constant lookup for `PresenceService` inside the
|
|
63
|
+
# generated method then can't walk up to the enclosing module and raises
|
|
64
|
+
# `uninitialized constant #<Class:WhereIsWaldo>::PresenceService`. A plain
|
|
65
|
+
# `def` inherits the enclosing lexical nesting and resolves correctly.
|
|
66
|
+
def subject_online?(...)
|
|
67
|
+
PresenceService.subject_online?(...)
|
|
68
|
+
end
|
|
59
69
|
|
|
60
|
-
|
|
70
|
+
def sessions_for_subject(...)
|
|
71
|
+
PresenceService.sessions_for_subject(...)
|
|
72
|
+
end
|
|
61
73
|
|
|
62
|
-
|
|
74
|
+
def session_status(...)
|
|
75
|
+
PresenceService.session_status(...)
|
|
76
|
+
end
|
|
63
77
|
|
|
64
78
|
def cleanup(timeout: nil)
|
|
65
79
|
PresenceService.cleanup(timeout: timeout)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: where_is_waldo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Scott Gibson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|