opentok 0.1.3 → 2.2.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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -2
  3. data/.travis.yml +6 -0
  4. data/.yardopts +1 -0
  5. data/CONTRIBUTING.md +47 -0
  6. data/DEVELOPING.md +91 -0
  7. data/LICENSE +19 -5
  8. data/README.md +170 -53
  9. data/Rakefile +10 -5
  10. data/doc/OpenTok.html +411 -0
  11. data/doc/OpenTok/Archive.html +1320 -0
  12. data/doc/OpenTok/ArchiveList.html +216 -0
  13. data/doc/OpenTok/Archives.html +1028 -0
  14. data/doc/OpenTok/Client.html +695 -0
  15. data/doc/OpenTok/OpenTok.html +1046 -0
  16. data/doc/OpenTok/OpenTokArchiveError.html +142 -0
  17. data/doc/OpenTok/OpenTokAuthenticationError.html +143 -0
  18. data/doc/OpenTok/OpenTokError.html +138 -0
  19. data/doc/OpenTok/Session.html +665 -0
  20. data/doc/OpenTok/TokenGenerator.html +204 -0
  21. data/doc/OpenTok/TokenGenerator/ClassMethods.html +187 -0
  22. data/doc/README.md +15 -0
  23. data/doc/_index.html +182 -0
  24. data/doc/class_list.html +54 -0
  25. data/doc/css/common.css +1 -0
  26. data/doc/css/full_list.css +57 -0
  27. data/doc/css/style.css +339 -0
  28. data/doc/file.README.html +87 -0
  29. data/doc/file_list.html +56 -0
  30. data/doc/frames.html +26 -0
  31. data/doc/index.html +87 -0
  32. data/doc/js/app.js +219 -0
  33. data/doc/js/full_list.js +178 -0
  34. data/doc/js/jquery.js +4 -0
  35. data/doc/method_list.html +227 -0
  36. data/doc/top-level-namespace.html +112 -0
  37. data/lib/opentok.rb +3 -14
  38. data/lib/opentok/archive.rb +92 -0
  39. data/lib/opentok/archive_list.rb +17 -0
  40. data/lib/opentok/archives.rb +120 -0
  41. data/lib/opentok/client.rb +125 -0
  42. data/lib/opentok/constants.rb +5 -0
  43. data/lib/opentok/exceptions.rb +10 -0
  44. data/lib/opentok/opentok.rb +174 -0
  45. data/lib/opentok/session.rb +76 -0
  46. data/lib/opentok/token_generator.rb +101 -0
  47. data/lib/opentok/version.rb +4 -0
  48. data/opentok.gemspec +29 -22
  49. data/sample/Archiving/Gemfile +4 -0
  50. data/sample/Archiving/README.md +212 -0
  51. data/sample/Archiving/archiving_sample.rb +80 -0
  52. data/sample/Archiving/public/css/sample.css +22 -0
  53. data/sample/Archiving/public/img/archiving-off.png +0 -0
  54. data/sample/Archiving/public/img/archiving-on-idle.png +0 -0
  55. data/sample/Archiving/public/img/archiving-on-message.png +0 -0
  56. data/sample/Archiving/public/js/host.js +37 -0
  57. data/sample/Archiving/public/js/participant.js +13 -0
  58. data/sample/Archiving/views/history.erb +65 -0
  59. data/sample/Archiving/views/host.erb +69 -0
  60. data/sample/Archiving/views/index.erb +48 -0
  61. data/sample/Archiving/views/layout.erb +29 -0
  62. data/sample/Archiving/views/participant.erb +55 -0
  63. data/sample/HelloWorld/Gemfile +4 -0
  64. data/sample/HelloWorld/README.md +123 -0
  65. data/sample/HelloWorld/hello_world.rb +27 -0
  66. data/sample/HelloWorld/public/js/helloworld.js +32 -0
  67. data/sample/HelloWorld/views/index.erb +21 -0
  68. data/spec/cassettes/OpenTok_Archives/should_create_archives.yml +48 -0
  69. data/spec/cassettes/OpenTok_Archives/should_create_named_archives.yml +49 -0
  70. data/spec/cassettes/OpenTok_Archives/should_delete_an_archive_by_id.yml +32 -0
  71. data/spec/cassettes/OpenTok_Archives/should_find_archives_by_id.yml +46 -0
  72. data/spec/cassettes/OpenTok_Archives/should_stop_archives.yml +48 -0
  73. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_all_archives.yml +104 -0
  74. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_archives_with_an_offset.yml +71 -0
  75. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_count_number_of_archives.yml +60 -0
  76. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_part_of_the_archives_when_using_offset_and_count.yml +82 -0
  77. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_default_sessions.yml +39 -0
  78. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_relayed_media_sessions.yml +39 -0
  79. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_relayed_media_sessions_with_a_location_hint.yml +39 -0
  80. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_routed_media_sessions.yml +39 -0
  81. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_routed_media_sessions_for_invalid_media_modes.yml +39 -0
  82. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_routed_media_sessions_with_a_location_hint.yml +39 -0
  83. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_sessions_with_a_location_hint.yml +39 -0
  84. data/spec/matchers/token.rb +48 -0
  85. data/spec/opentok/archives_spec.rb +91 -0
  86. data/spec/opentok/opentok_spec.rb +144 -0
  87. data/spec/opentok/session_spec.rb +71 -0
  88. data/spec/shared/opentok_generates_tokens.rb +62 -0
  89. data/spec/shared/session_generates_tokens.rb +63 -0
  90. data/spec/spec_helper.rb +6 -7
  91. metadata +197 -59
  92. data/.rspec +0 -3
  93. data/CHANGES +0 -33
  94. data/doc/reference.md +0 -122
  95. data/lib/open_tok/archive.rb +0 -53
  96. data/lib/open_tok/archive_timeline_event.rb +0 -22
  97. data/lib/open_tok/archive_video_resource.rb +0 -28
  98. data/lib/open_tok/exception.rb +0 -50
  99. data/lib/open_tok/open_tok_sdk.rb +0 -198
  100. data/lib/open_tok/request.rb +0 -63
  101. data/lib/open_tok/role_constants.rb +0 -18
  102. data/lib/open_tok/session.rb +0 -25
  103. data/lib/open_tok/session_property_constants.rb +0 -30
  104. data/lib/open_tok/utils.rb +0 -10
  105. data/lib/open_tok/version.rb +0 -5
  106. data/sample/sample.rb +0 -26
  107. data/spec/cassettes/archives.yml +0 -83
  108. data/spec/cassettes/deleteArchive.yml +0 -91
  109. data/spec/cassettes/invalidSession.yml +0 -41
  110. data/spec/cassettes/session.yml +0 -46
  111. data/spec/cassettes/stitchArchive.yml +0 -42
  112. data/spec/opentok_exception_spec.rb +0 -38
  113. data/spec/opentok_spec.rb +0 -135
@@ -0,0 +1,76 @@
1
+ require "base64"
2
+ require "opentok/token_generator"
3
+
4
+ module OpenTok
5
+
6
+ # Represents an OpenTok session.
7
+ #
8
+ # Use the OpenTok.createSession() method to create an OpenTok session. Use the
9
+ # session_id property of the Session object to get the session ID.
10
+ #
11
+ # @attr_reader [String] session_id The session ID.
12
+ # @attr_reader [String] api_secret @private The OpenTok API secret.
13
+ # @attr_reader [String] api_key @private The OpenTok API key.
14
+ # @attr_reader [String] media_mode Set to :routed if the session uses the OpenTok Media Router
15
+ # or to :relayed if the session attempts to transmit streams directly between clients.
16
+ #
17
+ # @attr_reader [String] location The location hint IP address. See the OpenTok.createSession()
18
+ # method.
19
+ #
20
+ # @!method generate_token(options)
21
+ # Generates a token.
22
+ #
23
+ # @param [Hash] options
24
+ # @option options [String] :role The role for the token. Set this to one of the following
25
+ # values:
26
+ # * <code>:subscriber</code> -- A subscriber can only subscribe to streams.
27
+ #
28
+ # * <code>:publisher</code> -- A publisher can publish streams, subscribe to
29
+ # streams, and signal. (This is the default value if you do not specify a role.)
30
+ #
31
+ # * <code>:moderator</code> -- In addition to the privileges granted to a
32
+ # publisher, in clients using the OpenTok.js 2.2 library, a moderator can call the
33
+ # <code>forceUnpublish()</code> and <code>forceDisconnect()</code> method of the
34
+ # Session object.
35
+ # @option options [integer] :expire_time The expiration time, in seconds since the UNIX epoch.
36
+ # Pass in 0 to use the default expiration time of 24 hours after the token creation time.
37
+ # The maximum expiration time is 30 days after the creation time.
38
+ # @option options [String] :data A string containing connection metadata describing the
39
+ # end-user. For example, you can pass the user ID, name, or other data describing the
40
+ # end-user. The length of the string is limited to 1000 characters. This data cannot be
41
+ # updated once it is set.
42
+ # @return [String] The token string.
43
+ class Session
44
+
45
+ include TokenGenerator
46
+ generates_tokens({
47
+ :api_key => ->(instance) { instance.api_key },
48
+ :api_secret => ->(instance) { instance.api_secret },
49
+ :session_id => ->(instance) { instance.session_id }
50
+ })
51
+
52
+ attr_reader :session_id, :media_mode, :location, :api_key, :api_secret
53
+
54
+ # @private
55
+ # this implementation doesn't completely understand the format of a Session ID
56
+ # that is intentional, that is too much responsibility.
57
+ def self.belongs_to_api_key?(session_id, api_key)
58
+ encoded = session_id[2..session_id.length]
59
+ .gsub('-', '+')
60
+ .gsub('_', '/')
61
+ decoded = Base64.decode64(encoded)
62
+ decoded.include? api_key
63
+ end
64
+
65
+ # @private
66
+ def initialize(api_key, api_secret, session_id, opts={})
67
+ @api_key, @api_secret, @session_id = api_key, api_secret, session_id
68
+ @media_mode, @location = opts.fetch(:media_mode, :routed), opts[:location]
69
+ end
70
+
71
+ # @private
72
+ def to_s
73
+ @session_id
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,101 @@
1
+ require "opentok/constants"
2
+ require "opentok/session"
3
+
4
+ require "base64"
5
+ require "addressable/uri"
6
+ require "digest/hmac"
7
+ require "active_support/time"
8
+
9
+ module OpenTok
10
+ # @private
11
+ module TokenGenerator
12
+ # this works when using include TokenGenerator
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+ module ClassMethods
17
+
18
+ # @private arguments the method we should generate will need (in order):
19
+ # * api_key (required - if no lambda assigned, part of method sig)
20
+ # * api_secret (required - if no lambda assigned, part of method sig)
21
+ # * session_id (required - if no lambda assigned, part of method sig)
22
+ # * token_opts (optional - part of method sig)
23
+ #
24
+ # arg_lambdas is a hash of keys which are the above args and values are lambdas that all have the
25
+ # signature ->(instance)
26
+ def generates_tokens(arg_lambdas={})
27
+ @arg_lambdas = arg_lambdas
28
+ define_method(:generate_token) do |*args|
29
+ # puts "generate_something is being called on #{self}. set up with #{method_opts.inspect}"
30
+ dynamic_args = [ :api_key, :api_secret, :session_id, :token_opts ].map do |arg|
31
+ self.class.arg_lambdas[arg].call(self) if self.class.arg_lambdas[arg]
32
+ end
33
+ dynamic_args.compact!
34
+ args = args.first(4-dynamic_args.length)
35
+ self.class.generate_token.call(*dynamic_args, *args)
36
+ end
37
+ end
38
+
39
+ # @private For internal use by the SDK.
40
+ def arg_lambdas
41
+ @arg_lambdas
42
+ end
43
+
44
+ # Generates a token
45
+ def generate_token
46
+ TokenGenerator::GENERATE_TOKEN_LAMBDA
47
+ end
48
+
49
+ end
50
+
51
+ # @private TODO: this probably doesn't need to be a constant anyone can read
52
+ GENERATE_TOKEN_LAMBDA = ->(api_key, api_secret, session_id, opts = {}) do
53
+ # normalize required data params
54
+ role = opts.fetch(:role, :publisher)
55
+ unless ROLES.has_key? role
56
+ raise "'#{role}' is not a recognized role"
57
+ end
58
+ unless Session.belongs_to_api_key? session_id.to_s, api_key
59
+ raise "Cannot generate token for a session_id that doesn't belong to api_key: #{api_key}"
60
+ end
61
+
62
+ # minimum data params
63
+ data_params = {
64
+ :role => role,
65
+ :session_id => session_id,
66
+ :create_time => Time.now.to_i,
67
+ :nonce => Random.rand
68
+ }
69
+
70
+ # normalize and add additional data params
71
+ unless (expire_time = opts[:expire_time]).nil?
72
+ unless expire_time.between?(Time.now, Time.now + 30.days)
73
+ raise "Expire time must be within the next 30 days"
74
+ end
75
+ data_params[:expire_time] = expire_time.to_i
76
+ end
77
+
78
+ unless opts[:data].nil?
79
+ unless (data = opts[:data].to_s).length < 1000
80
+ raise "Connection data must be less than 1000 characters"
81
+ end
82
+ data_params[:connection_data] = data
83
+ end
84
+
85
+ data_string = Addressable::URI.form_encode data_params
86
+ meta_string = Addressable::URI.form_encode({
87
+ :partner_id => api_key,
88
+ :sig => Digest::HMAC.hexdigest(data_string, api_secret, Digest::SHA1)
89
+ })
90
+
91
+ TOKEN_SENTINEL + Base64.strict_encode64(meta_string + ":" + data_string)
92
+ end
93
+
94
+
95
+ # this works when using extend TokenGenerator
96
+ # def generates_tokens(method_opts)
97
+ # puts "I'm being called on #{self} with argument #{method_opts.inspect}"
98
+ #
99
+ # end
100
+ end
101
+ end
@@ -0,0 +1,4 @@
1
+ module OpenTok
2
+ # @private
3
+ VERSION = '2.2.0'
4
+ end
@@ -1,27 +1,34 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "open_tok/version"
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "opentok/version"
4
5
 
5
- Gem::Specification.new do |s|
6
- s.name = "opentok"
7
- s.version = OpenTok::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.authors = ["Stijn Mathysen", "Karmen Blake", "Song Zheng"]
10
- s.email = ["stijn@skylight.be", "karmenblake@gmail.com", "song@tokbox.com"]
11
- s.homepage = "https://github.com/opentok/Opentok-Ruby-SDK"
12
- s.summary = %q{OpenTok gem}
13
- s.description = %q{OpenTok is an API from TokBox that enables websites to weave live group video communication into their online experience. With OpenTok you have the freedom and flexibility to create the most engaging web experience for your users. OpenTok is currently available as a JavaScript and ActionScript 3.0 library. This gem allows you to connect to the API from within Ruby (and Rails)}
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "opentok"
8
+ spec.version = OpenTok::VERSION
9
+ spec.authors = ["Stijn Mathysen", "Karmen Blake", "Song Zheng", "Patrick Quinn-Graham", "Ankur Oberoi"]
10
+ spec.email = ["stijn@skylight.be", "karmenblake@gmail.com", "song@tokbox.com", "pqg@tokbox.com", "ankur@tokbox.com"]
11
+ spec.summary = %q{Ruby gem for the OpenTok API}
12
+ spec.description = %q{OpenTok is an API from TokBox that enables websites to weave live group video communication into their online experience. With OpenTok you have the freedom and flexibility to create the most engaging web experience for your users. This gem lets you generate sessions and tokens for OpenTok applications. It also includes support for working with OpenTok 2.0 archives. See <http://tokbox.com/opentok/platform> for more details.}
13
+ # TODO: this homepage isn't set up just yet
14
+ spec.homepage = "https://opentok.github.io/opentok-ruby-sdk"
15
+ spec.license = "MIT"
14
16
 
15
- s.rubyforge_project = "opentok"
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
16
21
 
17
- s.files = `git ls-files`.split("\n")
18
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
- s.require_paths = ["lib"]
22
+ spec.add_development_dependency "bundler", "~> 1.5"
23
+ spec.add_development_dependency "rake", "~> 10.1.1"
24
+ spec.add_development_dependency "rspec", "~> 2.14.1"
25
+ spec.add_development_dependency "webmock", "~> 1.17.4"
26
+ spec.add_development_dependency "vcr", "~> 2.8.0"
27
+ spec.add_development_dependency "yard", "~> 0.8.7"
28
+ # TODO: exclude this for compatibility with rbx
29
+ # spec.add_development_dependency "debugger", "~> 1.6.6"
21
30
 
22
- s.add_dependency "addressable"
23
- s.add_development_dependency "rake"
24
- s.add_development_dependency "rspec"
25
- s.add_development_dependency "webmock"
26
- s.add_development_dependency "vcr"
31
+ spec.add_dependency "addressable", "~> 2.3.5"
32
+ spec.add_dependency "httparty", "0.13.0"
33
+ spec.add_dependency "activesupport", ">= 3.2"
27
34
  end
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "sinatra", "~> 1.4.4"
4
+ gem "opentok", :path => "../../"
@@ -0,0 +1,212 @@
1
+ # OpenTok Archiving Sample for Ruby
2
+
3
+ This is a simple demo app that shows how you can use the OpenTok Java SDK to archive (or record)
4
+ Sessions, list archives that have been created, download the recordings, and delete the recordings.
5
+
6
+ ## Running the App
7
+
8
+ First, download the dependencies using [Bundler](http://bundler.io)
9
+
10
+ ```
11
+ $ bundle install
12
+ ```
13
+
14
+ Next, add your own API Key and API Secret to the environment variables. There are a few ways to do
15
+ this but the simplest would be to do it right in your shell.
16
+
17
+ ```
18
+ $ export API_KEY=0000000
19
+ $ export API_SECRET=abcdef1234567890abcdef01234567890abcdef
20
+ ```
21
+
22
+ Finally, start the server using Bundler to handle dependencies
23
+
24
+ ```
25
+ $ bundle exec ruby hello_world.rb
26
+ ```
27
+
28
+ Visit <http://localhost:9393> in your browser. You can now create new archives (either as a host or
29
+ as a participant) and also play archives that have already been created.
30
+
31
+ ## Walkthrough
32
+
33
+ This demo application uses the same frameworks and libraries as the HelloWorld sample. If you have
34
+ not already gotten familiar with the code in that project, consider doing so before continuing.
35
+
36
+ The explanations below are separated by page. Each section will focus on a route handler within the
37
+ main application (archiving_sample.rb).
38
+
39
+ ### Creating Archives – Host View
40
+
41
+ Start by visiting the host page at <http://localhost:9393/host> and using the application to record
42
+ an archive. Your browser will first ask you to approve permission to use the camera and microphone.
43
+ Once you've accepted, your image will appear inside the section titled 'Host'. To start recording
44
+ the video stream, press the 'Start Archiving' button. Once archiving has begun the button will turn
45
+ green and change to 'Stop Archiving'. You should also see a red blinking indicator that you are
46
+ being recorded. Wave and say hello! Stop archiving when you are done.
47
+
48
+ Next we will see how the host view is implemented on the server. The route handler for this page is
49
+ shown below:
50
+
51
+ ```ruby
52
+ get '/host' do
53
+ api_key = settings.api_key
54
+ session_id = settings.session.session_id
55
+ token = settings.opentok.generate_token(session_id, :role => :moderator)
56
+
57
+ erb :host, :locals => {
58
+ :api_key => api_key,
59
+ :session_id => session_id,
60
+ :token => token
61
+ }
62
+ end
63
+ ```
64
+
65
+ If you've completed the HelloWorld walkthrough, this should look familiar. This handler simply
66
+ generates the three strings that the client (JavaScript) needs to connect to the session: `api_key`,
67
+ `session_id` and `token`. After the user has connected to the session, they press the
68
+ 'Start Archiving' button, which sends an XHR (or Ajax) request to the <http://localhost:9393/start>
69
+ URL. The route handler for this URL is shown below:
70
+
71
+ ```ruby
72
+ get '/start' do
73
+ archive = settings.opentok.archives.create settings.session.session_id, {
74
+ :name => "Ruby Archiving Sample App"
75
+ }
76
+ body archive.to_json
77
+ end
78
+ ```
79
+
80
+ In this handler, `opentok.archives.create` is called with the `session_id` for the session that
81
+ needs to be archived. The optional second argument is a hash which contains a `:name` key. The value
82
+ is a string that will be stored with the archive and can be read later. In this case, as in the
83
+ HelloWorld sample app, there is only one session created and it is used here and for the participant
84
+ view. This will trigger the recording to begin. The response sent back to the client's XHR request
85
+ will be the JSON representation of the archive, which is returned from the `to_json()` method. The
86
+ client is also listening for the `archiveStarted` event, and uses that event to change the
87
+ 'Start Archiving' button to show 'Stop Archiving' instead. When the user presses the button this
88
+ time, another XHR request is sent to the <http://localhost:9393/stop/:archiveId> URL where
89
+ `:archiveId` represents the ID the client receives in the 'archiveStarted' event. The route handler
90
+ for this request is shown below:
91
+
92
+ ```ruby
93
+ get '/stop/:archive_id' do
94
+ archive = settings.opentok.archives.stop_by_id(params[:archive_id])
95
+ body archive.to_json
96
+ end
97
+ ```
98
+
99
+ This handler is very similar to the previous one. Instead of calling the `archives.create()` method,
100
+ the `archives.stop_by_id()` method is called. This method takes an `archive_id` as its parameter,
101
+ which is different for each time a session starts recording. But the client has sent this to the
102
+ server as part of the URL, so the `params[:archive_id]` expression is used to retrieve it.
103
+
104
+ Now you have understood the three main routes that are used to create the Host experience of
105
+ creating an archive. Much of the functionality is done in the client with JavaScript. That code can
106
+ be found in the `public/js/host.js` file. Read about the
107
+ [OpenTok.js JavaScript](http://tokbox.com/opentok/libraries/client/js/) library to learn more.
108
+
109
+ ### Creating Archives - Participant View
110
+
111
+ With the host view still open and publishing, open an additional window or tab and navigate to
112
+ <http://localhost:9393/participant> and allow the browser to use your camera and microphone. Once
113
+ again, start archiving in the host view. Back in the participant view, notice that the red blinking
114
+ indicator has been shown so that the participant knows his video is being recorded. Now stop the
115
+ archiving in the host view. Notice that the indicator has gone away in the participant view too.
116
+
117
+ Creating this view on the server is as simple as the HelloWorld sample application. See the code
118
+ for the route handler below:
119
+
120
+ ```ruby
121
+ get '/participant' do
122
+ api_key = settings.api_key
123
+ session_id = settings.session.session_id
124
+ token = settings.opentok.generate_token(session_id, :role => :moderator)
125
+
126
+ erb :participant, :locals => {
127
+ :api_key => api_key,
128
+ :session_id => session_id,
129
+ :token => token
130
+ }
131
+ end
132
+ ```
133
+
134
+ Since this view has no further interactivity with buttons, this is all that is needed for a client
135
+ that is participating in an archived session. Once again, much of the functionality is implemented
136
+ in the client, in code that can be found in the `public/js/participant.js` file.
137
+
138
+ ### Past Archives
139
+
140
+ Start by visiting the history page at <http://localhost:9393/history>. You will see a table that
141
+ displays all the archives created with your API Key. If there are more than five, the older ones
142
+ can be seen by clicking the "Older →" link. If you click on the name of an archive, your browser
143
+ will start downloading the archive file. If you click the "Delete" link in the end of the row
144
+ for any archive, that archive will be deleted and no longer available. Some basic information like
145
+ when the archive was created, how long it is, and its status is also shown. You should see the
146
+ archives you created in the previous sections here.
147
+
148
+ We begin to see how this page is created by looking at the route handler for this URL:
149
+
150
+ ```ruby
151
+ get '/history' do
152
+ page = (params[:page] || "1").to_i
153
+ offset = (page - 1) * 5
154
+ archives = settings.opentok.archives.all(:offset => offset, :count => 5)
155
+
156
+ show_previous = page > 1 ? '/history?page=' + (page-1).to_s : nil
157
+ show_next = archives.total > (offset + 5) ? '/history?page=' + (page+1).to_s : nil
158
+
159
+ erb :history, :locals => {
160
+ :archives => archives,
161
+ :show_previous => show_previous,
162
+ :show_next => show_next
163
+ }
164
+ end
165
+ ```
166
+
167
+ This view is paginated so that we don't potentially show hundreds of rows on the table, which would
168
+ be difficult for the user to navigate. So this code starts by figuring out which page needs to be
169
+ shown, where each page is a set of 5 archives. The `page` number is read from the request's query
170
+ string parameters as a string and then converted into an Integer. The `offset`, which represents how
171
+ many archives are being skipped is always calculated as five times as many pages that are less than
172
+ the current page, which is `(page - 1) * 5`. Now there is enough information to ask for a list of
173
+ archives from OpenTok, which we do by calling the `archives.all()` method of the `opentok` instance.
174
+ The parameter is an optional Hash that contains the offset, the count (which is always 5 in this
175
+ view). If we are not at the first page, we can pass the view a string that contains the relative URL
176
+ for the previous page. Similarly, we can also include one for the next page. Now the application
177
+ renders the view using that information and the partial list of archives.
178
+
179
+ At this point the template file `views/history.erb` handles looping over the array of archives and
180
+ outputting the proper information for each column in the table. It also places a link to the
181
+ download and delete routes around the archive's name and its delete button, respectively.
182
+
183
+ The code for the download route handler is shown below:
184
+
185
+ ```ruby
186
+ get '/download/:archive_id' do
187
+ archive = settings.opentok.archives.find(params[:archive_id])
188
+ redirect archive.url
189
+ end
190
+ ```
191
+
192
+ The download URL for an archive is available as a property of an `Archive` instance. In order to get
193
+ an instance to this archive, the `archives.find()` method of the `opentok` instance is used. The only
194
+ parameter it needs is the `archive_id`. We use the same technique as above to read that `archive_id`
195
+ from the URL. Lastly, we send a redirect response to the download URL back to the browser so the
196
+ download begins.
197
+
198
+ The code for the delete route handler is shown below:
199
+
200
+ ```ruby
201
+ get '/delete/:archive_id' do
202
+ settings.opentok.archives.delete_by_id(params[:archive_id])
203
+ redirect '/history'
204
+ end
205
+ ```
206
+
207
+ Once again the `archive_id` is retrieved from the URL of the request. This value is then passed to
208
+ the `archives.delete_by_id()` method of the `opentok` instance. Now that the archive has been
209
+ deleted, a redirect response back to the first page of the history is sent back to the browser.
210
+
211
+ That completes the walkthrough for this Archiving sample application. Feel free to continue to use
212
+ this application to browse the archives created for your API Key.