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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 96f8f9560cf8a66a733f7755fd374d741191b7f8
4
- data.tar.gz: bbcfa722aefa2285b4f632b98e4109fdb585041c
3
+ metadata.gz: 3352202fbc4460b9f83a50b93f32dce58142b7ad
4
+ data.tar.gz: a2680033dec036183f9fcb767cb4935f17ecad27
5
5
  SHA512:
6
- metadata.gz: 207ba956da9189b584ae3e6362f0f46fe7cb877105d28b4550eb6c071f38aca8f8f326b04e4c7082ce129a719e2b56cb0870ae715e95eb3aee2b7ee0d027eaf0
7
- data.tar.gz: fe5cf08c7d393d8720324a8aeceb80456b0ceda886df6cb1a185ab4b35516fe2cf89bacb0697af78fbe6da45c979320e4569d41f842f8d7c392f9fbe38f1d2e3
6
+ metadata.gz: 8ef0d7d4a741961ce17a76fb5b93e3906f216bbe7bea5dbe2598eead312571e859392e4a54f32414fda1cffb0ffca9a4a3e8283f9eb333baea58529e51d9f86a
7
+ data.tar.gz: b6f445ee78e3e217b9279b7958744281e9d16dcb5cb7e1606c1a1cdc7d6f462afc8c7278a1de4ce751fd929a8372d5fbdd22c19148bc504b64f6790f1e52cfdb
@@ -1,3 +1,7 @@
1
+ ## 3.1.0 (Aug 16, 2018)
2
+
3
+ * Add support for replaying missed messages when using the Salesforce Streaming API (@andreimaxim, @drteeth, @panozzaj)
4
+
1
5
  ## 3.0.1 (Aug 4, 2018)
2
6
 
3
7
  * Fix `NoMethodError` when upserting an existing record (@opti)
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.1'
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
- _See also: [Force.com Streaming API docs](http://www.salesforce.com/us/developer/docs/api_streaming/index.htm)_
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Restforce
4
- VERSION = '3.0.1'
4
+ VERSION = '3.1.0'
5
5
  end
@@ -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.1
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-04 00:00:00.000000000 Z
12
+ date: 2018-08-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: faraday