restforce 3.0.1 → 3.1.0
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 +4 -0
- data/README.md +97 -2
- data/lib/restforce/concerns/streaming.rb +60 -1
- data/lib/restforce/version.rb +1 -1
- data/spec/unit/concerns/streaming_spec.rb +112 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3352202fbc4460b9f83a50b93f32dce58142b7ad
|
4
|
+
data.tar.gz: a2680033dec036183f9fcb767cb4935f17ecad27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ef0d7d4a741961ce17a76fb5b93e3906f216bbe7bea5dbe2598eead312571e859392e4a54f32414fda1cffb0ffca9a4a3e8283f9eb333baea58529e51d9f86a
|
7
|
+
data.tar.gz: b6f445ee78e3e217b9279b7958744281e9d16dcb5cb7e1606c1a1cdc7d6f462afc8c7278a1de4ce751fd929a8372d5fbdd22c19148bc504b64f6790f1e52cfdb
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -25,7 +25,7 @@ Features include:
|
|
25
25
|
|
26
26
|
Add this line to your application's Gemfile:
|
27
27
|
|
28
|
-
gem 'restforce', '~> 3.0
|
28
|
+
gem 'restforce', '~> 3.1.0'
|
29
29
|
|
30
30
|
And then execute:
|
31
31
|
|
@@ -547,7 +547,102 @@ end
|
|
547
547
|
Boom, you're now receiving push notifications when Accounts are
|
548
548
|
created/updated.
|
549
549
|
|
550
|
-
|
550
|
+
#### Replaying Events
|
551
|
+
|
552
|
+
Since API version 37.0, Salesforce stores events for 24 hours and they can be
|
553
|
+
replayed if your application experienced some downtime.
|
554
|
+
|
555
|
+
In order to replay past events, all you need to do is specify the last known
|
556
|
+
event ID when subscribing and you will receive all events that happened since
|
557
|
+
that event ID:
|
558
|
+
|
559
|
+
```ruby
|
560
|
+
EM.run {
|
561
|
+
# Subscribe to the PushTopic.
|
562
|
+
client.subscribe 'AllAccounts', replay: 10 do |message|
|
563
|
+
puts message.inspect
|
564
|
+
end
|
565
|
+
}
|
566
|
+
```
|
567
|
+
|
568
|
+
In this specific case you will see events with replay ID 11, 12 and so on.
|
569
|
+
|
570
|
+
There are two magic values for the replay ID accepted by Salesforce:
|
571
|
+
|
572
|
+
* `-2`, for getting all the events that appeared in the last 24 hours
|
573
|
+
* `-1`, for getting only newer events
|
574
|
+
|
575
|
+
**Warning**: Only use a replay ID of a event from the last 24 hours otherwise
|
576
|
+
Salesforce will not send anything, including newer events. If in doubt, use one
|
577
|
+
of the two magic replay IDs mentioned above.
|
578
|
+
|
579
|
+
You might want to store the replay ID in some sort of datastore so you can
|
580
|
+
access it, for example between application restarts. In that case, there is the
|
581
|
+
option of passing a custom replay handler which responds to `[]` and `[]=`.
|
582
|
+
|
583
|
+
Below is a sample replay handler that stores the replay ID for each channel in
|
584
|
+
memory using a Hash, stores a timestamp and has some rudimentary logic that
|
585
|
+
will use one of the magic IDs depending on the value of the timestamp:
|
586
|
+
|
587
|
+
```ruby
|
588
|
+
class SimpleReplayHander
|
589
|
+
|
590
|
+
MAX_AGE = 86_400 # 24 hours
|
591
|
+
|
592
|
+
INIT_REPLAY_ID = -1
|
593
|
+
DEFAULT_REPLAY_ID = -2
|
594
|
+
|
595
|
+
def initialize
|
596
|
+
@channels = {}
|
597
|
+
@last_modified = nil
|
598
|
+
end
|
599
|
+
|
600
|
+
# This method is called during the initial subscribe phase
|
601
|
+
# in order to send the correct replay ID.
|
602
|
+
def [](channel)
|
603
|
+
if @last_modified.nil?
|
604
|
+
puts "[#{channel}] No timestamp defined, sending magic replay ID #{INIT_REPLAY_ID}"
|
605
|
+
|
606
|
+
INIT_REPLAY_ID
|
607
|
+
elsif old_replay_id?
|
608
|
+
puts "[#{channel}] Old timestamp, sending magic replay ID #{DEFAULT_REPLAY_ID}"
|
609
|
+
|
610
|
+
DEFAULT_REPLAY_ID
|
611
|
+
else
|
612
|
+
@channels[channel]
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
def []=(channel, replay_id)
|
617
|
+
puts "[#{channel}] Writing replay ID: #{replay_id}"
|
618
|
+
|
619
|
+
@last_modified = Time.now
|
620
|
+
@channels[channel] = replay_id
|
621
|
+
end
|
622
|
+
|
623
|
+
def old_replay_id?
|
624
|
+
@last_modified.is_a?(Time) && Time.now - @last_modified > MAX_AGE
|
625
|
+
end
|
626
|
+
end
|
627
|
+
```
|
628
|
+
|
629
|
+
In order to use it, simply pass the object as the value of the `replay` option
|
630
|
+
of the subscription:
|
631
|
+
|
632
|
+
```ruby
|
633
|
+
EM.run {
|
634
|
+
# Subscribe to the PushTopic and use the custom replay handler to store any
|
635
|
+
# received replay ID.
|
636
|
+
client.subscribe 'AllAccounts', replay: SimpleReplayHandler.new do |message|
|
637
|
+
puts message.inspect
|
638
|
+
end
|
639
|
+
}
|
640
|
+
```
|
641
|
+
|
642
|
+
_See also_:
|
643
|
+
|
644
|
+
* [Force.com Streaming API docs](http://www.salesforce.com/us/developer/docs/api_streaming/index.htm)
|
645
|
+
* [Message Durability docs](https://developer.salesforce.com/docs/atlas.en-us.api_streaming.meta/api_streaming/using_streaming_api_durability.htm)
|
551
646
|
|
552
647
|
*Note:* Restforce's streaming implementation is known to be compatible with version `0.8.9` of the faye gem.
|
553
648
|
|
@@ -9,7 +9,8 @@ module Restforce
|
|
9
9
|
# block - A block to run when a new message is received.
|
10
10
|
#
|
11
11
|
# Returns a Faye::Subscription
|
12
|
-
def subscribe(channels, &block)
|
12
|
+
def subscribe(channels, options = {}, &block)
|
13
|
+
Array(channels).each { |channel| replay_handlers[channel] = options[:replay] }
|
13
14
|
faye.subscribe Array(channels).map { |channel| "/topic/#{channel}" }, &block
|
14
15
|
end
|
15
16
|
|
@@ -32,6 +33,64 @@ module Restforce
|
|
32
33
|
client.bind 'transport:up' do
|
33
34
|
Restforce.log "[COMETD UP]"
|
34
35
|
end
|
36
|
+
|
37
|
+
client.add_extension ReplayExtension.new(replay_handlers)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def replay_handlers
|
42
|
+
@_replay_handlers ||= {}
|
43
|
+
end
|
44
|
+
|
45
|
+
class ReplayExtension
|
46
|
+
def initialize(replay_handlers)
|
47
|
+
@replay_handlers = replay_handlers
|
48
|
+
end
|
49
|
+
|
50
|
+
def incoming(message, callback)
|
51
|
+
callback.call(message).tap do
|
52
|
+
channel = message.fetch('channel').gsub('/topic/', '')
|
53
|
+
replay_id = message.fetch('data', {}).fetch('event', {})['replayId']
|
54
|
+
|
55
|
+
handler = @replay_handlers[channel]
|
56
|
+
if !replay_id.nil? && !handler.nil? && handler.respond_to?(:[]=)
|
57
|
+
# remember the last replay_id for this channel
|
58
|
+
handler[channel] = replay_id
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def outgoing(message, callback)
|
64
|
+
# Leave non-subscribe messages alone
|
65
|
+
unless message['channel'] == '/meta/subscribe'
|
66
|
+
return callback.call(message)
|
67
|
+
end
|
68
|
+
|
69
|
+
channel = message['subscription'].gsub('/topic/', '')
|
70
|
+
|
71
|
+
# Set the replay value for the channel
|
72
|
+
message['ext'] ||= {}
|
73
|
+
message['ext']['replay'] = {
|
74
|
+
"/topic/#{channel}" => replay_id(channel)
|
75
|
+
}
|
76
|
+
|
77
|
+
# Carry on and send the message to the server
|
78
|
+
callback.call message
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def replay_id(channel)
|
84
|
+
handler = @replay_handlers[channel]
|
85
|
+
if handler.is_a?(Integer)
|
86
|
+
handler # treat it as a scalar
|
87
|
+
elsif handler.respond_to?(:[])
|
88
|
+
# Ask for the latest replayId for this channel
|
89
|
+
handler[channel]
|
90
|
+
else
|
91
|
+
# Just pass it along
|
92
|
+
handler
|
93
|
+
end
|
35
94
|
end
|
36
95
|
end
|
37
96
|
end
|
data/lib/restforce/version.rb
CHANGED
@@ -17,6 +17,33 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
17
17
|
|
18
18
|
client.subscribe(channels, &subscribe_block)
|
19
19
|
end
|
20
|
+
|
21
|
+
context "replay_handlers" do
|
22
|
+
before {
|
23
|
+
faye_double.should_receive(:subscribe).at_least(1)
|
24
|
+
client.stub faye: faye_double
|
25
|
+
}
|
26
|
+
|
27
|
+
it 'registers nil handlers when no replay option is given' do
|
28
|
+
client.subscribe(channels, &subscribe_block)
|
29
|
+
client.replay_handlers.should eq('channel1' => nil, 'channel2' => nil)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'registers a replay_handler for each channel given' do
|
33
|
+
client.subscribe(channels, replay: -2, &subscribe_block)
|
34
|
+
client.replay_handlers.should eq('channel1' => -2, 'channel2' => -2)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'replaces earlier handlers in subsequent calls' do
|
38
|
+
client.subscribe(%w[channel1 channel2], replay: 2, &subscribe_block)
|
39
|
+
client.subscribe(%w[channel2 channel3], replay: 3, &subscribe_block)
|
40
|
+
client.replay_handlers.should eq(
|
41
|
+
'channel1' => 2,
|
42
|
+
'channel2' => 3,
|
43
|
+
'channel3' => 3
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
20
47
|
end
|
21
48
|
|
22
49
|
describe '.faye' do
|
@@ -42,6 +69,8 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
42
69
|
faye_double.should_receive(:set_header).with('Authorization', 'OAuth secret2')
|
43
70
|
faye_double.should_receive(:bind).with('transport:down').and_yield
|
44
71
|
faye_double.should_receive(:bind).with('transport:up').and_yield
|
72
|
+
faye_double.should_receive(:add_extension).with \
|
73
|
+
kind_of(Restforce::Concerns::Streaming::ReplayExtension)
|
45
74
|
subject
|
46
75
|
end
|
47
76
|
end
|
@@ -52,4 +81,87 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
52
81
|
end
|
53
82
|
end
|
54
83
|
end
|
84
|
+
|
85
|
+
describe Restforce::Concerns::Streaming::ReplayExtension do
|
86
|
+
let(:handlers) { {} }
|
87
|
+
let(:extension) { Restforce::Concerns::Streaming::ReplayExtension.new(handlers) }
|
88
|
+
|
89
|
+
it 'sends nil without a specified handler' do
|
90
|
+
output = subscribe(extension, to: "channel1")
|
91
|
+
read_replay(output).should eq('/topic/channel1' => nil)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'with a scalar replay id' do
|
95
|
+
handlers['channel1'] = -2
|
96
|
+
output = subscribe(extension, to: "channel1")
|
97
|
+
read_replay(output).should eq('/topic/channel1' => -2)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'with a hash' do
|
101
|
+
hash_handler = { 'channel1' => -1, 'channel2' => -2 }
|
102
|
+
|
103
|
+
handlers['channel1'] = hash_handler
|
104
|
+
handlers['channel2'] = hash_handler
|
105
|
+
|
106
|
+
output = subscribe(extension, to: "channel1")
|
107
|
+
read_replay(output).should eq('/topic/channel1' => -1)
|
108
|
+
|
109
|
+
output = subscribe(extension, to: "channel2")
|
110
|
+
read_replay(output).should eq('/topic/channel2' => -2)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'with an object' do
|
114
|
+
custom_handler = double('custom_handler')
|
115
|
+
custom_handler.should_receive(:[]).and_return(123)
|
116
|
+
handlers['channel1'] = custom_handler
|
117
|
+
|
118
|
+
output = subscribe(extension, to: "channel1")
|
119
|
+
read_replay(output).should eq('/topic/channel1' => 123)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'remembers the last replayId' do
|
123
|
+
handler = { 'channel1' => 41 }
|
124
|
+
handlers['channel1'] = handler
|
125
|
+
message = {
|
126
|
+
'channel' => '/topic/channel1',
|
127
|
+
'data' => {
|
128
|
+
'event' => { 'replayId' => 42 }
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
extension.incoming(message, ->(m) {})
|
133
|
+
handler.should eq('channel1' => 42)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'when an incoming message has no replayId' do
|
137
|
+
handler = { 'channel1' => 41 }
|
138
|
+
handlers['channel1'] = handler
|
139
|
+
|
140
|
+
message = {
|
141
|
+
'channel' => '/topic/channel1',
|
142
|
+
'data' => {}
|
143
|
+
}
|
144
|
+
|
145
|
+
extension.incoming(message, ->(m) {})
|
146
|
+
handler.should eq('channel1' => 41)
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def subscribe(extension, options = {})
|
152
|
+
output = nil
|
153
|
+
message = {
|
154
|
+
'channel' => '/meta/subscribe',
|
155
|
+
'subscription' => "/topic/#{options[:to]}"
|
156
|
+
}
|
157
|
+
extension.outgoing(message, ->(m) {
|
158
|
+
output = m
|
159
|
+
})
|
160
|
+
output
|
161
|
+
end
|
162
|
+
|
163
|
+
def read_replay(message)
|
164
|
+
message.fetch('ext', {})['replay']
|
165
|
+
end
|
166
|
+
end
|
55
167
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restforce
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric J. Holmes
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-08-
|
12
|
+
date: 2018-08-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: faraday
|