panda_pal 5.15.1.beta4 → 5.16.1

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: cc92de1f76650accfff8d4ecc7de8ce545f3e544d145cee1b96625b10dfa7344
4
- data.tar.gz: 8a981b6f9106d445cbb6c133a3aa992ab268c74eb63c1bedcecab7874c0ccfde
3
+ metadata.gz: 2b33c2970defb1a7405a8482186720ab63a05b10db522e4c39ce56b9aa38c1b9
4
+ data.tar.gz: 6a44dc5db6d135882e4c2aefe8b7872ea87ccd066fe329b0764e2f8564694800
5
5
  SHA512:
6
- metadata.gz: 73669c16287ed12c5a371549bb093b3e9ad815dd25d646fb46770be776fd30ae956ba5205cbbc9f26dd7358d50595fa30ba6a078b601ceb4d535902f331c9360
7
- data.tar.gz: 9715f5c8c0a7c3ca84e690a337aec8cb41961a32afb1f7caa749c2d67ed0822422d508ced453d7309efd12f3d0f6d10a39c7ffa187a75061fdb947bc35a08784
6
+ metadata.gz: 589b2581d892bca49ba635d01a70b9db1f6044b9c885758fdbfb6d483ca3c91861bc22515fdbde2086499c7d380b96f199f8dd26e44e1cd900f2a3c3c72fc53a
7
+ data.tar.gz: 330d2c17013313a47f361f12709acd99291fe918cdea00405b7f76d55925557000c66b0eee08ea68049ece4acd77e86db1fd3acec73ccacaa3165e235cadcafa
data/README.md CHANGED
@@ -149,6 +149,54 @@ end
149
149
  the defined action. Setting it to `false` indicates that the defined action handles launch validation and setup itself (this has been the legacy approach).
150
150
  Because `auto_launch: false` is most similar to the previous behavior, it is the default for LTI 1.0/1.1 LTIs. For LTI 1.3 LTIs, `auto_launch: true` is the default. If not specified and `:organization_id` is detected in the Route Path, `auto_launch` will be set to `true`
151
151
 
152
+ ## Deep Linking (LTI 1.3)
153
+
154
+ PandaPal provides comprehensive support for LTI 1.3 Deep Linking, allowing tools to be configured for content selection that can be placed directly into Canvas courses.
155
+
156
+ ### Quick Start
157
+
158
+ 1. **Configure placements** in your LTI initializer:
159
+ ```ruby
160
+ # config/initializers/lti.rb
161
+ PandaPal.stage_placement(:content_selection, {
162
+ deep_linking: true,
163
+ deep_link_redirect_url: :content_selection_url,
164
+ text: 'Select Content',
165
+ enabled: true
166
+ })
167
+ ```
168
+
169
+ 2. **Include helpers** in your controller and build content items with **required `launch_type`**:
170
+ ```ruby
171
+ class ContentSelectionController < ApplicationController
172
+ include PandaPal::DeepLinkingHelpers
173
+
174
+ def create
175
+ content_items = [
176
+ build_link_content_item(
177
+ url: panda_pal.v1p3_resource_link_request_url,
178
+ title: 'Selected Content',
179
+ custom: {
180
+ content_id: params[:content_id],
181
+ launch_type: :content_launch # REQUIRED for proper routing
182
+ }
183
+ )
184
+ ]
185
+ render_deep_linking_response(content_items)
186
+ end
187
+ end
188
+ ```
189
+
190
+ 3. **Add corresponding launch routes**:
191
+ ```ruby
192
+ # config/routes.rb
193
+ lti_nav content_launch: 'content#launch'
194
+ ```
195
+
196
+ **⚠️ IMPORTANT**: All content items **MUST** include `launch_type` in the `custom` hash. This is used by the `resource_link_request` action to determine which route to redirect to when Canvas launches your content.
197
+
198
+ **For complete configuration options, implementation examples, helper method documentation, and troubleshooting, see [DEEP_LINKING.md](DEEP_LINKING.md).**
199
+
152
200
  ## Implementating data segregation
153
201
  This engine uses Apartment to keep data segregated between installations of the implementing LTI tool.
154
202
  By default, it does this by inspecting the path of the request, and matching URLs containing `orgs` or `organizations`,
@@ -457,7 +505,7 @@ data_type: If specified, and a settings hash contains this attribute, attribute
457
505
  use `class` to determine data type. For example: 30.minutes.class.to_s => "ActiveSupport::Duration"
458
506
 
459
507
  ## Bridge vs Canvas
460
- As of 3.2.0, the LTI XML config can have subtle differences based on whether your platform is bridge, or canvas.
508
+ As of 3.2.0, the LTI XML config can have subtle differences based on whether your platform is bridge, or canvas.
461
509
  This is determined by `PandaPal.lti_options[:platform]`.
462
510
  Set this to `platform: 'canvas.instructure.com'` (default)
463
511
  OR `platform: 'bridgeapp.com'`
@@ -490,7 +538,7 @@ You will want to watch out for a few scenarios:
490
538
  2) Use the `Authorization` header with `token={session_key}` to send your
491
539
  PandaPal session info into api calls.
492
540
  3) If you use `link_to` and navigate in your LTI (apps that are not single page)
493
- make sure you include the `link_nonce` like so:
541
+ make sure you include the `link_nonce` like so:
494
542
  ```ruby
495
543
  link_to "Link Name", somewhere_else_path(arg, session_token: link_nonce)
496
544
  ```
@@ -6,7 +6,7 @@ module PandaPal
6
6
  skip_before_action :verify_authenticity_token, raise: false
7
7
  end
8
8
 
9
- before_action ->{ validate_launch!(version: :v1p3) }, only: [:resource_link_request]
9
+ before_action ->{ validate_launch!(version: :v1p3) }, only: [:resource_link_request, :deep_link_request]
10
10
  before_action :enforce_environment!, only: [:resource_link_request]
11
11
 
12
12
  # Redirect to beta/test as necessary
@@ -40,7 +40,7 @@ module PandaPal
40
40
  end
41
41
 
42
42
  def resource_link_request
43
- ltype = @decoded_lti_jwt['https://www.instructure.com/placement']
43
+ ltype = @decoded_lti_jwt[LtiConstants::Canvas::PLACEMENT] || @decoded_lti_jwt[LtiConstants::Claims::CUSTOM]&.dig("launch_type")
44
44
 
45
45
  if ltype
46
46
  current_session.data.merge!({
@@ -54,6 +54,32 @@ module PandaPal
54
54
  end
55
55
  end
56
56
 
57
+ def deep_link_request
58
+ message_type = @decoded_lti_jwt[LtiConstants::Claims::MESSAGE_TYPE]
59
+
60
+ unless message_type == LtiConstants::MessageTypes::DEEP_LINKING_REQUEST
61
+ render plain: 'Not a Deep Linking request', status: :bad_request
62
+ return
63
+ end
64
+
65
+ # Store deep linking session data
66
+ current_session.data.merge!({
67
+ lti_version: 'v1p3',
68
+ lti_launch_placement: 'deep_linking',
69
+ launch_url_params: request.query_parameters.to_h,
70
+ launch_params: @decoded_lti_jwt,
71
+ deep_link_return_url: @decoded_lti_jwt[LtiConstants::DeepLinking::DEEP_LINKING_SETTINGS][LtiConstants::DeepLinkingSettings::DEEP_LINK_RETURN_URL],
72
+ deep_link_data: @decoded_lti_jwt[LtiConstants::DeepLinking::DATA]
73
+ })
74
+
75
+ redirect_url = LaunchUrlHelpers.deep_link_redirect_url(request)
76
+ if redirect_url
77
+ redirect_with_session_to(redirect_url, route_context: main_app)
78
+ else
79
+ render plain: 'Deep linking redirect URL not configured. Please configure deep_link_redirect_url in your placement or global LTI options.', status: :internal_server_error
80
+ end
81
+ end
82
+
57
83
  def tool_config
58
84
  if PandaPal.lti_environments.empty?
59
85
  render plain: 'Domains must be set in lti_environments'
@@ -71,7 +97,6 @@ module PandaPal
71
97
  target_link_uri: LaunchUrlHelpers.absolute_launch_url(
72
98
  k.to_sym,
73
99
  host: parsed_request_url,
74
- launch_handler: v1p3_resource_link_request_path,
75
100
  default_auto_launch: true
76
101
  ),
77
102
  })
@@ -0,0 +1,142 @@
1
+ module PandaPal
2
+ module DeepLinkingHelpers
3
+ # Build a link content item
4
+ # @param url [String] The launch URL for the content
5
+ # @param title [String] The title of the content item
6
+ # @param text [String] Optional description text
7
+ # @param icon [String] Optional icon URL
8
+ # @param thumbnail [String] Optional thumbnail URL
9
+ # @param custom [Hash] Custom parameters - MUST include launch_type for proper routing
10
+ # @param options [Hash] Additional options to merge into the content item
11
+ #
12
+ # Example:
13
+ # build_link_content_item(
14
+ # url: panda_pal.v1p3_resource_link_request_url,
15
+ # title: 'My Quiz',
16
+ # custom: {
17
+ # quiz_id: 123,
18
+ # launch_type: :quiz_launch # REQUIRED for proper routing
19
+ # }
20
+ # )
21
+ def build_link_content_item(url:, title: nil, text: nil, icon: nil, thumbnail: nil, custom: {}, **options)
22
+ validate_custom_launch_type!(custom)
23
+
24
+ content_item = {
25
+ type: 'link',
26
+ url: url,
27
+ title: title
28
+ }
29
+ content_item[:text] = text if text.present?
30
+ content_item[:icon] = icon if icon.present?
31
+ content_item[:thumbnail] = thumbnail if thumbnail.present?
32
+ content_item[:custom] = custom if custom.present?
33
+ content_item.merge(options)
34
+ end
35
+
36
+ # Build a file content item
37
+ # @param url [String] The file URL
38
+ # @param title [String] The title of the content item
39
+ # @param text [String] Optional description text
40
+ # @param media_type [String] Optional media type
41
+ # @param icon [String] Optional icon URL
42
+ # @param custom [Hash] Custom parameters - MUST include launch_type for proper routing
43
+ # @param options [Hash] Additional options to merge into the content item
44
+ def build_file_content_item(url:, title: nil, text: nil, media_type: nil, icon: nil, custom: {}, **options)
45
+ validate_custom_launch_type!(custom)
46
+
47
+ content_item = {
48
+ type: 'file',
49
+ url: url,
50
+ title: title
51
+ }
52
+ content_item[:text] = text if text.present?
53
+ content_item[:mediaType] = media_type if media_type.present?
54
+ content_item[:icon] = icon if icon.present?
55
+ content_item[:custom] = custom if custom.present?
56
+ content_item.merge(options)
57
+ end
58
+
59
+ # Build an HTML content item
60
+ # @param html [String] The HTML content
61
+ # @param title [String] The title of the content item
62
+ # @param text [String] Optional description text
63
+ # @param custom [Hash] Custom parameters - MUST include launch_type for proper routing
64
+ # @param options [Hash] Additional options to merge into the content item
65
+ def build_html_content_item(html:, title: nil, text: nil, custom: {}, **options)
66
+ validate_custom_launch_type!(custom)
67
+
68
+ content_item = {
69
+ type: 'html',
70
+ html: html,
71
+ title: title
72
+ }
73
+ content_item[:text] = text if text.present?
74
+ content_item[:custom] = custom if custom.present?
75
+ content_item.merge(options)
76
+ end
77
+
78
+ # Render the deep linking response
79
+ # This builds the JWT and renders the auto-submit form
80
+ def render_deep_linking_response(content_items, custom_data: {})
81
+ deep_link_return_url = current_session[:deep_link_return_url]
82
+ deep_link_data = current_session[:deep_link_data]
83
+
84
+ unless deep_link_return_url.present?
85
+ render plain: 'No deep linking session found. Please ensure you accessed this through a proper deep linking flow.', status: :bad_request
86
+ return
87
+ end
88
+
89
+ # Build custom deep link data - merge Canvas data with custom data
90
+ launch_params = current_session[:launch_params]
91
+ merged_custom_data = (deep_link_data || {}).merge({
92
+ selection_time: Time.now.iso8601,
93
+ selected_by: launch_params&.dig('sub') || 'unknown'
94
+ }).merge(custom_data)
95
+
96
+ # Build the JWT response
97
+ jwt = build_deep_link_jwt(content_items, merged_custom_data)
98
+
99
+ # Set up the auto-submit form
100
+ @form_action = deep_link_return_url
101
+ @method = :post
102
+ @form_data = { JWT: jwt }
103
+
104
+ render partial: 'panda_pal/partials/auto_submit_form'
105
+ end
106
+
107
+ private
108
+
109
+ def validate_custom_launch_type!(custom)
110
+ unless custom.is_a?(Hash) && custom.key?(:launch_type)
111
+ raise ArgumentError, "custom parameter must include :launch_type for proper routing. Example: custom: { launch_type: :my_content_type }"
112
+ end
113
+ end
114
+
115
+ def build_deep_link_jwt(content_items, custom_deep_link_data)
116
+ # Get issuer (client_id) and audience from session
117
+ launch_params = current_session[:launch_params]
118
+ issuer = launch_params&.dig('aud') || current_organization.key
119
+ audience = launch_params&.dig('iss')
120
+
121
+ unless audience.present?
122
+ raise StandardError, 'Unable to determine audience for deep linking response'
123
+ end
124
+
125
+ deep_link_jwt_payload = {
126
+ iss: issuer,
127
+ aud: audience,
128
+ iat: Time.now.to_i,
129
+ exp: Time.now.to_i + 600,
130
+ nonce: SecureRandom.uuid,
131
+ PandaPal::LtiConstants::Claims::MESSAGE_TYPE => PandaPal::LtiConstants::MessageTypes::DEEP_LINKING_RESPONSE,
132
+ PandaPal::LtiConstants::Claims::VERSION => PandaPal::LtiConstants::Versions::V1_3,
133
+ PandaPal::LtiConstants::DeepLinking::CONTENT_ITEMS => content_items,
134
+ PandaPal::LtiConstants::DeepLinking::DATA => custom_deep_link_data,
135
+ }
136
+
137
+ require 'json/jwt'
138
+ jwk = JSON::JWK.new(PandaPal.lti_private_key)
139
+ JSON::JWT.new(deep_link_jwt_payload).sign(jwk, :RS256).to_s
140
+ end
141
+ end
142
+ end
@@ -2,6 +2,13 @@ module PandaPal
2
2
  module LaunchUrlHelpers
3
3
  def self.absolute_launch_url(launch_type, host: uri_host, launch_handler: nil, default_auto_launch: false)
4
4
  opts = PandaPal.lti_paths[launch_type]
5
+
6
+ if launch_handler.nil? && opts.present?
7
+ launch_handler = opts[:message_type] == PandaPal::LtiConstants::MessageTypes::DEEP_LINKING_REQUEST ?
8
+ :v1p3_deep_link_request_path :
9
+ :v1p3_resource_link_request_path
10
+ end
11
+
5
12
  auto_launch = opts[:auto_launch] != nil ? opts[:auto_launch] : default_auto_launch
6
13
  auto_launch = auto_launch && launch_handler.present?
7
14
 
@@ -45,7 +52,7 @@ module PandaPal
45
52
  def self.launch_route(opts, launch_type: nil)
46
53
  if opts.is_a?(Symbol) || opts.is_a?(String)
47
54
  launch_type = opts.to_sym
48
- opts = PandaPal.lti_paths[launch_type]
55
+ opts = PandaPal.lti_paths[launch_type] || {}
49
56
  end
50
57
 
51
58
  if opts[:route_helper_key]
@@ -100,5 +107,35 @@ module PandaPal
100
107
  engine = key_bits[0] == 'MainApp' ? Rails.application : (key_bits[0].constantize)::Engine
101
108
  engine.routes.url_helpers.send(key_bits[1], *arguments, **kwargs)
102
109
  end
110
+
111
+ def self.deep_link_redirect_url(request)
112
+ # Try placement-specific configuration first
113
+ launch_type = request.query_parameters['launch_type']&.to_sym
114
+ return global_configuration_url unless launch_type
115
+
116
+ placement_config = PandaPal.lti_paths[launch_type]
117
+ return global_configuration_url unless deep_linking_placement?(placement_config)
118
+
119
+ # Check for explicit deep_link_redirect_url configuration
120
+ redirect_url = placement_config[:deep_link_redirect_url]
121
+ return normalize_redirect_url(redirect_url) if redirect_url.present?
122
+
123
+ # Fall back to global configuration
124
+ global_configuration_url
125
+ end
126
+
127
+ def self.global_configuration_url
128
+ redirect_url = PandaPal.lti_options[:deep_link_redirect_url]
129
+ normalize_redirect_url(redirect_url) if redirect_url.present?
130
+ end
131
+
132
+ def self.deep_linking_placement?(placement_config)
133
+ placement_config&.dig(:message_type) == PandaPal::LtiConstants::MessageTypes::DEEP_LINKING_REQUEST
134
+ end
135
+
136
+ def self.normalize_redirect_url(url)
137
+ return nil unless url.present?
138
+ url.is_a?(Symbol) ? url : url.to_sym
139
+ end
103
140
  end
104
141
  end
@@ -199,7 +199,7 @@ module PandaPal
199
199
  def custom_lti_params
200
200
  @custom_lti_params ||= begin
201
201
  # LT 1.3
202
- custom_params = launch_params["https://purl.imsglobal.org/spec/lti/claim/custom"]
202
+ custom_params = launch_params[PandaPal::LtiConstants::Claims::CUSTOM]
203
203
  return custom_params if custom_params.present?
204
204
 
205
205
  # LTI 1.0/1.1
@@ -10,29 +10,25 @@ require "apartment/adapters/postgresql_adapter"
10
10
  module Apartment
11
11
  SHARD_PREFIXES = ["SHARD_DB", "HEROKU_POSTGRESQL"]
12
12
 
13
- class << self
14
- def_delegators :connection_class, :connected?
13
+ def self.shard_configurations
14
+ $shard_configurations ||= begin
15
+ shard_to_env = {}
15
16
 
16
- def shard_configurations
17
- $shard_configurations ||= begin
18
- shard_to_env = {}
17
+ base_config = ActiveRecord::Base.configurations.find_db_config(Rails.env).configuration_hash
19
18
 
20
- base_config = ActiveRecord::Base.configurations.find_db_config(Rails.env).configuration_hash
19
+ ENV.keys.each do |k|
20
+ m = /^(#{SHARD_PREFIXES.join("|")})_(\w+)_URL$/.match(k)
21
+ next unless m
21
22
 
22
- ENV.keys.each do |k|
23
- m = /^(#{SHARD_PREFIXES.join("|")})_(\w+)_URL$/.match(k)
24
- next unless m
25
-
26
- url = ENV[k]
27
- shardcfg = ActiveRecord::Base.configurations.resolve(url).configuration_hash
28
- shardcfg = base_config.merge(shardcfg || {})
29
- shard_to_env[m[2].downcase] = shardcfg
30
- end
23
+ url = ENV[k]
24
+ shardcfg = ActiveRecord::Base.configurations.resolve(url).configuration_hash
25
+ shardcfg = base_config.merge(shardcfg || {})
26
+ shard_to_env[m[2].downcase] = shardcfg
27
+ end
31
28
 
32
- shard_to_env.freeze unless Rails.env.test?
29
+ shard_to_env.freeze unless Rails.env.test?
33
30
 
34
- shard_to_env
35
- end
31
+ shard_to_env
36
32
  end
37
33
  end
38
34
 
@@ -124,12 +120,14 @@ module Apartment
124
120
 
125
121
  raise ActiveRecord::StatementInvalid, "PandaPal/Apartment Mutli-DB support does not currently support DB roles" if ActiveRecord::Base.current_role != ActiveRecord::Base.default_role
126
122
 
127
- shard, schema = Apartment::Tenant.split_tenant(@current)
128
- Apartment.establish_connection(multi_tenantify(tenant, false)) unless shard == "default" || Apartment.connected?
129
- Apartment.connection.verify!
123
+ unless ActiveRecord::Base.connected?
124
+ Apartment.establish_connection multi_tenantify(tenant, false)
125
+ Apartment.connection.verify!
126
+ end
127
+
130
128
  Apartment.connection.enable_query_cache! if query_cache_enabled
131
129
 
132
- raise ActiveRecord::StatementInvalid, "Could not find schema for tenant \"#{tenant}\": (#{tenant_schemas.inspect})" unless schema_exists?(tenant_schemas)
130
+ raise ActiveRecord::StatementInvalid, "Could not find schema for tenant #{tenant} (#{tenant_schemas.inspect})" unless schema_exists?(tenant_schemas)
133
131
 
134
132
  Apartment.connection.schema_search_path = full_search_path
135
133
  rescue *rescuable_exceptions => e
@@ -152,14 +150,6 @@ module Apartment
152
150
  schema
153
151
  end
154
152
  end
155
-
156
- def raise_schema_connect_to_new(tenant, exception)
157
- shard, schema = Tenant.split_tenant(tenant)
158
- raise TenantNotFound, <<~EXCEPTION_MESSAGE
159
- Could not set search path to schemas, they may be invalid: "#{schema}" #{full_search_path}.
160
- Original error: #{exception.class}: #{exception}
161
- EXCEPTION_MESSAGE
162
- end
163
153
  end
164
154
  end
165
155
 
@@ -220,7 +210,7 @@ Apartment.configure do |config|
220
210
  shard_configurations = Apartment.shard_configurations
221
211
 
222
212
  PandaPal::Organization.all.to_a.each_with_object({}) do |org, hash|
223
- shard = org.shard.to_s || "default"
213
+ shard = org.shard || "default"
224
214
  hash[org.tenant_name] = shard == "default" ? base_config : shard_configurations[shard.downcase]
225
215
  end
226
216
  else
data/config/routes.rb CHANGED
@@ -14,6 +14,7 @@ PandaPal::Engine.routes.draw do
14
14
  post '/oidc_login' => 'lti_v1_p3#login'
15
15
  get '/oidc_login' => 'lti_v1_p3#login'
16
16
  post '/resource_link_request' => 'lti_v1_p3#resource_link_request'
17
+ post '/deep_link_request' => 'lti_v1_p3#deep_link_request'
17
18
  get '/public_jwks' => 'lti_v1_p3#public_jwks'
18
19
  end
19
20
  end
@@ -1,4 +1,4 @@
1
- class RenameSessionSessionKey < PandaPal::MiscHelper::MigrationClass
1
+ class RenameSessionSessionKey < ActiveRecord::Migration[7.1]
2
2
  def change
3
3
  rename_column :panda_pal_sessions, :session_key, :session_secret
4
4
  end
@@ -0,0 +1,48 @@
1
+ module PandaPal
2
+ module LtiConstants
3
+ # LTI 1.3 Standard Claims
4
+ module Claims
5
+ MESSAGE_TYPE = 'https://purl.imsglobal.org/spec/lti/claim/message_type'.freeze
6
+ VERSION = 'https://purl.imsglobal.org/spec/lti/claim/version'.freeze
7
+ DEPLOYMENT_ID = 'https://purl.imsglobal.org/spec/lti/claim/deployment_id'.freeze
8
+ TARGET_LINK_URI = 'https://purl.imsglobal.org/spec/lti/claim/target_link_uri'.freeze
9
+ CONTEXT = 'https://purl.imsglobal.org/spec/lti/claim/context'.freeze
10
+ RESOURCE_LINK = 'https://purl.imsglobal.org/spec/lti/claim/resource_link'.freeze
11
+ ROLES = 'https://purl.imsglobal.org/spec/lti/claim/roles'.freeze
12
+ CUSTOM = 'https://purl.imsglobal.org/spec/lti/claim/custom'.freeze
13
+ end
14
+
15
+ # LTI Deep Linking Claims
16
+ module DeepLinking
17
+ CONTENT_ITEMS = 'https://purl.imsglobal.org/spec/lti-dl/claim/content_items'.freeze
18
+ DATA = 'https://purl.imsglobal.org/spec/lti-dl/claim/data'.freeze
19
+ DEEP_LINKING_SETTINGS = 'https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings'.freeze
20
+ end
21
+
22
+ # Canvas-specific Claims
23
+ module Canvas
24
+ PLACEMENT = 'https://www.instructure.com/placement'.freeze
25
+ end
26
+
27
+ # LTI Message Types
28
+ module MessageTypes
29
+ DEEP_LINKING_REQUEST = 'LtiDeepLinkingRequest'.freeze
30
+ DEEP_LINKING_RESPONSE = 'LtiDeepLinkingResponse'.freeze
31
+ RESOURCE_LINK_REQUEST = 'LtiResourceLinkRequest'.freeze
32
+ end
33
+
34
+ # LTI Versions
35
+ module Versions
36
+ V1_3 = '1.3.0'.freeze
37
+ end
38
+
39
+ # Deep Linking Settings Keys
40
+ module DeepLinkingSettings
41
+ DEEP_LINK_RETURN_URL = 'deep_link_return_url'.freeze
42
+ ACCEPT_TYPES = 'accept_types'.freeze
43
+ ACCEPT_PRESENTATION_DOCUMENT_TARGETS = 'accept_presentation_document_targets'.freeze
44
+ ACCEPT_MEDIA_TYPES = 'accept_media_types'.freeze
45
+ AUTO_CREATE = 'auto_create'.freeze
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  module PandaPal
2
- VERSION = "5.15.1.beta4"
2
+ VERSION = "5.16.1"
3
3
  end
data/lib/panda_pal.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "panda_pal/engine"
2
2
  require 'panda_pal/plugins'
3
3
  require 'panda_pal/helpers'
4
+ require 'panda_pal/lti_constants'
4
5
  require 'oauth/request_proxy/rack_request'
5
6
  require 'oauth/request_proxy/action_dispatch_request'
6
7
 
@@ -50,9 +51,22 @@ module PandaPal
50
51
  @@lti_custom_params.deep_dup
51
52
  end
52
53
 
53
- def self.stage_navigation(navigation, options)
54
- @@lti_navigation[navigation] ||= {}
55
- @@lti_navigation[navigation].merge!(options)
54
+ def self.stage_navigation(navigation, options = {})
55
+ warn "[DEPRECATION] `stage_navigation` is deprecated. Please use `stage_placement` instead."
56
+ stage_placement(navigation, options)
57
+ end
58
+
59
+ def self.stage_placement(placement, options = {})
60
+ @@lti_navigation[placement] ||= {}
61
+
62
+ if options.delete(:deep_linking)
63
+ options = options.merge({
64
+ message_type: LtiConstants::MessageTypes::DEEP_LINKING_REQUEST,
65
+ route_helper_key: :deep_link_request
66
+ })
67
+ end
68
+
69
+ @@lti_navigation[placement].merge!(options)
56
70
  end
57
71
 
58
72
  def self.lti_paths
@@ -70,7 +84,7 @@ module PandaPal
70
84
  end
71
85
 
72
86
  def self.lti_private_key=(v)
73
- @@lti_private_key = k
87
+ @@lti_private_key = v
74
88
  end
75
89
 
76
90
  def self.register_extension(type, modul)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panda_pal
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.15.1.beta4
4
+ version: 5.16.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Instructure CustomDev
@@ -159,6 +159,7 @@ files:
159
159
  - app/lib/lti_xml/base_platform.rb
160
160
  - app/lib/lti_xml/bridge_platform.rb
161
161
  - app/lib/lti_xml/canvas_platform.rb
162
+ - app/lib/panda_pal/deep_linking_helpers.rb
162
163
  - app/lib/panda_pal/launch_url_helpers.rb
163
164
  - app/lib/panda_pal/lti_jwt_validator.rb
164
165
  - app/models/panda_pal/api_call.rb
@@ -197,6 +198,7 @@ files:
197
198
  - lib/panda_pal/helpers/route_helper.rb
198
199
  - lib/panda_pal/helpers/secure_headers.rb
199
200
  - lib/panda_pal/helpers/session_replacement.rb
201
+ - lib/panda_pal/lti_constants.rb
200
202
  - lib/panda_pal/plugins.rb
201
203
  - lib/panda_pal/spec_helper.rb
202
204
  - lib/panda_pal/version.rb