dexiecable 2.0.1 → 2.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05e9525058173a0392999802c6243cac3eb5c0406a28cb405963a9069d20b098
4
- data.tar.gz: 3394af2d34854137a4032dd672bb0e1ebc6073e907872b168a5e31e9ffea1daf
3
+ metadata.gz: c5284c177e5517bff845edf2e5317d1ab9c676deafd605a662b3dea1b7363b6a
4
+ data.tar.gz: 94f3e811e2bca16d05967c0c0dbf242bcae00b880212ad0dba7d1ee44f8263c9
5
5
  SHA512:
6
- metadata.gz: 936fa88cb0cd6dc55665e9214d54064eb914863552bf8b84e58e1ca4d616751863f9572740ceb00c7b09503ed9996fe9ec52640a65279b2bc5249498d3671684
7
- data.tar.gz: 1b9a73d2e21d3ac4e5138feeb1c13c958f9b4eccffeb7968c0f7fbc28a45cd24b8dff7f6ab4e2f976730536dcee3fc30758cae5aaa04c4bd8dd3940ec83b5ef6
6
+ metadata.gz: 4999890a88da548b86e19843b40a0221654eb2a144ec52f954c7723cb652c287d7884fd34e0703d5895131e3b1213ee840a427f31defa91ff21738a38133514a
7
+ data.tar.gz: 67bb97e60d5d9fc7416e239e0695b69ff00a4930df8d97d8380da29bd51686af9f2d70f04bd69d92322e61ea042a71704ce4e98bbc35d14ee5f0a33d13343def
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # DexieCable
2
2
 
3
3
  > [!NOTE]
4
- > DexieCable is NOT meant to be a local-first solution. It has no automatic capability to sync updates back to the server. For now, think of it as an alternative to Turbo Streams built with component frameworks (Vue, React, Svelte, etc) in mind.
4
+ > By itself, DexieCable is NOT a local-first solution. It has no automatic capability to push client-side changes back to the server.
5
5
  >
6
- > Full synchronization utilizing event streams will arrive in DexieCable 3.0.
6
+ > An addon providing full synchronization based on event streams is currently in development. But for now, if you need full synchronization, you'll have to roll your own.
7
7
 
8
8
  DexieCable gives your ActionCable channel a query DSL that mirrors the Dexie.js API, letting you push database mutations from the server to the client in real time. It also gives you a [`syncs_to_dexie`](#syncs_to_dexie-automatic-model-streaming) ActiveRecord macro for automatic change syncing.
9
9
 
@@ -26,6 +26,40 @@ class Notification < ApplicationRecord
26
26
  end
27
27
  ```
28
28
 
29
+ ## What's new in 2.0
30
+
31
+ DexieCable 2.0 is a mixin. Create your own `DexieChannel` and `include DexieCable`:
32
+
33
+ ```ruby
34
+ class DexieChannel < ApplicationCable::Channel
35
+ include DexieCable
36
+ end
37
+ ```
38
+
39
+ On the client, subscribe to that channel and add streams as needed. `addStream` accepts a signed token or a plain string, and returns a function that removes the stream:
40
+
41
+ ```js
42
+ const subscription = subscribe(db);
43
+ const unsubscribe = subscription.addStream(userStreamToken);
44
+ ```
45
+
46
+ You can then use the new `subscribed_to` hook to push initial data before any live mutation arrives. The first argument is the record the token was issued for, or the plain stream name for a public stream:
47
+
48
+ ```ruby
49
+ class DexieChannel < ApplicationCable::Channel
50
+ include DexieCable
51
+
52
+ def subscribed_to(record, params)
53
+ case record
54
+ when "feed"
55
+ table(record).bulkAdd(Announcement.for_stream(record).map(&:as_json_for_dexie))
56
+ when User
57
+ table("notifications").bulkAdd(record.notifications.map(&:as_json_for_dexie))
58
+ end
59
+ end
60
+ end
61
+ ```
62
+
29
63
  ## Installation
30
64
 
31
65
  ### Ruby gem
@@ -101,6 +135,8 @@ Tokens never expire by default. Pass `expires_in:` or `expires_at:` to limit a t
101
135
  DexieChannel.stream_token_for(current_user, expires_in: 1.day)
102
136
  ```
103
137
 
138
+ An expired token, or one whose record no longer exists, is rejected. Only plain strings are treated as public stream names.
139
+
104
140
  Send that token to the client (render it in a view, return it from an endpoint, etc.) and add it to the subscription:
105
141
 
106
142
  ```js
@@ -228,6 +264,9 @@ Add to any ActiveRecord model. Optionally provide the broadcast target.
228
264
 
229
265
  ```ruby
230
266
  class Message < ApplicationRecord
267
+ belongs_to :conversation
268
+ belongs_to :receiver
269
+
231
270
  # Calls send(:receiver), then broadcasts: DexieChannel.broadcast_to(receiver, ...)
232
271
  syncs_to_dexie via: :receiver
233
272
 
@@ -72,29 +72,35 @@ module DexieCable
72
72
  end
73
73
 
74
74
  def add_stream(data)
75
- record = resolve_subscribe_target(data["stream"])
75
+ token = data["stream"].to_s
76
76
  params = data.except("action", "stream").with_indifferent_access
77
77
 
78
- if record
78
+ if signed_token?(token)
79
+ # A token that no longer resolves (expired, revoked, deleted record) is
80
+ # rejected instead of being treated as a public stream name.
81
+ record = resolve_subscribe_target(token)
82
+ return unless record
83
+
79
84
  stream_for record
80
85
  subscribed_to(record, params)
81
86
  else
82
- name = data["stream"].to_s
83
- return if name.blank?
87
+ return if token.blank?
84
88
 
85
- stream_from public_stream_name(name)
86
- subscribed_to(name, params)
89
+ stream_from public_stream_name(token)
90
+ subscribed_to(token, params)
87
91
  end
88
92
  end
89
93
 
90
94
  def remove_stream(data)
91
- record = resolve_subscribe_target(data["stream"])
95
+ token = data["stream"].to_s
96
+
97
+ if signed_token?(token)
98
+ record = resolve_subscribe_target(token)
99
+ return unless record
92
100
 
93
- if record
94
101
  stop_stream_from self.class.broadcasting_for(record)
95
102
  else
96
- name = data["stream"].to_s
97
- stop_stream_from public_stream_name(name) if name.present?
103
+ stop_stream_from public_stream_name(token) if token.present?
98
104
  end
99
105
  end
100
106
 
@@ -110,6 +116,15 @@ module DexieCable
110
116
 
111
117
  private
112
118
 
119
+ # Distinguishes a signed stream token from a public stream name. A signed
120
+ # token keeps a valid signature even after it expires, which is what lets us
121
+ # reject expired tokens instead of falling back to a public stream.
122
+ def signed_token?(token)
123
+ return false if token.blank? || SignedGlobalID.verifier.nil?
124
+
125
+ SignedGlobalID.verifier.valid_message?(token)
126
+ end
127
+
113
128
  def public_stream_name(name)
114
129
  self.class.broadcasting_for("#{PUBLIC_STREAM_PREFIX}#{name}")
115
130
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DexieCable
4
- VERSION = "2.0.1"
4
+ VERSION = "2.0.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dexiecable
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Buhrmester