Commit c46e6812 authored by laurent l's avatar laurent l

MyBibApp user agent is sent on webviews

parent 4aa32f5e
......@@ -22,8 +22,9 @@ import {
import { RadSideDrawer } from 'nativescript-ui-sidedrawer';
import { WebView } from 'tns-core-modules/ui/web-view';
import { OAuthWebViewClient } from './utils/oauth-webview-client';
import { MbaWebView } from './utils/mba-web-view';
import { WebViewUtils } from 'nativescript-webview-utils';
import * as imageSource from "tns-core-modules/image-source";
import * as AppVersion from 'nativescript-appversion'
export class LibraryController extends Controller {
protected _account: Account;
......@@ -337,22 +338,25 @@ export class LibraryController extends Controller {
public onWebsitePageLoaded(args) {
this._view = args.object;
let webview = args.object.getViewById("webView");
let activity_indicator = args.object.getViewById("activity-indicator");
let webview = args.object.getViewById("webView")
let activity_indicator = args.object.getViewById("activity-indicator")
this._addHeadersToWebView(webview);
new Cookies().syncHttpToWebView(webview).then(() => {
webview.src = this.get('website_url');
webview.src = this.get('website_url')
})
activity_indicator.busy = true;
activity_indicator.busy = true
webview.on(WebView.loadFinishedEvent, () => {
activity_indicator.busy = false;
});
activity_indicator.busy = false
})
}
public onOauthPageLoaded(args) {
let webview = args.object.getViewById("webView");
let oauth_web_view = OAuthWebViewClient.newWithWebView(webview);
this._doOAuthLogin(oauth_web_view);
}
......@@ -542,4 +546,13 @@ export class LibraryController extends Controller {
return this;
}
protected _addHeadersToWebView(webview): this {
let headers: Map<string, string> = new Map()
headers.set('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36 MyBibApp/' + AppVersion.getVersionNameSync())
WebViewUtils.addHeaders(webview, headers);
return this;
}
}
......@@ -14,6 +14,8 @@ export class OAuthWebViewClient {
public constructor(view: WebView) {
this._view = view;
view.android.getSettings().setUserAgentString(L('app_name'));
let WVClient = (<any>android.webkit.WebViewClient).extend({
shouldOverrideUrlLoading: (_webview, page_url) => {
if (typeof page_url === 'object') // android 7.1.1 compatibility
......
......@@ -10,38 +10,51 @@ var NSWKNavigationDelegate = (NSObject as any).extend(
_onError: null,
webViewDidStartLoad: function(webview: WKWebView) {
console.log('start Load');
return this._origDelegate.webViewDidStartLoad(webview);
},
webViewDidStartProvisionalNavigation: function(webview, navigation) {
console.log('start prov url:' + webview.URL.absoluteString);
return this._origDelegate.webViewDidStartProvisionalNavigation(webview, navigation);
},
webViewDidFailNavigationWithError: function(webview: WKWebView, navigation: WKNavigation, error: NSError) {
console.log('didFailNavigationWithError');
return this._origDelegate.webViewDidFailNavigationWithError(webview, navigation, error);
},
webViewDecidePolicyForNavigationActionDecisionHandler: function(webview: WKWebView, navigationAction: WKNavigationAction, decisionHandler: WKNavigationActionPolicy) {
console.log('decidePolicyForNavigationActionDecisionHandler:' + webview.URL.absoluteString);
webViewDecidePolicyForNavigationActionDecisionHandler: function(webview: WKWebView, navigationAction: WKNavigationAction, decisionHandler: (p1: WKNavigationActionPolicy) => void) {
if (!webview.URL)
return this._origDelegate.webViewDecidePolicyForNavigationActionDecisionHandler(webview, navigationAction, decisionHandler);
let page_url = webview.URL.absoluteString;
const user_agent = L('app_name');
if (webview.customUserAgent !== user_agent) {
webview.customUserAgent = user_agent;
const customRequest = navigationAction.request.mutableCopy();
customRequest.setValueForHTTPHeaderField('User-Agent', user_agent);
webview.loadRequest(customRequest);
decisionHandler(WKNavigationActionPolicy.Cancel);
return;
}
let matches = page_url.match(/.*token=(.*)$/);
if (matches) {
let token = matches[1];
this._onTokenSet(token);
new Cookies().clearCookies();
decisionHandler(WKNavigationActionPolicy.Cancel);
return;
}
if (page_url == 'bibapp://authorize') {
this._onError();
decisionHandler(WKNavigationActionPolicy.Cancel);
return;
}
return this._origDelegate.webViewDecidePolicyForNavigationActionDecisionHandler(webview, navigationAction, decisionHandler);
......@@ -49,7 +62,6 @@ var NSWKNavigationDelegate = (NSObject as any).extend(
webViewShouldStartLoadWithRequestNavigationType: function(webview: WKWebView, request: NSURLRequest, navigationType: number) {
console.log('start loading');
return this._origDelegate.webViewShouldStartLoadWithRequestNavigationType(webview, request, navigationType);
},
},
......
import {PortalAdapter} from './adapter';
import {Database, Account, Hold, Loan, Record, Item} from '../../models';
import {OAuthTokenNotSet} from './oauth-token-not-set';
import { PortalAdapter } from './adapter';
import { Database, Account, Hold, Loan, Record, Item } from '../../models';
import { OAuthTokenNotSet } from './oauth-token-not-set';
export class Bokeh extends PortalAdapter {
......@@ -8,18 +8,24 @@ export class Bokeh extends PortalAdapter {
return 'bokeh';
}
public getLabel(): string {
return 'Bokeh';
}
public requestUser(account: Account, action: string): Promise<any> {
const authorization = "Bearer " + account.credentials['token']
return this
.request(account,
{ url: account.getUrl() + '/api/user/' + action,
headers: {"authorization": "Bearer " + account.credentials['token']} })
.then( (response) => {
{
url: account.getUrl() + '/api/user/' + action,
headers: {
"Authorization": authorization,
"authorization": authorization // compatibility Bokeh =< 8.0.25
}
})
.then((response) => {
let datas = response.content.toJSON();
if (datas['error']) {
......@@ -36,9 +42,9 @@ export class Bokeh extends PortalAdapter {
public updateCard(account: Account): Promise<any> {
return this
.requestUser(account, 'account')
.then( (datas) => {
.then((datas) => {
account.credentials['login'] = datas['account']['login'];
let card = account.getCard();
card.setNumber(datas['account']['card']['id']);
card.setExpirationDate(datas['account']['card']['expire_at']);
......@@ -49,23 +55,23 @@ export class Bokeh extends PortalAdapter {
public fetchNovelties(account: Account, page: number): Promise<any> {
return this
.request(account,
{ url: account.getUrl() + '/recherche/simple/tri/date_creation+desc/format/json/page/' + page })
.then( (response) => {
let search_result = response.content.toJSON();
{ url: account.getUrl() + '/recherche/simple/tri/date_creation+desc/format/json/page/' + page })
.then((response) => {
let search_result = response.content.toJSON();
let records = new Array();
for(let record of search_result.records) {
for (let record of search_result.records) {
if (record.thumbnail) {
let author: string = (record.creators && record.creators.length )
let author: string = (record.creators && record.creators.length)
? record.creators[0]
: '';
records.push( new Record()
.setUrl(record.permalink)
.setThumbnailUrl(record.thumbnail)
.setTitle(record.titles[0])
.setDocType(record.type.label)
.setAuthor(author.replace('|', ' ').trim()) );
records.push(new Record()
.setUrl(record.permalink)
.setThumbnailUrl(record.thumbnail)
.setTitle(record.titles[0])
.setDocType(record.type.label)
.setAuthor(author.replace('|', ' ').trim()));
}
}
......@@ -87,8 +93,8 @@ export class Bokeh extends PortalAdapter {
public fetchItem(account, barcode): Promise<Item> {
return this
.request(account,
{ url: account.getUrl() + '/api/catalog/item/barcode/' + barcode })
.then( (response) => {
{ url: account.getUrl() + '/api/catalog/item/barcode/' + barcode })
.then((response) => {
let datas = response.content.toJSON();
let item = new Item(datas['barcode'])
item
......@@ -115,20 +121,20 @@ export class Bokeh extends PortalAdapter {
return true;
}
protected _fetchAccountLabel(account: Account): Promise<any> {
return this
.requestUser(account, 'account')
.then( (datas) => {
.then((datas) => {
return datas['account']['label'];
})
.catch( () => {
.catch(() => {
let regex = new RegExp("https?://(www\.)?([^/\.?]+)");
return regex.exec(account.getUrl())[2];
});
}
public _fetchLoans(account: Account): Promise<Array<Loan>> {
let record_url = account.getUrl() + '/recherche/viewnotice/retour_abonne/prets/id/';
let loans_url = account.getUrl() + '/abonne/prets'
......@@ -136,18 +142,18 @@ export class Bokeh extends PortalAdapter {
.requestUser(account, 'loans')
.then(
(datas) => {
return datas['loans'].map( (loan) => {
return datas['loans'].map((loan) => {
return new Loan()
.setLoanId(loan['id'])
.setTitle(loan['title'])
.setAuthor(loan['author'])
.setDateDue(loan['date_due'])
.setLibrary(loan['library'])
.setRecordId(loan['record'] ? loan['record']['id']: null)
.setRecordId(loan['record'] ? loan['record']['id'] : null)
.setRecordUrl(loan['record']
? record_url + loan['record']['id']
: loans_url)
.setRecordThumbnail(loan['record'] ? loan['record']['thumbnail']: null)
? record_url + loan['record']['id']
: loans_url)
.setRecordThumbnail(loan['record'] ? loan['record']['thumbnail'] : null)
});
});
}
......@@ -160,25 +166,25 @@ export class Bokeh extends PortalAdapter {
return this
.requestUser(account, 'holds')
.then( (datas) => {
return datas['holds'].map( (hold) => {
.then((datas) => {
return datas['holds'].map((hold) => {
return new Hold()
.setHoldId(hold['id'])
.setTitle(hold['title'])
.setAuthor(hold['author'])
.setStatus(hold['status'])
.setLibrary(hold['library'])
.setRecordId(hold['record'] ? hold['record']['id']: null)
.setRecordId(hold['record'] ? hold['record']['id'] : null)
.setRecordUrl(hold['record']
? record_url + hold['record']['id']
: holds_url)
.setRecordThumbnail(hold['record'] ? hold['record']['thumbnail']: null);
? record_url + hold['record']['id']
: holds_url)
.setRecordThumbnail(hold['record'] ? hold['record']['thumbnail'] : null);
})
});
}
protected _signIn(account): Promise<any> {
if (!account.credentials['token'])
......
declare var expect: any;
import {HTTPScenario} from './http-scenario';
import { HTTPScenario } from './http-scenario';
abstract class BokehScenario extends HTTPScenario {
protected _headers = {
"Authorization": "Bearer 123456789",
"authorization": "Bearer 123456789"
}
}
abstract class BokehWithValidToken extends HTTPScenario {
abstract class BokehWithValidToken extends BokehScenario {
public constructor(base_url: string) {
super();
this.setup(base_url);
......@@ -11,14 +20,16 @@ abstract class BokehWithValidToken extends HTTPScenario {
public setup(base_url: string) {
this
.expect({ url: base_url + '/api/user/account',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.accountJSON() });
.expect({
url: base_url + '/api/user/account',
headers: this._headers
},
{ content: this.accountJSON() });
}
public loansJSON(): string {
return JSON.stringify( { loans:[] } );
return JSON.stringify({ loans: [] });
}
......@@ -44,9 +55,11 @@ export class BokehSignInWithValidToken extends BokehWithValidToken {
public constructor(base_url: string) {
super(base_url);
this
.expect({ url: base_url + '/api/user/account',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.accountJSON() });
.expect({
url: base_url + '/api/user/account',
headers: this._headers
},
{ content: this.accountJSON() });
}
}
......@@ -55,9 +68,11 @@ export class BokehSignInWithValidToken extends BokehWithValidToken {
export class BokehAccountInfoWithValidToken extends BokehWithValidToken {
public setup(base_url) {
this
.expect({ url: base_url + '/api/user/account',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.accountJSON() });
.expect({
url: base_url + '/api/user/account',
headers: this._headers
},
{ content: this.accountJSON() });
}
}
......@@ -68,13 +83,17 @@ export class BokehLoansAndHoldsWithValidToken extends BokehWithValidToken {
public constructor(base_url: string) {
super(base_url);
this
.expect({ url: base_url + '/api/user/loans',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.loansJSON() })
.expect({
url: base_url + '/api/user/loans',
headers: this._headers
},
{ content: this.loansJSON() })
this
.expect({ url: base_url + '/api/user/holds',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.holdsJSON() });
.expect({
url: base_url + '/api/user/holds',
headers: this._headers
},
{ content: this.holdsJSON() });
}
......@@ -82,24 +101,27 @@ export class BokehLoansAndHoldsWithValidToken extends BokehWithValidToken {
return JSON.stringify(
{
loans: [
{ id: '13137_180337',
{
id: '13137_180337',
title: 'Harry Potter',
author: 'J.K. Rowling',
date_due: '2016-08-12',
loaned_by: 'Captain Haddock',
library: 'Annecy',
record: {
record: {
id: 9876,
thumbnail: 'https://images.fr/SL300_.jpg'
}
},
{ id: '13137_193847',
{
id: '13137_193847',
title: 'Millenium',
author: 'Stieg Larsson',
date_due: '2016-09-11',
loaned_by: 'Captain Haddock',
library: 'Annecy' }
library: 'Annecy'
}
]
});
}
......@@ -149,16 +171,20 @@ export class BokehLoansAndHoldsWithValidToken extends BokehWithValidToken {
export class BokehWithValidTokenButNoHTTPS extends HTTPScenario {
export class BokehWithValidTokenButNoHTTPS extends BokehScenario {
public constructor(base_url: string) {
super();
this
.expect({ url: base_url + '/api/user/account',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.errorJSON() })
.expect({ url: base_url + '/api/user/account',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.errorJSON() });
.expect({
url: base_url + '/api/user/account',
headers: this._headers
},
{ content: this.errorJSON() })
.expect({
url: base_url + '/api/user/account',
headers: this._headers
},
{ content: this.errorJSON() });
}
......@@ -173,16 +199,16 @@ export class BokehWithValidTokenButNoHTTPS extends HTTPScenario {
export class BokehWithNovelties extends HTTPScenario {
export class BokehWithNovelties extends BokehScenario {
public constructor(base_url: string) {
super();
this.expect({ url: base_url + '/recherche/simple/tri/date_creation+desc/format/json/page/1' },
{ content: this.recordsJSON() })
{ content: this.recordsJSON() })
}
public recordsJSON(): string {
return JSON.stringify (
return JSON.stringify(
{
"url": "http:\/\/bokeh-enligne.fr\/recherche\/simple\/format\/json",
"total": 6144,
......
declare var describe, expect, it, before, beforeEach, chai: any;
import {Loan, Hold, Portal, Account, Database} from '../../models/';
import {Bokeh} from '../../models/portal/bokeh';
import {BokehLoansAndHoldsWithValidToken,
BokehWithNovelties,
BokehSignInWithValidToken,
BokehAccountInfoWithValidToken,
BokehWithValidTokenButNoHTTPS} from './bokeh-fixtures';
import { Loan, Hold, Portal, Account, Database } from '../../models/';
import { Bokeh } from '../../models/portal/bokeh';
import {
BokehLoansAndHoldsWithValidToken,
BokehWithNovelties,
BokehSignInWithValidToken,
BokehAccountInfoWithValidToken,
BokehWithValidTokenButNoHTTPS
} from './bokeh-fixtures';
describe('Account on Bokeh', () => {
let db: Database;
let db: Database;
beforeEach( () => {
db = Database.open('testdb').clear();
});
beforeEach(() => {
db = Database.open('testdb').clear();
});
describe('with valid token scenario', () => {
describe('with valid token scenario', () => {
let account: Account;
beforeEach( () => {
beforeEach(() => {
account = (new Account())
.setUrl('https://bokeh-enligne.fr')
.setCredentials({token: '123456789',
portal: 'bokeh'});
.setUrl('https://bokeh-enligne.fr')
.setCredentials({
token: '123456789',
portal: 'bokeh'
});
db.save(account);
db.save(account);
});
describe('refresh account', () => {
it('db should have two loans', () => {
let http = new BokehLoansAndHoldsWithValidToken('https://bokeh-enligne.fr');
return (new Bokeh()).setHTTP(http).refresh(account).then(() => {
let loans = db.findAll(Loan).sort((a, b) => {
it('db should have two loans', () => {
let http = new BokehLoansAndHoldsWithValidToken('https://bokeh-enligne.fr');
return (new Bokeh()).setHTTP(http).refresh(account).then(() => {
let loans = db.findAll(Loan).sort((a, b) => {
return a.getDateDue().localeCompare(b.getDateDue());
});
expect(loans).to.have.lengthOf(2);
expect(loans).to.have.lengthOf(2);
expect(loans[0].getLoanId()).to.equals('13137_180337');
expect(loans[0].getTitle()).to.equals('Harry Potter');
expect(loans[0].getLibrary()).to.equals('Annecy');
......@@ -50,18 +54,18 @@ describe('Account on Bokeh', () => {
expect(account.loanEarliestDateDue()).to.equals('2016-08-12');
expect(account.daysToNextLoanAction(new Date('2016-09-02'))).to.equals(21)
expect(account.daysToNextLoanAction() > 100).to.be.true
})
})
})
})
it('db should have two holds', () => {
let http = new BokehLoansAndHoldsWithValidToken('https://bokeh-enligne.fr');
return (new Bokeh()).setHTTP(http).refresh(account).then(() => {
let holds = db.findAll(Hold).sort((a, b) => {
let http = new BokehLoansAndHoldsWithValidToken('https://bokeh-enligne.fr');
return (new Bokeh()).setHTTP(http).refresh(account).then(() => {
let holds = db.findAll(Hold).sort((a, b) => {
return a.getHoldId().localeCompare(b.getHoldId());
});
expect(holds).to.have.lengthOf(3);
expect(holds).to.have.lengthOf(3);
expect(account.numberOfHolds()).to.equals(3);
expect(holds[0].getHoldId()).to.equals('13137_180337');
expect(holds[0].getTitle()).to.equals('La poire');
......@@ -71,29 +75,29 @@ describe('Account on Bokeh', () => {
expect(holds[0].getRecordId()).to.equals(12218);
expect(holds[0].getRecordThumbnail()).to.not.exist;
expect(holds[2].getRecordThumbnail()).to.equals('https://images.fr/SL300_.jpg');
});
})
});
})
});
it('account label should be Magellan', () => {
let http = new BokehSignInWithValidToken('https://bokeh-enligne.fr');
return (new Bokeh()).setHTTP(http).signIn(account).then(() => {
let http = new BokehSignInWithValidToken('https://bokeh-enligne.fr');
return (new Bokeh()).setHTTP(http).signIn(account).then(() => {
expect(account.label).to.equals('Magellan');
});
})
});
})
it('should have Portal.updateAccountInfo to setup card', () => {
let portal = new Portal();
portal.findAdapterIdentifiedBy('bokeh').setHTTP(new BokehAccountInfoWithValidToken('https://bokeh-enligne.fr'));
return portal.updateAccountInfo(account).then( () => {
return portal.updateAccountInfo(account).then(() => {
let card = account.getCard();
expect(account.credentials['login']).to.equals('magg');
expect(card.getNumber()).to.equals('9834');
expect(card.getExpirationDate()).to.equals('2018-07-21');
});
})
});
})
it('should fetch two novelties', () => {
......@@ -104,29 +108,31 @@ describe('Account on Bokeh', () => {
expect(records[0].getThumbnailUrl()).to.equals('http://bokeh-enligne.fr/userfiles/album/28486/thumb_28486_1.200.jpg');
expect(records[0].getTitle()).to.equals('Entitlement');
expect(records[0].getAuthor()).to.equals('D. Scott Soriano');
});
});
})
});
describe('with server HTTPS error', () => {
it('should raise error', () => {
let account = (new Account())
.setLabel('Bokeh')
.setUrl('http://bokeh-enligne.fr')
.setCredentials({token: '123456789',
portal: 'bokeh'});
db.save(account);
let http = new BokehWithValidTokenButNoHTTPS('http://bokeh-enligne.fr');
return (new Bokeh()).setHTTP(http).refresh(account)
.then(() => {},
(error) => {
expect(error).to.have.property('message', 'HTTPS mandatory');
expect(error).to.have.property('name', 'invalid_request');
});
});
});
let account = (new Account())
.setLabel('Bokeh')
.setUrl('http://bokeh-enligne.fr')
.setCredentials({
token: '123456789',
portal: 'bokeh'
});
db.save(account);
let http = new BokehWithValidTokenButNoHTTPS('http://bokeh-enligne.fr');
return (new Bokeh()).setHTTP(http).refresh(account)
.then(() => { },
(error) => {
expect(error).to.have.property('message', 'HTTPS mandatory');
expect(error).to.have.property('name', 'invalid_request');
});
});
});
});
......@@ -6530,6 +6530,11 @@
}
}
},
"nativescript-webview-utils": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/nativescript-webview-utils/-/nativescript-webview-utils-3.0.1.tgz",
"integrity": "sha512-4NZjascEnaT0zHLSbhnZ5RCafCD8rbqMvF4vtrGNbkhn61L+QT2q6wj4sS2vVZyPHePrmgT3+zHXFx1nFRUmnw=="
},
"nativescript-worker-loader": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/nativescript-worker-loader/-/nativescript-worker-loader-0.8.1.tgz",
......
......@@ -33,6 +33,7 @@
"nativescript-ui-listview": "^3.8.0",
"nativescript-ui-sidedrawer": "^5.1.0",
"nativescript-unit-test-runner": "^0.6.3",
"nativescript-webview-utils": "^3.0.1",
"nativescript-zxing": "git+https://github.com/Afibre/nativescript-zxing.git",
"papaparse": "^4.3.7",
"tns-core-modules": "^5.4.1",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment