From 904875cd9cfceeb8681c4d2befa878b61ed65a06 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Tue, 4 Jul 2017 16:20:11 -0700 Subject: [PATCH] LP#1673870: Handle OverDrive ebook checkout and download The workflow for checking out and downloading a title via the OverDrive API is relatively complex: 1. Check out a title. 2. Lock in a specific format for the checked-out title. Once you lock in a format, you can only download the title in that format -- except that the browser-based OverDrive Read and OverDrive Listen formats are always available (if supported for that title), even if you've locked in another format. 3. Request a link for downloading the title in the specified format. Download links are dynamically generated and only work for 60 seconds from the time of your request. To simplify the process, we require the user to lock in a format during checkout. Then, when the user clicks the Download button, we request a download link; OverDrive responds with a URL, and we immediately redirect the current browser tab/window to that URL. A new API call, open-ils.ebook_api.title.get_download_link, has been added for requesting the download link. Since API calls are not vendor-specific, we also add support for the new method in the test module, complete with unit test. Supplementary fixes: - show spinner in My Account while loading from ebook API - ensure session ID is available to ebook object during transactions - fix display of ebook formats Signed-off-by: Jeff Davis Signed-off-by: Galen Charlton Signed-off-by: Bill Erickson --- .../lib/OpenILS/Application/EbookAPI.pm | 47 +++++++- .../OpenILS/Application/EbookAPI/OverDrive.pm | 86 ++++++++++++-- .../lib/OpenILS/Application/EbookAPI/Test.pm | 23 ++++ .../perlmods/live_t/20-lp1541559-ebook-api.t | 10 +- .../opac/parts/ebook_api/avail_js.tt2 | 2 +- .../web/js/ui/default/opac/ebook_api/ebook.js | 64 ++++++++-- .../js/ui/default/opac/ebook_api/loggedin.js | 111 ++++++++++++++---- 7 files changed, 302 insertions(+), 41 deletions(-) diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI.pm index d6961a8a01..bc968b5586 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI.pm @@ -481,7 +481,7 @@ __PACKAGE__->register_method( # - barcode: patron barcode # sub do_xact { - my ($self, $conn, $auth, $session_id, $title_id, $barcode, $email) = @_; + my ($self, $conn, $auth, $session_id, $title_id, $barcode, $param) = @_; my $action; if ($self->api_name =~ /checkout/) { @@ -509,9 +509,12 @@ sub do_xact { # handler method constructs and submits request (and handles any external authentication) my $res; - # place_hold has email as optional additional param - if ($action eq 'place_hold') { - $res = $handler->place_hold($title_id, $user_token, $email); + if ($action eq 'checkout') { + # checkout has format as optional additional param + $res = $handler->checkout($title_id, $user_token, $param); + } elsif ($action eq 'place_hold') { + # place_hold has email as optional additional param + $res = $handler->place_hold($title_id, $user_token, $param); } else { $res = $handler->$action($title_id, $user_token); } @@ -845,4 +848,40 @@ __PACKAGE__->register_method( } ); +sub get_download_link { + my ($self, $conn, $auth, $session_id, $request_link) = @_; + my $handler = new_handler($session_id); + return $handler->do_get_download_link($request_link); +} +__PACKAGE__->register_method( + method => 'get_download_link', + api_name => 'open-ils.ebook_api.title.get_download_link', + api_level => 1, + argc => 3, + signature => { + desc => "Get download link for an OverDrive title that has been checked out", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'request_link', + desc => 'The URL used to request a download link', + type => 'string' + } + ], + return => { + desc => 'Success: { url => "http://example.com/download-link" } / Failure: { error_msg => "Download link request failed." }', + type => 'hashref' + } + } +); + 1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/OverDrive.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/OverDrive.pm index 9549fa49d9..b6997d16ce 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/OverDrive.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/OverDrive.pm @@ -376,10 +376,13 @@ sub get_title_info { }; if (my $res = $self->handle_http_request($req, $self->{session_id})) { if ($res->{content}->{title}) { - return { + my $info = { title => $res->{content}->{title}, author => $res->{content}->{creators}[0]{name} }; + # Append format information (useful for checkouts). + $info->{formats} = $self->get_formats($title_id); + return $info; } else { $logger->error("EbookAPI: OverDrive metadata lookup failed for $title_id"); } @@ -446,6 +449,20 @@ sub do_holdings_lookup { } # request available formats + $holdings->{formats} = $self->get_formats($title_id); + + return $holdings; +} + +# Returns a list of available formats for a given title. +sub get_formats { + my ($self, $title_id) = @_; + $self->do_client_auth() if (!$self->{bearer_token}); + $self->get_library_info() if (!$self->{collection_token}); + my $collection_token = $self->{collection_token}; + + my $formats = []; + my $format_req = { method => 'GET', uri => $self->{discovery_base_uri} . "/collections/$collection_token/products/$title_id/metadata" @@ -453,7 +470,7 @@ sub do_holdings_lookup { if (my $format_res = $self->handle_http_request($format_req, $self->{session_id})) { if ($format_res->{content}->{formats}) { foreach my $f (@{$format_res->{content}->{formats}}) { - push @{$holdings->{formats}}, $f->{name}; + push @$formats, { id => $f->{id}, name => $f->{name} }; } } else { $logger->info("EbookAPI: OverDrive holdings format request for title $title_id contained no format information"); @@ -462,7 +479,7 @@ sub do_holdings_lookup { $logger->error("EbookAPI: failed to retrieve OverDrive holdings formats for title $title_id"); } - return $holdings; + return $formats; } # POST https://patron.api.overdrive.com/v1/patrons/me/checkouts @@ -500,8 +517,17 @@ sub do_holdings_lookup { # ], # ... # } +# +# Our return value looks like this: +# { +# due_date => "10/14/2013 10:56:00 AM", +# formats => [ +# "ebook-overdrive" => "https://patron.api.overdrive.com/v1/patrons/me/checkouts/76C1B7D0-17F4-4C05-8397-C66C17411584/formats/ebook-overdrive/downloadlink?errorpageurl={errorpageurl}&odreadauthurl={odreadauthurl}", +# ... +# ] +# } sub checkout { - my ($self, $title_id, $patron_token) = @_; + my ($self, $title_id, $patron_token, $format) = @_; my $request_content = { fields => [ { @@ -510,6 +536,9 @@ sub checkout { } ] }; + if ($format) { + push @{$request_content->{fields}}, { name => 'formatType', value => $format }; + } my $req = { method => 'POST', uri => $self->{circulation_base_uri} . "/patrons/me/checkouts", @@ -517,7 +546,16 @@ sub checkout { }; if (my $res = $self->handle_http_request($req, $self->{session_id})) { if ($res->{content}->{expires}) { - return { due_date => $res->{content}->{expires} }; + my $checkout = { due_date => $res->{content}->{expires} }; + if (defined $res->{content}->{formats}) { + my $formats = {}; + foreach my $f (@{$res->{content}->{formats}}) { + my $ftype = $f->{formatType}; + $formats->{$ftype} = $f->{linkTemplates}->{downloadLink}->{href}; + } + $checkout->{formats} = $formats; + } + return $checkout; } $logger->error("EbookAPI: checkout failed for OverDrive title $title_id"); return { error_msg => ( (defined $res->{content}) ? $res->{content} : 'Unknown checkout error' ) }; @@ -637,12 +675,17 @@ sub get_patron_checkouts { foreach my $checkout (@{$res->{content}->{checkouts}}) { my $title_id = $checkout->{reserveId}; my $title_info = $self->get_title_info($title_id); - # TODO get download URL - need to "lock in" a format first, see OD Checkouts API docs + my $formats = {}; + foreach my $f (@{$checkout->{formats}}) { + my $ftype = $f->{formatType}; + $formats->{$ftype} = $f->{linkTemplates}->{downloadLink}->{href}; + }; push @$checkouts, { title_id => $title_id, due_date => $checkout->{expires}, title => $title_info->{title}, - author => $title_info->{author} + author => $title_info->{author}, + formats => $formats } }; $self->{checkouts} = $checkouts; @@ -703,4 +746,33 @@ sub do_get_patron_xacts { return $self->handle_http_request($req, $self->{session_id}); } +# get download URL for checked-out title +sub do_get_download_link { + my ($self, $request_link) = @_; + # Request links use the same domain as the circulation base URI, but they + # are apparently always plain HTTP. The request link still works if you + # use HTTPS instead. So, if our circulation base URI uses HTTPS, let's + # force the request link to HTTPS too, for two reasons: + # 1. A preference for HTTPS is implied by the library's circulation base + # URI setting. + # 2. The base URI of the request link has to match the circulation base URI + # (including the same protocol) in order for the handle_http_request() + # method above to automatically re-authenticate the patron, if required. + if ($self->{circulation_base_uri} =~ /^https:/) { + $request_link =~ s/^http:/https:/; + } + my $req = { + method => 'GET', + uri => $request_link + }; + if (my $res = $self->handle_http_request($req, $self->{session_id})) { + if ($res->{content}->{links}->{contentlink}->{href}) { + return { url => $res->{content}->{links}->{contentlink}->{href} }; + } + return { error_msg => ( (defined $res->{content}) ? $res->{content} : 'Could not get content link' ) }; + } + $logger->error("EbookAPI: no response received from OverDrive server"); + return; +} + 1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/Test.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/Test.pm index adab52c094..31d7bf9ac0 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/Test.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/Test.pm @@ -272,6 +272,9 @@ sub checkout { # Patron ID or patron auth token, as returned by do_patron_auth(). my $user_token = shift; + # Ebook format to be checked out (optional, not used here). + my $format = shift; + # If checkout succeeds, the response is a hashref with the following fields: # - due_date # - xact_id (optional) @@ -503,3 +506,23 @@ sub get_patron_holds { return $self->{holds}; } +sub do_get_download_link { + my $self = shift; + my $request_link = shift; + + # For some vendors (e.g. OverDrive), the workflow is as follows: + # + # 1. Perform a checkout. + # 2. Checkout response contains a URL which we use to request a + # format-specific download link for the checked-out title. + # 3. Submit a request to the request link. + # 4. Response contains a (temporary/dynamic) URL which the user + # clicks on to download the ebook in the desired format. + # + # For other vendors, the download link for a title is static and not + # format-dependent. In that case, we just return the original request link + # (but ideally the UI will skip the download link request altogether, since + # it's superfluous in that case). + + return $request_link; +} diff --git a/Open-ILS/src/perlmods/live_t/20-lp1541559-ebook-api.t b/Open-ILS/src/perlmods/live_t/20-lp1541559-ebook-api.t index 6c0aedb888..0b81a4ca68 100644 --- a/Open-ILS/src/perlmods/live_t/20-lp1541559-ebook-api.t +++ b/Open-ILS/src/perlmods/live_t/20-lp1541559-ebook-api.t @@ -1,6 +1,6 @@ #!perl use strict; use warnings; -use Test::More tests => 23; # XXX +use Test::More tests => 24; # XXX use OpenILS::Utils::TestUtils; diag("Tests Ebook API"); @@ -154,6 +154,14 @@ my $checkout_req = $ebook_api->request( my $checkout = $checkout_req->recv->content; ok(exists $checkout->{due_date}, 'Ebook checked out'); +# open-ils.ebook_api.title.get_download_link +my $request_link = 'http://example.com/ebookapi/t/003'; +my $download_link_req = $ebook_api->request( + 'open-ils.ebook_api.title.get_download_link', $authtoken, $session_id, $request_link); +my $download_link = $download_link_req->recv->content; +# Test module just returns the original request_link as the response. +ok($download_link eq $request_link, 'Received download link for ebook'); + # open-ils.ebook_api.renew my $renew_req = $ebook_api->request( 'open-ils.ebook_api.renew', $authtoken, $session_id, '001', EBOOK_API_PATRON_USERNAME); diff --git a/Open-ILS/src/templates/opac/parts/ebook_api/avail_js.tt2 b/Open-ILS/src/templates/opac/parts/ebook_api/avail_js.tt2 index 5a03dc5073..216d2da2f3 100644 --- a/Open-ILS/src/templates/opac/parts/ebook_api/avail_js.tt2 +++ b/Open-ILS/src/templates/opac/parts/ebook_api/avail_js.tt2 @@ -29,7 +29,7 @@ dojo.addOnLoad(function() { if (holdings.formats.length > 0) { var formats_ul = dojo.create("ul", null, ebook.rec_id + '_formats'); dojo.forEach(holdings.formats, function(f) { - dojo.create("li", { innerHTML: f }, formats_ul); + dojo.create("li", { innerHTML: f.name }, formats_ul); }); var status_node = dojo.byId(ebook.rec_id + '_status'); var status_str = holdings.copies_available + ' of ' + holdings.copies_owned + ' available'; diff --git a/Open-ILS/web/js/ui/default/opac/ebook_api/ebook.js b/Open-ILS/web/js/ui/default/opac/ebook_api/ebook.js index 5119507da9..03814c4a7d 100644 --- a/Open-ILS/web/js/ui/default/opac/ebook_api/ebook.js +++ b/Open-ILS/web/js/ui/default/opac/ebook_api/ebook.js @@ -13,11 +13,10 @@ function Ebook(vendor, id) { this.avail; // availability info for this title this.holdings = {}; // holdings info this.conns = {}; // references to Dojo event connection for performing actions with this ebook - } Ebook.prototype.getDetails = function(callback) { - var ses = dojo.cookie(this.vendor); + var ses = this.ses || dojo.cookie(this.vendor); var ebook = this; new OpenSRF.ClientSession('open-ils.ebook_api').request({ method: 'open-ils.ebook_api.title.details', @@ -29,6 +28,8 @@ Ebook.prototype.getDetails = function(callback) { console.log('title details response: ' + resp.content()); ebook.title = resp.content().title; ebook.author = resp.content().author; + if (typeof resp.content().formats !== 'undefined') + ebook.formats = resp.content().formats; return callback(ebook); } } @@ -36,7 +37,7 @@ Ebook.prototype.getDetails = function(callback) { } Ebook.prototype.getAvailability = function(callback) { - var ses = dojo.cookie(this.vendor); + var ses = this.ses || dojo.cookie(this.vendor); new OpenSRF.ClientSession('open-ils.ebook_api').request({ method: 'open-ils.ebook_api.title.availability', params: [ ses, this.id ], @@ -53,7 +54,7 @@ Ebook.prototype.getAvailability = function(callback) { } Ebook.prototype.getHoldings = function(callback) { - var ses = dojo.cookie(this.vendor); + var ses = this.ses || dojo.cookie(this.vendor); new OpenSRF.ClientSession('open-ils.ebook_api').request({ method: 'open-ils.ebook_api.title.holdings', params: [ ses, this.id ], @@ -70,11 +71,18 @@ Ebook.prototype.getHoldings = function(callback) { } Ebook.prototype.checkout = function(authtoken, patron_id, callback) { - var ses = dojo.cookie(this.vendor); + var ses = this.ses || dojo.cookie(this.vendor); var ebook = this; + // get selected checkout format (optional, used by OverDrive) + var checkout_format; + var format_selector = dojo.byId('checkout-format'); + if (format_selector) { + checkout_format = format_selector.value; + } + // perform checkout new OpenSRF.ClientSession('open-ils.ebook_api').request({ method: 'open-ils.ebook_api.checkout', - params: [ authtoken, ses, ebook.id, patron_id ], + params: [ authtoken, ses, ebook.id, patron_id, checkout_format ], async: true, oncomplete: function(r) { var resp = r.recv(); @@ -87,7 +95,7 @@ Ebook.prototype.checkout = function(authtoken, patron_id, callback) { } Ebook.prototype.placeHold = function(authtoken, patron_id, callback) { - var ses = dojo.cookie(this.vendor); + var ses = this.ses || dojo.cookie(this.vendor); var ebook = this; new OpenSRF.ClientSession('open-ils.ebook_api').request({ method: 'open-ils.ebook_api.place_hold', @@ -104,7 +112,7 @@ Ebook.prototype.placeHold = function(authtoken, patron_id, callback) { } Ebook.prototype.cancelHold = function(authtoken, patron_id, callback) { - var ses = dojo.cookie(this.vendor); + var ses = this.ses || dojo.cookie(this.vendor); var ebook = this; new OpenSRF.ClientSession('open-ils.ebook_api').request({ method: 'open-ils.ebook_api.cancel_hold', @@ -120,3 +128,43 @@ Ebook.prototype.cancelHold = function(authtoken, patron_id, callback) { }).send(); } +Ebook.prototype.download = function() { + var ses = this.ses || dojo.cookie(this.vendor); + var ebook = this; + var request_link; + var format_selector = dojo.byId('download-format'); + if (!format_selector) { + console.log('could not find a specified format for download'); + return; + } else { + request_link = format_selector.value; + } + // Request links include params like "errorpageurl={errorpageurl}" + // for redirecting the user if there's an error doing the download, etc. + // In these scenarios we always redirect the user to the current page. + // TODO: Add params to the current-page URL so that, if redirected, we + // can detect those params on page reload and show a useful message. + request_link = request_link.replace('{errorpageurl}', window.location.href); + request_link = request_link.replace('{odreadauthurl}', window.location.href); + // Now we're ready to request our download link. + new OpenSRF.ClientSession('open-ils.ebook_api').request({ + method: 'open-ils.ebook_api.title.get_download_link', + params: [ authtoken, ses, request_link ], + async: true, + oncomplete: function(r) { + var resp = r.recv(); + if (resp) { + if (resp.content().error_msg) { + console.log('download link request failed: ' + resp.content().error_msg); + } else if (resp.content().url) { + var url = resp.content().url; + console.log('download link received: ' + url); + window.location = url; + } else { + console.log('unknown error requesting download link'); + } + } + } + }).send(); +} + diff --git a/Open-ILS/web/js/ui/default/opac/ebook_api/loggedin.js b/Open-ILS/web/js/ui/default/opac/ebook_api/loggedin.js index be520d55e5..56846bc950 100644 --- a/Open-ILS/web/js/ui/default/opac/ebook_api/loggedin.js +++ b/Open-ILS/web/js/ui/default/opac/ebook_api/loggedin.js @@ -20,6 +20,7 @@ var xacts = { holds_pending: [], holds_ready: [] }; +var ebooks = []; // Ebook to perform actions on. var active_ebook; @@ -62,6 +63,10 @@ function addTotalsToPage() { // Update current page with detailed transaction info, where appropriate. function addTransactionsToPage() { + // ensure active ebook has access to session ID to avoid scoping issues during transactions + if (active_ebook && typeof active_ebook.vendor !== 'undefined') { + active_ebook.ses = active_ebook.ses || dojo.cookie(active_ebook.vendor); + } if (myopac_page) { console.log('updating page with cached transaction details, if applicable'); if (myopac_page === 'ebook_circs') @@ -113,13 +118,25 @@ function updateCheckoutView() { } else { dojo.empty('ebook_circs_main_table_body'); dojo.forEach(xacts.checkouts, function(x) { - var dl_link = '' + l_strings.download + ''; + var ebook = new Ebook(x.vendor, x.title_id); var tr = dojo.create("tr", null, dojo.byId('ebook_circs_main_table_body')); dojo.create("td", { innerHTML: x.title }, tr); dojo.create("td", { innerHTML: x.author }, tr); dojo.create("td", { innerHTML: x.due_date }, tr); - dojo.create("td", { innerHTML: dl_link}, tr); + var dl_td = dojo.create("td", null, tr); + if (x.download_url) { + dl_td.innerHTML = '' + l_strings.download + ''; + } + if (x.formats) { + var select = dojo.create("select", { id: "download-format" }, dl_td); + for (f in x.formats) { + dojo.create("option", { value: x.formats[f], innerHTML: f }, select); + } + var button = dojo.create("input", { id: "download-button", type: "button", value: l_strings.download }, dl_td); + ebook.conns.download = dojo.connect(button, 'onclick', ebook, "download"); + } // TODO: more actions (renew, checkin) + ebooks.push(ebook); }); dojo.addClass('no_ebook_circs', "hidden"); dojo.removeClass('ebook_circs_main', "hidden"); @@ -184,6 +201,8 @@ function updateHoldView() { // set up page for user to perform a checkout function getReadyForCheckout() { + if (typeof ebook_action.type === 'undefined') + return; if (typeof active_ebook === 'undefined') { console.log('No active ebook specified, cannot prepare for checkout'); dojo.removeClass('ebook_checkout_failed', "hidden"); @@ -195,6 +214,12 @@ function getReadyForCheckout() { dojo.create("td", { innerHTML: ebook.author }, tr); dojo.create("td", null, tr); dojo.create("td", { id: "checkout-button-td" }, tr); + if (typeof active_ebook.formats !== 'undefined') { + var select = dojo.create("select", { id: "checkout-format" }, dojo.byId('checkout-button-td')); + dojo.forEach(active_ebook.formats, function(f) { + dojo.create("option", { value: f.id, innerHTML: f.name }, select); + }); + } var button = dojo.create("input", { id: "checkout-button", type: "button", value: l_strings.checkout }, dojo.byId('checkout-button-td')); ebook.conns.checkout = dojo.connect(button, 'onclick', "doCheckout"); dojo.removeClass('ebook_circs_main', "hidden"); @@ -204,6 +229,8 @@ function getReadyForCheckout() { // set up page for user to place a hold function getReadyForHold() { + if (typeof ebook_action.type === 'undefined') + return; if (typeof active_ebook === 'undefined') { console.log('No active ebook specified, cannot prepare for hold'); dojo.removeClass('ebook_hold_failed', "hidden"); @@ -238,28 +265,72 @@ function cleanupAfterAction() { // check out our active ebook function doCheckout() { + var ses = dojo.cookie(active_ebook.vendor); // required when inspecting checkouts for download_url active_ebook.checkout(authtoken, patron_id, function(resp) { - if (resp.due_date) { - console.log('Checkout succeeded!'); - dojo.destroy('checkout-button'); - dojo.removeClass('ebook_checkout_succeeded', "hidden"); - // add our successful checkout to top of transaction cache - var new_xact = { - title_id: active_ebook.id, - title: active_ebook.title, - author: active_ebook.author, - due_date: resp.due_date, - download_url: '' // TODO - for OverDrive, user must "lock in" a format first! - }; - xacts.checkouts.unshift(new_xact); - cleanupAfterAction(); - } else { + if (resp.error_msg) { console.log('Checkout failed: ' + resp.error_msg); dojo.removeClass('ebook_checkout_failed', "hidden"); + return; + } + console.log('Checkout succeeded!'); + dojo.destroy('checkout-button'); + dojo.destroy('checkout-format'); // remove optional format selector + dojo.removeClass('ebook_checkout_succeeded', "hidden"); + // add our successful checkout to top of transaction cache + var new_xact = { + title_id: active_ebook.id, + title: active_ebook.title, + author: active_ebook.author, + due_date: resp.due_date, + finish: function() { + console.log('new_xact.finish()'); + xacts.checkouts.unshift(this); + cleanupAfterAction(); + // When we switch to jQuery, we can use .one() instead of .on(), + // obviating the need for an explicit disconnect here. + dojo.disconnect(active_ebook.conns.checkout); + } + }; + if (resp.download_url) { + // Use download URL from checkout response, if available. + new_xact.download_url = resp.download_url; + dojo.create("a", { href: new_xact.download_url, innerHTML: l_strings.download }, dojo.byId('checkout-button-td')); + new_xact.finish(); + } else if (typeof resp.formats !== 'undefined') { + // User must select download format from list of options. + var select = dojo.create("select", { id: "download-format" }, dojo.byId('checkout-button-td')); + for (f in resp.formats) { + dojo.create("option", { value: resp.formats[f], innerHTML: f }, select); + } + var button = dojo.create("input", { id: "download-button", type: "button", value: l_strings.download }, dojo.byId('checkout-button-td')); + active_ebook.conns.download = dojo.connect(button, 'onclick', active_ebook, "download"); + new_xact.finish(); + } else if (typeof resp.xact_id !== 'undefined') { + // No download URL provided by API checkout response. Grab fresh + // list of user checkouts from API, find the just-completed + // checkout by transaction ID, and get the download URL from that. + // We call the OpenSRF method directly because Relation.getCheckouts() + // results in scoping issues when retrieving the vendor session cookie. + new_xact.xact_id = resp.xact_id; + new OpenSRF.ClientSession('open-ils.ebook_api').request({ + method: 'open-ils.ebook_api.patron.get_checkouts', + params: [ authtoken, ses, patron_id ], + async: false, + oncomplete: function(r) { + var resp = r.recv(); + if (resp) { + dojo.forEach(resp.content(), function(x) { + if (x.xact_id === new_xact.xact_id) { + new_xact.download_url = x.download_url; + dojo.create("a", { href: new_xact.download_url, innerHTML: l_strings.download }, dojo.byId('checkout-button-td')); + return; + } + }); + new_xact.finish(); + } + } + }).send(); } - // When we switch to jQuery, we can use .one() instead of .on(), - // obviating the need for an explicit disconnect here. - dojo.disconnect(active_ebook.conns.checkout); }); } -- 2.43.2