restforce 3.0.1 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|