├── .gitignore
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── bin
├── clean
├── clean-appium
├── create-appium
├── install
├── login
├── project_files
│ ├── SpotifyPlugin-Prefix.pch
│ └── xcode
│ │ ├── project.pbxproj
│ │ └── xcshareddata
│ │ └── xcschemes
│ │ └── SpotifyPlugin.xcscheme
├── shared
│ └── env.js
├── test-all
├── test-appium
├── test-ios
└── test-js
├── install.sh
├── package.json
├── plugin.xml
├── src
└── ios
│ ├── Spotify.framework
│ └── .gitkeep
│ ├── SpotifyAudioPlayer.h
│ ├── SpotifyAudioPlayer.m
│ ├── SpotifyPlugin.h
│ └── SpotifyPlugin.m
├── test
├── appium
│ ├── specs
│ │ ├── audio-player.js
│ │ ├── specs.js
│ │ └── support
│ │ │ ├── appium-servers.js
│ │ │ ├── logging.js
│ │ │ └── setup.js
│ ├── src
│ │ └── ios
│ │ │ ├── AutomationCoreAudioController.h
│ │ │ ├── AutomationCoreAudioController.m
│ │ │ ├── SpotifyAudioPlayer+Testing.h
│ │ │ └── SpotifyAudioPlayer+Testing.m
│ └── www
│ │ ├── css
│ │ ├── bootstrap-theme.css
│ │ ├── bootstrap-theme.min.css
│ │ ├── bootstrap.css
│ │ ├── bootstrap.min.css
│ │ └── main.css
│ │ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ └── glyphicons-halflings-regular.woff
│ │ └── index.html
├── ios
│ ├── MockCommandDelegate.h
│ ├── MockCommandDelegate.m
│ ├── SpotifyAudioPlayer+Mock.h
│ ├── SpotifyAudioPlayer+Mock.m
│ ├── SpotifyPluginTests-Info.plist
│ ├── SpotifyPluginTests.m
│ ├── TestData
│ │ ├── profile.json
│ │ ├── refresh.json
│ │ ├── refresh_failed.json
│ │ └── session.json
│ ├── testShared.h
│ └── testShared.m
└── js
│ ├── index.html
│ └── spec
│ ├── lib
│ ├── event-dispatcher.test.js
│ ├── remote.test.js
│ └── utils.test.js
│ ├── shared
│ └── paginate.js
│ ├── spotify
│ ├── audio-player.test.js
│ └── auth.test.js
│ └── support
│ ├── android
│ └── .gitkeep
│ ├── ios
│ └── mock-exec.js
│ └── test-helper.js
└── www
├── lib
├── event-dispatcher.js
├── remote.js
└── utils.js
├── spotify
├── audio-player.js
├── auth.js
└── request.js
└── vendors
└── reqwest
├── reqwest.js
└── reqwest.min.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | .DS_Store
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | *.xcworkspace
13 | !default.xcworkspace
14 | xcuserdata
15 | profile
16 | *.moved-aside
17 | DerivedData
18 | .idea/
19 | *.xcodeproj
20 |
21 | # Node.js
22 | node_modules
23 |
24 | # Dev Dependencies
25 | .env
26 | .tmp
27 | /SpotifyPlugin-Prefix.pch
28 | development/
29 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | before_install:
3 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet
4 | install:
5 | - make prepare
6 | script:
7 | - make test-ios
8 | - make test-js
9 | after_script:
10 | - make clean
11 | env:
12 | global:
13 | - secure: bELi+wLvamcuB1w7IPY6k0cT4puuHjXA0qbNF3O7HKkk76XJFYMSGNcUnkncNJwvLL4/sF3yZROVjWj344yaaNLD6vSa0qMyNukiHEZ2KpTdv1IyfAjTdYJZ/58lJL6RW61ZJzQERRTF2zh2cE7gMlgZxqqHCTwoUOqd/wHMHEk=
14 | - secure: F7N5pyuRObdwDaS220E9/4nOjJPuXsrOkwdNa9Xy8VGHGOK4weoL2siv4KtzPGGoYqq36b9guiN4B5WOlDRBbuM3xmCsFFlE9GcN2uExAWoXaUKV9xzEGT/l/VVpdWu+bXOtvDRw/6uloWOX4Ti+gBPyREbEyIcgk5E7/gZ2wEs=
15 | - secure: anNBboQbuuPoGQgsbEex8y7ZqSryKnc2dGVpb36RA72xzye0YnmR6bfwM2k9nwqeWtDXONjyVjcfpM3WeHtw5xlwXo0GNDlZhEjvRWqKpxm7c10zjlLAd1IFTcICfGHo7ZoLjjWYDZQhoFMXEHw0otGxY9wVB2cWN8mrEqk/whI=
16 | - secure: DYsS+fkR3z+uPifoICq64cP3RtDZT/A07/aTzjqCM/fTm5fZuZ7YEAsYlGZNA0l4ym+lAi0xeizr5vI0zKK2UVnSKoqXQiKkD8TTycSdJyfJlNpHFxpYEAwEkNVy7LqArvNOclqMFkNBKcUK+nHCQEcmZ+pQgmHJnHN0VW+tcxc=
17 | - secure: gs4UxLMyBg+wiYR8idsgyHHNv+qH/HmPzUcAarxKRXiqvanf+PU8HACcYvigBvSLtjbugKSZGITF2M/RvqtVPOaY8/Syc92UwqWSawfylkMH4qZGIjslPs1tOMC6aDXv3ilIn5/NySpY0h5JYnLOQNpduqvwH4s7+Fpaw7v8eTI=
18 | - SAUCE=1
19 | - SPOTIFY_SCOPE="playlist-read-private playlist-modify-public playlist-modify-private streaming user-library-read user-library-modify user-read-private user-read-email"
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Tim Flapper
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | @export PLUGIN_ENV='test'; bin/test-all
3 |
4 | test-ios:
5 | @bin/test-ios
6 |
7 | test-js:
8 | @export PLUGIN_ENV='test'; bin/test-js
9 |
10 | test-appium:
11 | @bin/test-appium
12 |
13 | prepare: npm development
14 |
15 | clean:
16 | @bin/clean
17 | @bin/clean-appium
18 |
19 | npm:
20 | @npm install
21 |
22 | appium:
23 | @bin/create-appium
24 |
25 | development:
26 | @bin/install
27 |
28 | .PHONY: test
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cordova Spotify Plugin
2 |
3 | [](https://travis-ci.org/timflapper/cordova-spotify-plugin)
4 |
5 | This plugin provides a javascript API to Spotify's iOS SDK for Cordova applications.
6 |
7 | _Android integration is planned for a future release_
8 |
9 | ## Installation
10 |
11 | 1. Install the plugin:
12 |
13 | `cordova plugin add com.timflapper.spotify`
14 |
15 | 2. Add the iOS platform to your project (if needed):
16 |
17 | `cordova platform add ios`
18 |
19 | 3. The install script will start automatically. It will do two things:
20 | - Ask you for a [custom URL scheme](http://bit.ly/1u11ZUz).
21 | - Download and extract the Spotify iOS SDK.
22 |
23 | That's it!
24 |
25 | ## API
26 |
27 | Documentation can be found [here](https://github.com/timflapper/cordova-plugin-spotify/wiki/API)
28 |
29 | ## Setting up a token exchange service
30 |
31 | You can use the Ruby script that is included in the Spotify iOS SDK Demo Projects for development:
32 |
33 | - [Download the Spotify iOS SDK](https://github.com/spotify/ios-sdk/releases)
34 | - Follow the instructions from the [Spotify iOS SDK beginner's tutorial](https://developer.spotify.com/technologies/spotify-ios-sdk/tutorial/).
35 |
36 |
37 | ## Non-interactive installation
38 |
39 | To avoid being prompted for the [custom URL scheme](http://bit.ly/1u11ZUz),
40 | you can alternatively provide it in an environment variable:
41 | ```
42 | export CORDOVA_SPOTIFY_URL_SCHEME=somecustomscheme
43 | cordova plugin add com.timflapper.spotify
44 | ```
45 |
46 |
47 | ## License
48 |
49 | [MIT](LICENSE)
50 |
--------------------------------------------------------------------------------
/bin/clean:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var fs = require('fs')
4 | , path = require('path')
5 | , shell = require('shelljs');
6 |
7 | var projectDir = path.resolve(__dirname, '..');
8 | var devDir = path.join(projectDir, 'development');
9 | var xcodeProjTarget = path.join(projectDir, 'SpotifyPlugin.xcodeproj');
10 |
11 | shell.rm('-rf', devDir);
12 | shell.rm('-rf', xcodeProjTarget);
13 | shell.rm(path.join(projectDir, 'SpotifyPlugin-Prefix.pch'));
14 |
--------------------------------------------------------------------------------
/bin/clean-appium:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var fs = require('fs')
4 | , path = require('path')
5 | , shell = require('shelljs');
6 |
7 | var tmpDir = path.join(__dirname, '..', 'tmp');
8 |
9 | shell.rm('-rf', tmpDir);
10 |
--------------------------------------------------------------------------------
/bin/create-appium:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var cordova = require('cordova')
4 | , env = require('./shared/env')
5 | , fs = require('fs')
6 | , path = require('path')
7 | , shell = require('shelljs');
8 |
9 | env.loadEnvVariables(path.join(process.cwd(), '.env'));
10 |
11 | var workingDir = process.cwd()
12 | , pluginDir = path.join(__dirname, '..')
13 | , testPluginDir = path.join(pluginDir, 'test', 'appium')
14 | , tmpDir = path.join(pluginDir, '.tmp')
15 | , tmpAppDir = path.join(tmpDir, 'TestApp')
16 | , tmpPluginDir = path.join(tmpDir, 'SpotifyPlugin')
17 | , wwwDir = path.join(tmpAppDir, 'www')
18 | , zipFilePath = path.join(tmpDir, 'AcceptanceTest.zip');
19 |
20 | var accessToken = shell.exec('SPOTIFY_USERNAME='+process.env.SPOTIFY_USERNAME + ' SPOTIFY_PASSWORD=' + process.env.SPOTIFY_PASSWORD + ' SPOTIFY_CLIENTID=' + process.env.SPOTIFY_CLIENTID + ' ' + path.join(__dirname, 'login'), {silent:true}).output;
21 |
22 | var expirationDate = (new Date().getTime()+3600).toString()
23 | , origIndexHTML = fs.readFileSync(path.join(testPluginDir, 'www', 'index.html'), {encoding: 'utf8'})
24 | , testIndexHTML = origIndexHTML
25 | .replace('###USERNAME###', process.env.SPOTIFY_USERNAME)
26 | .replace('###CREDENTIAL###', accessToken)
27 | .replace('###EXPIRATION_DATE###', expirationDate);
28 |
29 | var testOnlyPluginXMLTags = ' \n' +
30 | ' \n' +
31 | ' \n' +
32 | ' \n'
33 | , originalPluginXML = fs.readFileSync(path.join(pluginDir, 'plugin.xml'), {encoding: 'utf8'})
34 | , testPluginXML = originalPluginXML.replace(
35 | '\n',
36 | '\n' +
37 | testOnlyPluginXMLTags);
38 |
39 | shell.rm('-rf', tmpDir);
40 | shell.mkdir('-p', tmpAppDir);
41 | shell.mkdir('-p', tmpPluginDir);
42 |
43 | fs.writeFileSync(path.join(tmpPluginDir, 'plugin.xml'), testPluginXML);
44 |
45 | shell.cp('-rf', path.join(pluginDir, 'www'), tmpPluginDir);
46 | shell.cp('-rf', path.join(pluginDir, 'src'), tmpPluginDir);
47 | shell.cp('-f', path.join(testPluginDir, 'src', 'ios', '*'), path.join(tmpPluginDir, 'src', 'ios'));
48 |
49 | cordova.raw.create(tmpAppDir, 'com.timflapper.spotify.acceptance-test', 'AcceptanceTest')
50 | .then(function() {
51 | shell.rm('-rf', path.join(wwwDir, '*'));
52 | shell.cp('-rf', path.join(testPluginDir, 'www', '*'), wwwDir);
53 |
54 | fs.writeFileSync(path.join(wwwDir, 'index.html'), testIndexHTML);
55 | shell.cd(tmpAppDir);
56 | })
57 | .then(function() {
58 | return cordova.raw.plugin('add', tmpPluginDir);
59 | })
60 | .then(function() {
61 | shell.cp('-rf', path.join(pluginDir, 'development', 'Spotify.framework'), path.join(tmpAppDir, 'plugins', 'com.timflapper.spotify', 'src', 'ios'));
62 | return cordova.raw.platform('add', 'ios');
63 | })
64 | .then(function() {
65 | return cordova.raw.build({platforms: ['ios']});
66 | })
67 | .then(function() {
68 | if (process.env.SAUCE) {
69 | shell.cd('platforms/ios/build/emulator');
70 | shell.exec('zip -r '+zipFilePath+' AcceptanceTest.app/', {silent: true});
71 | shell.exec('curl -u '+process.env.SAUCE_USERNAME+':'+process.env.SAUCE_ACCESS_KEY+' -X POST "http://saucelabs.com/rest/v1/storage/'+process.env.SAUCE_USERNAME+'/AcceptanceTest.zip?overwrite=true" -H "Content-Type: application/octet-stream" --data-binary @'+zipFilePath, { silent: true });
72 | }
73 | });
74 |
--------------------------------------------------------------------------------
/bin/install:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var path = require('path')
4 | , fs = require('fs')
5 | , request = require('request')
6 | , shell = require('shelljs')
7 | , zlib = require('zlib')
8 | , tar = require('tar');
9 |
10 | var projectDir = path.resolve(__dirname, '..');
11 |
12 | var devDir = path.join(projectDir, 'development');
13 | var cordovaDir = path.join(devDir, 'CordovaLib');
14 | var spotifyDir = path.join(devDir, 'Spotify.framework');
15 | var OHHTTPStubsDir = path.join(devDir, 'OHHTTPStubs');
16 |
17 | var tmpDir = path.join(devDir, 'tmp');
18 | var cordovaTmpDir = path.join(tmpDir, 'cordova-ios-3.7.0/CordovaLib');
19 | var spotifyTmpDir = path.join(tmpDir, 'ios-sdk-beta-6/Spotify.framework');
20 | var OHHTTPStubsTmpDir = path.join(tmpDir, 'OHHTTPStubs-3.1.6/OHHTTPStubs');
21 |
22 | var pchFile = path.join(__dirname, 'project_files', 'SpotifyPlugin-Prefix.pch');
23 | var xcodeProj = path.join(__dirname, 'project_files', 'xcode', '*');
24 | var xcodeProjTarget = path.join(projectDir, 'SpotifyPlugin.xcodeproj');
25 |
26 | shell.rm('-rf', devDir);
27 | shell.mkdir('-p', tmpDir);
28 |
29 | downloadCordovaLib();
30 |
31 | function downloadCordovaLib() {
32 | var size;
33 | if (fs.existsSync(cordovaDir)) {
34 | console.log('CordovaLib exists');
35 |
36 | return downloadSpotifyFramework();
37 | }
38 |
39 | console.log('Downloading CordovaLib');
40 |
41 | var req = request.get({
42 | url: 'https://github.com/apache/cordova-ios/archive/3.7.0.tar.gz'
43 | }, function(err, res, body) {
44 | if (err || res.statusCode != 200) {
45 | shell.rm('-rf', tmpDir);
46 | } else {
47 | size = body.length;
48 | }
49 | });
50 |
51 | req.pipe(zlib.createUnzip())
52 | .pipe(tar.Extract({path:tmpDir}))
53 | .on('error', function(err) {
54 | shell.rm('-rf', tmpDir);
55 | })
56 | .on('end', function() {
57 | shell.mv('-f', cordovaTmpDir, devDir);
58 | downloadSpotifyFramework();
59 | });
60 | }
61 |
62 | function downloadSpotifyFramework() {
63 | var size;
64 | if (fs.existsSync(spotifyDir)) {
65 | console.log('Spotify.framework exists');
66 |
67 | return downloadOHHTTPStubs();
68 | }
69 |
70 | console.log('Downloading Spotify.framework');
71 |
72 | var req = request.get({
73 | url: 'https://github.com/spotify/ios-sdk/archive/beta-6.tar.gz'
74 | }, function(err, res, body) {
75 | if (err || res.statusCode != 200) {
76 | shell.rm('-rf', tmpDir);
77 | } else {
78 | size = body.length;
79 | }
80 | });
81 |
82 | req.pipe(zlib.createUnzip())
83 | .pipe(tar.Extract({path:tmpDir}))
84 | .on('error', function(err) {
85 | shell.rm('-rf', tmpDir);
86 | })
87 | .on('end', function() {
88 | shell.mv('-f', spotifyTmpDir, devDir);
89 | downloadOHHTTPStubs();
90 | });
91 | }
92 |
93 | function downloadOHHTTPStubs() {
94 | var size;
95 | if (fs.existsSync(OHHTTPStubsDir)) {
96 | console.log('OHHTTPStubs exists');
97 |
98 | return createXcodeProj();
99 | }
100 |
101 | console.log('Downloading OHHTTPStubs');
102 |
103 | var req = request.get({
104 | url: 'https://github.com/AliSoftware/OHHTTPStubs/archive/3.1.6.tar.gz'
105 | }, function(err, res, body) {
106 | if (err || res.statusCode != 200) {
107 | shell.rm('-rf', tmpDir);
108 | } else {
109 | size = body.length;
110 | }
111 | });
112 |
113 | req.pipe(zlib.createUnzip())
114 | .pipe(tar.Extract({path:tmpDir}))
115 | .on('error', function(err) {
116 | shell.rm('-rf', tmpDir);
117 | })
118 | .on('end', function() {
119 | shell.mv('-f', OHHTTPStubsTmpDir, devDir);
120 | createXcodeProj();
121 | });
122 | }
123 |
124 | function createXcodeProj() {
125 | shell.rm('-rf', tmpDir);
126 | shell.cp('-f', pchFile, projectDir);
127 | shell.cp('-rf', xcodeProj, xcodeProjTarget);
128 |
129 | console.log('done');
130 | }
131 |
--------------------------------------------------------------------------------
/bin/login:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var https = require('https')
4 | , http = require('http');
5 |
6 | var scope = encodeURIComponent(process.env.SPOTIFY_SCOPE || 'streaming')
7 | , redirectUri = process.env.SPOTIFY_REDIRECT_URI || 'http://localhost:1337/'
8 | , encodedRedirectUri = encodeURIComponent(redirectUri);
9 |
10 | var spAc, spDc, csrfToken;
11 |
12 | var url = 'https://accounts.spotify.com/nl/login?continue=https:%2F%2Faccounts.spotify.com%2Fnl%2Fauthorize%3Fclient_id%3D' + process.env.SPOTIFY_CLIENTID + '%26response_type%3Dtoken%26redirect_uri%3D'+encodedRedirectUri+'%26scope%3D'+scope;
13 |
14 | var options = {
15 | hostname: 'accounts.spotify.com',
16 | port: 443,
17 | path: '/nl/login?continue=https:%2F%2Faccounts.spotify.com%2Fnl%2Fauthorize%3Fclient_id%3D' + process.env.SPOTIFY_CLIENTID + '%26response_type%3Dtoken%26redirect_uri%3D'+encodedRedirectUri+'%26scope%3D'+scope,
18 | method: 'GET',
19 | headers: {
20 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
21 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36'
22 | }
23 | };
24 |
25 | var req = https.request(options, onUrlResult);
26 |
27 | req.on('error', function(e) {
28 | console.error(e);
29 | });
30 |
31 | req.end();
32 |
33 | function onUrlResult(res) {
34 | res.headers['set-cookie'].forEach(function(item, index) {
35 | if ((matches = /^csrf_token\=([^;]+)/.exec(item)) !== null) {
36 | csrfToken = matches[1];
37 |
38 | doLogin(csrfToken);
39 | }
40 | });
41 |
42 | res.on('data', function(d) {});
43 | }
44 |
45 | function doLogin(csrfToken) {
46 | var data = 'username='+process.env.SPOTIFY_USERNAME+'&password='+process.env.SPOTIFY_PASSWORD+'&csrf_token='+csrfToken;
47 |
48 | var options = {
49 | hostname: 'accounts.spotify.com',
50 | port: 443,
51 | path: '/api/login',
52 | method: 'POST',
53 | headers: {
54 | 'Accept': 'application/json, text/plain, */*',
55 | 'Host': 'accounts.spotify.com',
56 | 'Origin': 'https://accounts.spotify.com',
57 | 'Referer': 'https://accounts.spotify.com/nl/login?continue=https:%2F%2Faccounts.spotify.com%2Fnl%2Fauthorize%3Fclient_id%3D' + process.env.SPOTIFY_CLIENTID + '%26response_type%3Dtoken%26redirect_uri%3D'+encodedRedirectUri+'%26scope%3D'+scope,
58 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36',
59 | 'Cookie': 'csrf_token='+csrfToken+'; fb_continue=https%3A%2F%2Faccounts.spotify.com%2Fnl%2Fauthorize%3Fclient_id%3D' + process.env.SPOTIFY_CLIENTID + '%26response_type%3Dtoken%26redirect_uri%3D'+encodedRedirectUri+'%26scope%3D'+scope+'%26show_dialog%3Dtrue; _ga=GA1.2.1205302991.1415300731; _gat=1',
60 | 'Connection': 'keep-alive',
61 | 'Content-Type': 'application/x-www-form-urlencoded'
62 | }
63 | };
64 |
65 | var loginReq = https.request(options, onLoginResult);
66 |
67 | loginReq.on('error', function(e) {
68 | console.log(e);
69 | });
70 |
71 | loginReq.write(data);
72 | loginReq.end();
73 | }
74 |
75 | function onLoginResult(res) {
76 | res.headers['set-cookie'].forEach(function(item, index) {
77 | if ((matches = /^sp_ac\=([^;]+)/.exec(item)) !== null) {
78 | spAc = matches[1];
79 | }
80 |
81 | if ((matches = /^sp_dc\=([^;]+)/.exec(item)) !== null) {
82 | spDc = matches[1];
83 | }
84 |
85 | if ((matches = /^csrf_token\=([^;]+)/.exec(item)) !== null) {
86 | csrfToken = matches[1];
87 | }
88 | });
89 |
90 | res.on('data', function(d) {});
91 |
92 | doAuthorize();
93 | }
94 |
95 | function doAuthorize() {
96 | var options = {
97 | hostname: 'accounts.spotify.com',
98 | port: 443,
99 | path: '/nl/authorize?client_id=' + process.env.SPOTIFY_CLIENTID + '&response_type=token&redirect_uri='+redirectUri+'&scope='+scope,
100 | method: 'GET',
101 | headers: {
102 | 'Accept': 'application/json, text/plain, */*',
103 | 'Host': 'accounts.spotify.com',
104 | 'Origin': 'https://accounts.spotify.com',
105 | 'Referer': 'https://accounts.spotify.com/nl/login?continue=https:%2F%2Faccounts.spotify.com%2Fnl%2Fauthorize%3Fclient_id%3D' + process.env.SPOTIFY_CLIENTID + '%26response_type%3Dtoken%26redirect_uri%3D'+encodedRedirectUri+'%26scope%3D'+scope,
106 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36',
107 | 'Cookie': '_gat=1; sp_ac='+spAc+'; sp_dc='+spDc+'; _ga=GA1.2.1293684910.1415403317; csrf_token='+csrfToken,
108 | 'Connection': 'keep-alive',
109 | }
110 | };
111 |
112 | var loginReq = https.request(options, onAuthorize);
113 |
114 | loginReq.on('error', function(e) {
115 | console.log(e);
116 | });
117 |
118 | loginReq.end();
119 | };
120 |
121 | function onAuthorize(res) {
122 | var data = '';
123 |
124 | res.headers['set-cookie'].forEach(function(item, index) {
125 | if ((matches = /^csrf_token\=([^;]+)/.exec(item)) !== null) {
126 | csrfToken = matches[1];
127 | }
128 | });
129 |
130 | res.setEncoding('utf8');
131 | res.on('data', function(d) {
132 | data += d;
133 | });
134 |
135 | res.on('end', function() {
136 | onAuthorizeResponse(data);
137 | });
138 | }
139 |
140 | function onAuthorizeResponse(data) {
141 | if (extractTokenFromData(data)) return;
142 |
143 | var cookies = [
144 | '_gat=1',
145 | 'sp_ac='+spAc,
146 | 'sp_dc='+spDc,
147 | 'csrf_token='+csrfToken,
148 | '_ga=GA1.2.1596465940.1415422830'
149 | ].join('; ');
150 |
151 | acceptApplicationAccess(cookies);
152 | }
153 |
154 | function acceptApplicationAccess(cookies) {
155 | var data = 'client_id=' + process.env.SPOTIFY_CLIENTID + '&response_type=token&redirect_uri='+encodedRedirectUri+'&scope='+scope+'&csrf_token=' + csrfToken;
156 |
157 | var options = {
158 | hostname: 'accounts.spotify.com',
159 | port: 443,
160 | path: 'https://accounts.spotify.com/nl/authorize/accept',
161 | method: 'POST',
162 | headers: {
163 | 'Accept': 'application/json, text/plain, */*',
164 | 'Host': 'accounts.spotify.com',
165 | 'Origin': 'https://accounts.spotify.com',
166 | 'Referer': 'https://accounts.spotify.com/nl/authorize?client_id=' + process.env.SPOTIFY_CLIENTID + '&response_type=token&redirect_uri=' + encodedRedirectUri + '&scope=' + scope,
167 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36',
168 | 'Cookie': cookies,
169 | 'Connection': 'keep-alive',
170 | 'Content-Type': 'application/x-www-form-urlencoded'
171 | }
172 | };
173 |
174 | var acceptReq = https.request(options, onAccept);
175 |
176 | acceptReq.on('error', function(e) {
177 | console.log(e);
178 | });
179 |
180 | acceptReq.write(data);
181 | acceptReq.end();
182 | }
183 |
184 | function onAccept(res) {
185 | var data = '';
186 |
187 | res.setEncoding('utf8');
188 | res.on('data', function(d) {
189 | data += d;
190 | });
191 |
192 | res.on('end', function() {
193 | onAcceptResponse(data);
194 | });
195 | }
196 |
197 | function onAcceptResponse(data) {
198 | if (extractTokenFromData(data)) return;
199 |
200 | process.stdout.write('Unable to grab token');
201 | }
202 |
203 | function extractTokenFromData(data) {
204 | var obj = JSON.parse(data);
205 |
206 | if (obj.hasOwnProperty('redirect')) {
207 | var token = /#access_token\=([^&]+)/.exec(obj.redirect)[1];
208 |
209 | registerToken(token);
210 | return true;
211 | }
212 |
213 | return false;
214 | }
215 |
216 | function registerToken(token) {
217 | process.stdout.write(token);
218 | }
219 |
--------------------------------------------------------------------------------
/bin/project_files/SpotifyPlugin-Prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #endif
4 |
--------------------------------------------------------------------------------
/bin/project_files/xcode/xcshareddata/xcschemes/SpotifyPlugin.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
61 |
62 |
68 |
69 |
70 |
71 |
72 |
73 |
79 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/bin/shared/env.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | module.exports = {
4 | loadEnvVariables: function(fn) {
5 | if (fs.existsSync(fn)) {
6 | fs.readFileSync(fn, {encoding: 'utf8'})
7 | .split('\n')
8 | .forEach(function(item) {
9 | var env = item.split('=')
10 | , key = env[0], value = env[1];
11 | process.env[key] = process.env[key] || value;
12 | });
13 | }
14 | },
15 | setupEnvVariablesForTestScript: function(keys) {
16 | var enabledVars = [];
17 |
18 | if (process.env.SAUCE)
19 | keys = keys.concat(['SAUCE_USERNAME', 'SAUCE_ACCESS_KEY']);
20 |
21 | keys.forEach(function(key, index, arr) {
22 | if (process.env[key])
23 | enabledVars.push(key+'='+process.env[key]);
24 | });
25 |
26 | return enabledVars;
27 | },
28 | setupTestCommand: function(env, framework, script) {
29 | return env.concat([framework, script]).join(' ');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/bin/test-all:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | TEST_EXIT_CODE=0
4 |
5 | ./bin/test-ios
6 |
7 | EC=$?
8 | if [ $EC -ne 0 ]; then
9 | TEST_EXIT_CODE=$(($TEST_EXIT_CODE + $EC))
10 | fi
11 |
12 | ./bin/test-js
13 | EC=$?
14 | if [ $EC -ne 0 ]; then
15 | TEST_EXIT_CODE=$(($TEST_EXIT_CODE + $EC))
16 | fi
17 |
18 | exit $TEST_EXIT_CODE
19 |
--------------------------------------------------------------------------------
/bin/test-appium:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | console.info("\n\n\x1b[32m\x1b[1m==Appium Acceptance Tests==\x1b[0m\n");
4 |
5 | var exitCode = 1;
6 |
7 | var env = require('./shared/env')
8 | , path = require('path')
9 | , shell = require('shelljs');
10 |
11 | env.loadEnvVariables(path.join(process.cwd(), '.env'));
12 |
13 | var testEnv = env.setupEnvVariablesForTestScript(['DEV', 'VM', 'SAUCE'])
14 | , testFramework = 'node_modules/.bin/mocha'
15 | , testScript = 'test/appium/specs/specs.js'
16 | , testCommand = env.setupTestCommand(testEnv, testFramework, testScript);
17 |
18 | exitCode = shell.exec(testCommand).code;
19 |
20 | process.exit(exitCode);
21 |
--------------------------------------------------------------------------------
/bin/test-ios:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo -e "\n\n\x1b[32m\x1b[1m==XCode Unit tests==\x1b[0m\n"
4 |
5 | xcodebuild test -project SpotifyPlugin.xcodeproj -scheme SpotifyPlugin -destination 'platform=iOS Simulator,name=iPhone 6,OS=8.1' | xcpretty -tc
6 | exit ${PIPESTATUS[0]}
7 |
--------------------------------------------------------------------------------
/bin/test-js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | console.info("\n\n\x1b[32m\x1b[1m==JavaScript Unit tests==\x1b[0m\n");
4 |
5 | var cordova = require('cordova')
6 | , fs = require('fs')
7 | , path = require('path')
8 | , shell = require('shelljs')
9 | , temp = require('temp');
10 |
11 | temp.track();
12 |
13 | var old_log = console.log;
14 |
15 | var pluginDir = path.join(__dirname, '..')
16 | , tmpDir = temp.mkdirSync()
17 | , wwwDir = path.join(tmpDir, 'www');
18 |
19 | cordova.raw.create(tmpDir)
20 | .then(function() {
21 |
22 | shell.rm('-rf', path.join(wwwDir, '*'));
23 | shell.cp('-rf', path.join(pluginDir, 'test', 'js', '*'), wwwDir);
24 | shell.mkdir('-p', path.join(wwwDir, 'support', '{chai,mocha}'));
25 | shell.cp('-rf', path.join(pluginDir, 'node_modules', 'mocha', '*'), path.join(wwwDir, 'support', 'mocha'));
26 | shell.cp('-rf', path.join(pluginDir, 'node_modules', 'chai', '*'), path.join(wwwDir, 'support', 'chai'));
27 | shell.cp('-rf', path.join(pluginDir, 'node_modules', 'sinon-chai', 'lib', '*'), path.join(wwwDir, 'support', 'sinon-chai'));
28 | shell.cp('-rf', path.join(pluginDir, 'node_modules', 'sinon', 'pkg', 'sinon.js'), path.join(wwwDir, 'support', 'sinon'));
29 | shell.cd(tmpDir);
30 | })
31 | .then(function() {
32 | return cordova.raw.plugin('add', pluginDir);
33 | })
34 | .then(function() {
35 | shell.cp('-rf', path.join(pluginDir, 'development', 'Spotify.framework'), path.join(tmpDir, 'plugins', 'com.timflapper.spotify', 'src', 'ios'));
36 | return cordova.raw.platform('add', 'ios');
37 | })
38 | .then(function() {
39 | console.log = function() { };
40 | return cordova.raw.serve();
41 | })
42 | .then(function(server) {
43 | shell.cd(pluginDir);
44 | shell.exec('./node_modules/.bin/mocha-phantomjs -R dot -A "Mozilla/5.0 (iPhone; CPU iPhone OS 10_9_5 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12B411 (140430689179968)" http://localhost:8000/ios/www/', function(code, output) {
45 | console.log = old_log;
46 |
47 | server.close();
48 |
49 | process.exit(code);
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ "$PLUGIN_ENV" == "test" ]; then
4 | exit
5 | fi
6 |
7 | if [ "$CORDOVA_SPOTIFY_URL_SCHEME" == "" ]
8 | then
9 | DEFAULT_SCHEME="spotify-cordova"
10 |
11 | echo "The Spotify SDK Plugin needs a URL scheme for authentication."
12 | echo "See http://bit.ly/1u11ZUz for more information"
13 | printf "Specify your URL scheme [$DEFAULT_SCHEME]: "
14 |
15 | read CORDOVA_SPOTIFY_URL_SCHEME
16 | if [ "$CORDOVA_SPOTIFY_URL_SCHEME" == "" ]
17 | then
18 | CORDOVA_SPOTIFY_URL_SCHEME="$DEFAULT_SCHEME"
19 | fi
20 | fi
21 |
22 | echo "Writing URL scheme to plugin.xml"
23 |
24 | mv plugins/com.timflapper.spotify/plugin.xml plugins/com.timflapper.spotify/plugin.bak.xml
25 | sed "s/{{URL_SCHEME}}/$CORDOVA_SPOTIFY_URL_SCHEME/g" plugins/com.timflapper.spotify/plugin.bak.xml > plugins/com.timflapper.spotify/plugin.xml
26 | rm plugins/com.timflapper.spotify/plugin.bak.xml
27 |
28 | echo "Removing placeholder"
29 | rm -rf plugins/com.timflapper.spotify/src/ios/Spotify.framework
30 |
31 | echo "Downloading Spotify Framework"
32 | mkdir plugins/com.timflapper.spotify/src/ios/tmp
33 | cd plugins/com.timflapper.spotify/src/ios/tmp
34 | curl -OL "https://github.com/spotify/ios-sdk/archive/beta-6.tar.gz"
35 |
36 | echo "Extracting"
37 | tar xzvf beta-6.tar.gz
38 | cd ..
39 | mv tmp/ios-sdk-beta-6/Spotify.framework .
40 | rm -rf tmp
41 |
42 | echo "Finished!"
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SpotifyPlugin",
3 | "version": "0.0.0",
4 | "description": "",
5 | "repository": {
6 | "type": "git",
7 | "url": "git://github.com/timflapper/cordova-spotify-plugin.git"
8 | },
9 | "author": "Tim Flapper",
10 | "license": "MIT",
11 | "bugs": {
12 | "url": "https://github.com/timflapper/cordova-spotify-plugin/issues"
13 | },
14 | "homepage": "https://github.com/timflapper/cordova-spotify-plugin",
15 | "devDependencies": {
16 | "chai": "^1.9.2",
17 | "chai-as-promised": "^4.1.1",
18 | "colors": "^1.0.3",
19 | "cordova": "^4.0.0",
20 | "mocha": "^1.21.5",
21 | "mocha-phantomjs": "^3.5.1",
22 | "node-phantom": "^0.2.5",
23 | "phantomjs": "^1.9.11",
24 | "request": "^2.34.0",
25 | "shelljs": "^0.3.0",
26 | "sinon": "^1.10.3",
27 | "sinon-chai": "^2.6.0",
28 | "socket.io": "^0.9.17",
29 | "tar": "^0.1.19",
30 | "temp": "^0.8.1",
31 | "wd": "^0.3.9"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Spotify Cordova Plugin
4 | This plugin provides a JavaScript API for Spotify's iOS SDK.
5 | Tim Flapper
6 | MIT
7 | spotify
8 | https://github.com/timflapper/cordova-spotify-plugin
9 | https://github.com/timflapper/cordova-spotify-plugin/issues
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | CFBundleTypeRole
48 | Editor
49 | CFBundleURLName
50 | Spotify Auth
51 | CFBundleURLSchemes
52 |
53 | {{URL_SCHEME}}
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/ios/Spotify.framework/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timflapper/cordova-spotify-plugin/424f3a900c4915a0cdf0e20fccd51c7185349bb7/src/ios/Spotify.framework/.gitkeep
--------------------------------------------------------------------------------
/src/ios/SpotifyAudioPlayer.h:
--------------------------------------------------------------------------------
1 | //
2 | // SpotifyAudioPlayer.h
3 | // SpotifyPlugin
4 | //
5 | // Created by Tim Flapper on 08/05/14.
6 | //
7 | //
8 |
9 | #import
10 |
11 | @interface SpotifyAudioPlayer : SPTAudioStreamingController
12 | + (instancetype)getInstanceByID:(NSString *)ID;
13 | @property NSString *instanceID;
14 | @end
15 |
--------------------------------------------------------------------------------
/src/ios/SpotifyAudioPlayer.m:
--------------------------------------------------------------------------------
1 | //
2 | // SpotifyAudioPlayer.m
3 | // SpotifyPlugin
4 | //
5 | // Created by Tim Flapper on 08/05/14.
6 | //
7 | //
8 |
9 | #import "SpotifyAudioPlayer.h"
10 |
11 | @interface SpotifyAudioPlayer()
12 | @end
13 |
14 | @implementation SpotifyAudioPlayer
15 | static NSMutableDictionary *instances;
16 |
17 | @synthesize delegate, playbackDelegate;
18 |
19 | + (void)initialize
20 | {
21 | if (self == [SpotifyAudioPlayer class]) {
22 | instances = [NSMutableDictionary new];
23 | }
24 | }
25 |
26 | + (instancetype)getInstanceByID:(NSString *)ID
27 | {
28 | return [instances objectForKey: ID];
29 | }
30 |
31 | - (id)initWithClientId:(NSString *)clientId audioController:(SPTCoreAudioController *)audioController
32 | {
33 | self = [super initWithClientId:clientId audioController:audioController];
34 |
35 | if (self) {
36 | delegate = self;
37 | playbackDelegate = self;
38 |
39 | _instanceID = [NSString stringWithFormat:@"%d", (int)instances.count+1];
40 | [instances setObject:self
41 | forKey: _instanceID];
42 | }
43 |
44 | return self;
45 | }
46 |
47 | - (void)dispatchEvent:(NSString *)type
48 | {
49 | [self dispatchEvent:type withArguments:@[]];
50 | }
51 |
52 | - (void)dispatchEvent:(NSString *)type withArguments:(NSArray *)args
53 | {
54 |
55 | NSDictionary *info = @{@"type": type,
56 | @"args": args};
57 |
58 | NSNotification *note = [NSNotification notificationWithName:@"event" object:self userInfo:info];
59 |
60 | [[NSNotificationCenter defaultCenter] postNotification:note];
61 | }
62 |
63 | -(void)audioStreamingDidLogin:(SPTAudioStreamingController *)audioStreaming
64 | {
65 | [self dispatchEvent:@"login"];
66 | }
67 |
68 | -(void)audioStreamingDidLogout:(SPTAudioStreamingController *)audioStreaming
69 | {
70 | [self dispatchEvent:@"logout"];
71 | }
72 |
73 | -(void)audioStreamingDidLosePermissionForPlayback:(SPTAudioStreamingController *)audioStreaming
74 | {
75 | [self dispatchEvent:@"permissionLost"];
76 | }
77 |
78 | -(void)audioStreamingDidBecomeActivePlaybackDevice:(SPTAudioStreamingController *)audioStreaming
79 | {
80 | [self dispatchEvent:@"active"];
81 | }
82 |
83 | -(void)audioStreamingDidBecomeInactivePlaybackDevice:(SPTAudioStreamingController *)audioStreaming
84 | {
85 | [self dispatchEvent:@"inactive"];
86 | }
87 |
88 | -(void)audioStreamingDidEncounterTemporaryConnectionError:(SPTAudioStreamingController *)audioStreaming
89 | {
90 | [self dispatchEvent:@"temporaryConnectionError"];
91 | }
92 |
93 | -(void)audioStreaming:(SPTAudioStreamingController *)audioStreaming didChangePlaybackStatus:(BOOL)isPlaying
94 | {
95 | [self dispatchEvent:@"playbackStatus" withArguments:@[[NSNumber numberWithBool:isPlaying]]];
96 | }
97 |
98 | -(void)audioStreaming:(SPTAudioStreamingController *)audioStreaming didChangeRepeatStatus:(BOOL)isRepeated
99 | {
100 | [self dispatchEvent:@"repeatStatus" withArguments:@[[NSNumber numberWithBool:isRepeated]]];
101 | }
102 |
103 | -(void)audioStreaming:(SPTAudioStreamingController *)audioStreaming didChangeShuffleStatus:(BOOL)isShuffled
104 | {
105 | [self dispatchEvent:@"shuffleStatus" withArguments:@[[NSNumber numberWithBool:isShuffled]]];
106 | }
107 |
108 | -(void)audioStreaming:(SPTAudioStreamingController *)audioStreaming didChangeToTrack:(NSDictionary *)trackMetadata
109 | {
110 | if(trackMetadata != nil){
111 | [self dispatchEvent:@"trackChanged" withArguments:@[trackMetadata]];
112 | }
113 | }
114 |
115 | -(void)audioStreaming:(SPTAudioStreamingController *)audioStreaming didChangeVolume:(SPTVolume)volume
116 | {
117 | [self dispatchEvent:@"volumeChanged" withArguments:@[[NSNumber numberWithDouble:volume]]];
118 | }
119 |
120 | -(void)audioStreaming:(SPTAudioStreamingController *)audioStreaming didEncounterError:(NSError *)error
121 | {
122 | [self dispatchEvent:@"error" withArguments:@[error.localizedDescription]];
123 | }
124 |
125 | -(void)audioStreaming:(SPTAudioStreamingController *)audioStreaming didReceiveMessage:(NSString *)message
126 | {
127 | [self dispatchEvent:@"message" withArguments:@[message]];
128 | }
129 |
130 | -(void)audioStreaming:(SPTAudioStreamingController *)audioStreaming didSeekToOffset:(NSTimeInterval)offset
131 | {
132 | [self dispatchEvent:@"seekToOffset" withArguments:@[[NSNumber numberWithDouble:offset]]];
133 | }
134 |
135 | -(void)audioStreamingDidSkipToNextTrack:(SPTAudioStreamingController *)audioStreaming
136 | {
137 | [self dispatchEvent:@"skippedToNextTrack"];
138 | }
139 |
140 | -(void)audioStreamingDidSkipToPreviousTrack:(SPTAudioStreamingController *)audioStreaming
141 | {
142 | [self dispatchEvent:@"skippedToPreviousTrack"];
143 | }
144 |
145 | @end
146 |
--------------------------------------------------------------------------------
/src/ios/SpotifyPlugin.h:
--------------------------------------------------------------------------------
1 | //
2 | // SpotifyPlugin.h
3 | //
4 |
5 | #import "Cordova/CDV.h"
6 | #import
7 | #import "SpotifyAudioPlayer.h"
8 |
9 | NSString *dateToString(NSDate *date);
10 | NSDate *stringToDate(NSString *dateString);
11 |
12 | @interface SpotifyPlugin : CDVPlugin
13 |
14 | /* Linked to SPTAuth */
15 | - (void)authenticate:(CDVInvokedUrlCommand*)command;
16 | - (void)isSessionValid:(CDVInvokedUrlCommand*)command;
17 | - (void)renewSession:(CDVInvokedUrlCommand*)command;
18 |
19 | /* Linked to SpotifyAudioPlayer */
20 | - (void)createAudioPlayerAndLogin:(CDVInvokedUrlCommand*)command;
21 | - (void)audioPlayerLogout:(CDVInvokedUrlCommand*)command;
22 | - (void)addAudioPlayerEventListener:(CDVInvokedUrlCommand*)command;
23 | - (void)play:(CDVInvokedUrlCommand*)command;
24 | - (void)setURIs:(CDVInvokedUrlCommand*)command;
25 | - (void)playURIsFromIndex:(CDVInvokedUrlCommand*)command;
26 | - (void)queue:(CDVInvokedUrlCommand*)command;
27 | - (void)queuePlay:(CDVInvokedUrlCommand*)command;
28 | - (void)queueClear:(CDVInvokedUrlCommand*)command;
29 | - (void)stop:(CDVInvokedUrlCommand*)command;
30 | - (void)seekToOffset:(CDVInvokedUrlCommand*)command;
31 | - (void)skipNext:(CDVInvokedUrlCommand*)command;
32 | - (void)skipPrevious:(CDVInvokedUrlCommand*)command;
33 | - (void)getIsPlaying:(CDVInvokedUrlCommand*)command;
34 | - (void)setIsPlaying:(CDVInvokedUrlCommand*)command;
35 | - (void)getTargetBitrate:(CDVInvokedUrlCommand*)command;
36 | - (void)setTargetBitrate:(CDVInvokedUrlCommand*)command;
37 | - (void)getDiskCacheSizeLimit:(CDVInvokedUrlCommand*)command;
38 | - (void)setDiskCacheSizeLimit:(CDVInvokedUrlCommand*)command;
39 | - (void)getVolume:(CDVInvokedUrlCommand*)command;
40 | - (void)setVolume:(CDVInvokedUrlCommand*)command;
41 | - (void)getRepeat:(CDVInvokedUrlCommand*)command;
42 | - (void)setRepeat:(CDVInvokedUrlCommand*)command;
43 | - (void)getShuffle:(CDVInvokedUrlCommand*)command;
44 | - (void)setShuffle:(CDVInvokedUrlCommand*)command;
45 | - (void)getLoggedIn:(CDVInvokedUrlCommand*)command;
46 | - (void)getQueueSize:(CDVInvokedUrlCommand*)command;
47 | - (void)getTrackListSize:(CDVInvokedUrlCommand*)command;
48 | - (void)getTrackMetadata:(CDVInvokedUrlCommand*)command;
49 | - (void)getCurrentPlaybackPosition:(CDVInvokedUrlCommand*)command;
50 | @end
51 |
--------------------------------------------------------------------------------
/test/appium/specs/audio-player.js:
--------------------------------------------------------------------------------
1 | module.exports = function(driver) {
2 |
3 | before(function() {
4 | return driver
5 | .elementById('loginPlayerLink')
6 | .click()
7 | .waitForConditionInBrowser("document.getElementById('loginPlayerStatus').style.display !== 'none';", 30000)
8 | });
9 |
10 | it('should have a logged in player', function() {
11 | return driver
12 | .elementById('loginPlayerStatus')
13 | .text()
14 | .should.eventually.equal('success');
15 | });
16 |
17 | context('playing a song', function() {
18 | before(function() {
19 | return driver
20 | .elementById('playSongLink')
21 | .click()
22 | .waitForConditionInBrowser("document.getElementById('playSongStatus').style.display !== 'none';", 30000);
23 | });
24 |
25 | it('should be able to play a song', function() {
26 | return driver
27 | .elementById('playSongStatus')
28 | .text()
29 | .should.eventually.equal('success');
30 | });
31 |
32 | context('volume', function() {
33 | before(function() {
34 | return driver
35 | .elementById('muteLink')
36 | .click()
37 | .waitForConditionInBrowser("document.getElementById('muteStatus').style.display !== 'none';", 30000)
38 | });
39 |
40 | it('should be able to change the volume', function() {
41 | return driver
42 | .elementById('muteStatus')
43 | .text()
44 | .should.eventually.equal('success');
45 | });
46 | });
47 |
48 | context('playback position', function() {
49 | before(function() {
50 | return driver
51 | .sleep(5000)
52 | .elementById('playbackPositionLink')
53 | .click()
54 | .waitForConditionInBrowser("document.getElementById('playbackPosition').style.display !== 'none';", 30000)
55 | });
56 |
57 | it('should be able to get the playback position', function() {
58 | return driver
59 | .elementById('playbackPosition')
60 | .text()
61 | .should.eventually.match(/^\d+\.\d+$/);
62 | });
63 | })
64 | });
65 |
66 | };
67 |
--------------------------------------------------------------------------------
/test/appium/specs/specs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var wd = require('wd')
4 | , Asserter = wd.Asserter;
5 |
6 | var setup = require('./support/setup')
7 | , driver = setup.driver
8 | , endOfTestSuite = setup.endOfTestSuite
9 | , updateAllPassed = setup.updateAllPassed;
10 |
11 | describe("SpotifyPlugin", function () {
12 | this.timeout(600000);
13 |
14 | before(function () {
15 | var waitForWebViewContext = new Asserter(
16 | function(driver, cb) {
17 | return driver
18 | .contexts()
19 | .then(function(contexts) {
20 | var ret = false;
21 | if (contexts.length > 1) {
22 | ret = contexts[1];
23 | }
24 |
25 | cb(null, (ret !== false), ret);
26 | })
27 | }
28 | );
29 |
30 | return driver
31 | .setAsyncScriptTimeout(300000)
32 | .waitFor(waitForWebViewContext, 300000)
33 | .then(function(context) {
34 | if (! context)
35 | throw new Error('Context not found.');
36 |
37 | return driver.context(context);
38 | })
39 | .fail(function(error) {
40 | updateAllPassed(false);
41 |
42 | return endOfTestSuite();
43 | })
44 | .waitForConditionInBrowser("document.getElementById('page').style.display === 'block';", 30000)
45 | });
46 |
47 | after(function () {
48 | return endOfTestSuite();
49 | });
50 |
51 | afterEach(function () {
52 | return updateAllPassed(this.currentTest.state === 'passed');
53 | });
54 |
55 | require('./audio-player')(driver);
56 | });
57 |
--------------------------------------------------------------------------------
/test/appium/specs/support/appium-servers.js:
--------------------------------------------------------------------------------
1 | exports.local = {
2 | host: 'localhost',
3 | port: 4723
4 | };
5 |
6 | exports.sauce = {
7 | host: 'ondemand.saucelabs.com',
8 | port: 80,
9 | username: process.env.SAUCE_USERNAME,
10 | password: process.env.SAUCE_ACCESS_KEY
11 | };
12 |
--------------------------------------------------------------------------------
/test/appium/specs/support/logging.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | exports.configure = function (driver) {
4 | // See whats going on
5 | driver.on('status', function (info) {
6 | console.log(info.cyan);
7 | });
8 | driver.on('command', function (meth, path, data) {
9 | console.log(' > ' + meth.yellow, path.grey, data || '');
10 | });
11 | driver.on('http', function (meth, path, data) {
12 | console.log(' > ' + meth.magenta, path, (data || '').grey);
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/test/appium/specs/support/setup.js:
--------------------------------------------------------------------------------
1 | var wd = require('wd')
2 | , path = require('path')
3 |
4 | require('colors');
5 |
6 | var chai = require('chai')
7 | , chaiAsPromised = require('chai-as-promised')
8 | , should = chai.should();
9 |
10 | chai.use(chaiAsPromised);
11 | chaiAsPromised.transferPromiseness = wd.transferPromiseness;
12 |
13 | var allPassed = true;
14 |
15 | var driver = setupDriver();
16 |
17 | function setupDriver() {
18 | var appiumServers = require('./appium-servers');
19 |
20 | var serverConfig = process.env.SAUCE ?
21 | appiumServers.sauce : appiumServers.local;
22 | var driver = wd.promiseChainRemote(serverConfig);
23 |
24 | var desired = {
25 | name: 'SpotifyPlugin Test',
26 | browserName: '',
27 | 'appium-version': '1.3.1',
28 | platformName: 'iOS',
29 | platformVersion: '8.1',
30 | deviceName: 'iPhone 6',
31 | app: undefined // will be set later
32 | };
33 |
34 | if (process.env.SAUCE) {
35 | desired.app = 'sauce-storage:AcceptanceTest.zip';
36 | } else {
37 | desired.app = path.join(process.cwd(), '.tmp/TestApp/platforms/ios/build/emulator/AcceptanceTest.app');
38 | }
39 |
40 | return driver.init(desired);
41 | }
42 |
43 | function endOfTestSuite() {
44 | return driver
45 | .quit()
46 | .finally(function () {
47 | if (process.env.SAUCE) {
48 | return driver.sauceJobStatus(allPassed);
49 | }
50 | });
51 | }
52 |
53 | function updateAllPassed(state) {
54 | allPassed = allPassed && state;
55 | }
56 |
57 | exports.should = should;
58 |
59 | exports.driver = driver;
60 |
61 | exports.endOfTestSuite = endOfTestSuite;
62 |
63 | exports.updateAllPassed = updateAllPassed;
64 |
65 |
66 |
--------------------------------------------------------------------------------
/test/appium/src/ios/AutomationCoreAudioController.h:
--------------------------------------------------------------------------------
1 | //
2 | // AutomationCoreAudioController.h
3 | //
4 |
5 | #import
6 |
7 | @interface AutomationCoreAudioController : SPTCoreAudioController
8 | - (NSInteger)attemptToDeliverAudioFrames:(const void *)audioFrames ofCount:(NSInteger)frameCount streamDescription:(AudioStreamBasicDescription)audioDescription;
9 | @end
10 |
--------------------------------------------------------------------------------
/test/appium/src/ios/AutomationCoreAudioController.m:
--------------------------------------------------------------------------------
1 | //
2 | // AutomationCoreAudioController.m
3 | //
4 |
5 | #import "AutomationCoreAudioController.h"
6 |
7 | @interface AutomationCoreAudioController ()
8 |
9 | @property SPTCircularBuffer *circularBuffer;
10 | @property BOOL buffering;
11 |
12 | @property NSInteger framesSinceLastTimeUpdate;
13 | @end
14 |
15 | @implementation AutomationCoreAudioController
16 |
17 | - (id)init {
18 | self = [super init];
19 |
20 | if (self) {
21 | _framesSinceLastTimeUpdate = 0;
22 | _buffering = false;
23 | }
24 |
25 | return self;
26 | }
27 |
28 | - (NSInteger)attemptToDeliverAudioFrames:(const void *)audioFrames ofCount:(NSInteger)frameCount streamDescription:(AudioStreamBasicDescription)audioDescription
29 | {
30 | if (! self.circularBuffer) {
31 | self.circularBuffer = [[SPTCircularBuffer alloc] initWithMaximumLength:(audioDescription.mBytesPerFrame * audioDescription.mSampleRate) * 0.5];
32 | self.buffering = true;
33 | }
34 |
35 | NSUInteger bytesToAdd = audioDescription.mBytesPerPacket * frameCount;
36 |
37 | NSUInteger bytesAdded = [self.circularBuffer attemptAppendData:audioFrames ofLength:bytesToAdd chunkSize:audioDescription.mBytesPerPacket];
38 |
39 | NSUInteger framesAdded = bytesAdded / audioDescription.mBytesPerPacket;
40 |
41 | self.framesSinceLastTimeUpdate += framesAdded;
42 |
43 | if (self.framesSinceLastTimeUpdate >= 8820) {
44 | [[self delegate] coreAudioController:self didOutputAudioOfDuration:self.framesSinceLastTimeUpdate/audioDescription.mSampleRate];
45 |
46 | self.framesSinceLastTimeUpdate = 0;
47 | }
48 |
49 | if (self.buffering) {
50 | [self grabDataFromBufferOnInterval];
51 | self.buffering = false;
52 | }
53 |
54 | return framesAdded;
55 | }
56 |
57 | -(void)grabDataFromBufferOnInterval
58 | {
59 | [NSTimer scheduledTimerWithTimeInterval:0.50f
60 | target:self selector:@selector(grabDataFromBuffer:) userInfo:nil repeats:YES];
61 | }
62 |
63 | -(void)grabDataFromBuffer:(NSTimer *)timer
64 | {
65 | [self.circularBuffer clear];
66 | }
67 | @end
68 |
--------------------------------------------------------------------------------
/test/appium/src/ios/SpotifyAudioPlayer+Testing.h:
--------------------------------------------------------------------------------
1 | //
2 | // SpotifyAudioPlayer+Testing.h
3 | // SpotifyPlugin
4 | //
5 | // Created by Tim Flapper on 08/11/14.
6 | //
7 | //
8 |
9 | #import "SpotifyAudioPlayer.h"
10 |
11 | @interface SpotifyAudioPlayer (Testing)
12 | @end
13 |
--------------------------------------------------------------------------------
/test/appium/src/ios/SpotifyAudioPlayer+Testing.m:
--------------------------------------------------------------------------------
1 | //
2 | // SpotifyAudioPlayer+Testing.m
3 | // SpotifyPlugin
4 | //
5 | // Created by Tim Flapper on 08/11/14.
6 | //
7 | //
8 |
9 | #import "SpotifyAudioPlayer+Testing.h"
10 | #import "AutomationCoreAudioController.h"
11 | #import
12 |
13 | static IMP __initWithClientId_Imp;
14 | id __Swizzle_initWithClientId(SpotifyAudioPlayer *self, SEL _cmd, NSString *clientId, SPTCoreAudioController *audioController)
15 | {
16 | AutomationCoreAudioController *controller = [AutomationCoreAudioController new];
17 |
18 | return __initWithClientId_Imp(self, _cmd, clientId, controller);
19 | }
20 |
21 | @implementation SpotifyAudioPlayer (Testing)
22 | + (void)load
23 | {
24 | static dispatch_once_t onceToken;
25 | dispatch_once(&onceToken, ^{
26 | Method originalMethod = class_getInstanceMethod([self class], @selector(initWithClientId:audioController:));
27 | __initWithClientId_Imp = method_setImplementation(originalMethod, (IMP)__Swizzle_initWithClientId);
28 | });
29 | }
30 | @end
31 |
--------------------------------------------------------------------------------
/test/appium/www/css/bootstrap-theme.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.0 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | /*!
8 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=109fa5b75f9e34ab14e2)
9 | * Config saved to config.json and https://gist.github.com/109fa5b75f9e34ab14e2
10 | */
11 | .btn-default,
12 | .btn-primary,
13 | .btn-success,
14 | .btn-info,
15 | .btn-warning,
16 | .btn-danger {
17 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
18 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
19 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
20 | }
21 | .btn-default:active,
22 | .btn-primary:active,
23 | .btn-success:active,
24 | .btn-info:active,
25 | .btn-warning:active,
26 | .btn-danger:active,
27 | .btn-default.active,
28 | .btn-primary.active,
29 | .btn-success.active,
30 | .btn-info.active,
31 | .btn-warning.active,
32 | .btn-danger.active {
33 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
34 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
35 | }
36 | .btn-default .badge,
37 | .btn-primary .badge,
38 | .btn-success .badge,
39 | .btn-info .badge,
40 | .btn-warning .badge,
41 | .btn-danger .badge {
42 | text-shadow: none;
43 | }
44 | .btn:active,
45 | .btn.active {
46 | background-image: none;
47 | }
48 | .btn-default {
49 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
50 | background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
51 | background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);
52 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
53 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
54 | background-repeat: repeat-x;
55 | border-color: #dbdbdb;
56 | text-shadow: 0 1px 0 #fff;
57 | border-color: #ccc;
58 | }
59 | .btn-default:hover,
60 | .btn-default:focus {
61 | background-color: #e0e0e0;
62 | background-position: 0 -15px;
63 | }
64 | .btn-default:active,
65 | .btn-default.active {
66 | background-color: #e0e0e0;
67 | border-color: #dbdbdb;
68 | }
69 | .btn-default:disabled,
70 | .btn-default[disabled] {
71 | background-color: #e0e0e0;
72 | background-image: none;
73 | }
74 | .btn-primary {
75 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
76 | background-image: -o-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
77 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
78 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
79 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
80 | background-repeat: repeat-x;
81 | border-color: #2b669a;
82 | }
83 | .btn-primary:hover,
84 | .btn-primary:focus {
85 | background-color: #2d6ca2;
86 | background-position: 0 -15px;
87 | }
88 | .btn-primary:active,
89 | .btn-primary.active {
90 | background-color: #2d6ca2;
91 | border-color: #2b669a;
92 | }
93 | .btn-primary:disabled,
94 | .btn-primary[disabled] {
95 | background-color: #2d6ca2;
96 | background-image: none;
97 | }
98 | .btn-success {
99 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
100 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
101 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
102 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
103 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
104 | background-repeat: repeat-x;
105 | border-color: #3e8f3e;
106 | }
107 | .btn-success:hover,
108 | .btn-success:focus {
109 | background-color: #419641;
110 | background-position: 0 -15px;
111 | }
112 | .btn-success:active,
113 | .btn-success.active {
114 | background-color: #419641;
115 | border-color: #3e8f3e;
116 | }
117 | .btn-success:disabled,
118 | .btn-success[disabled] {
119 | background-color: #419641;
120 | background-image: none;
121 | }
122 | .btn-info {
123 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
124 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
125 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
126 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
127 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
128 | background-repeat: repeat-x;
129 | border-color: #28a4c9;
130 | }
131 | .btn-info:hover,
132 | .btn-info:focus {
133 | background-color: #2aabd2;
134 | background-position: 0 -15px;
135 | }
136 | .btn-info:active,
137 | .btn-info.active {
138 | background-color: #2aabd2;
139 | border-color: #28a4c9;
140 | }
141 | .btn-info:disabled,
142 | .btn-info[disabled] {
143 | background-color: #2aabd2;
144 | background-image: none;
145 | }
146 | .btn-warning {
147 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
148 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
149 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
150 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
151 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
152 | background-repeat: repeat-x;
153 | border-color: #e38d13;
154 | }
155 | .btn-warning:hover,
156 | .btn-warning:focus {
157 | background-color: #eb9316;
158 | background-position: 0 -15px;
159 | }
160 | .btn-warning:active,
161 | .btn-warning.active {
162 | background-color: #eb9316;
163 | border-color: #e38d13;
164 | }
165 | .btn-warning:disabled,
166 | .btn-warning[disabled] {
167 | background-color: #eb9316;
168 | background-image: none;
169 | }
170 | .btn-danger {
171 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
172 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
173 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
174 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
175 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
176 | background-repeat: repeat-x;
177 | border-color: #b92c28;
178 | }
179 | .btn-danger:hover,
180 | .btn-danger:focus {
181 | background-color: #c12e2a;
182 | background-position: 0 -15px;
183 | }
184 | .btn-danger:active,
185 | .btn-danger.active {
186 | background-color: #c12e2a;
187 | border-color: #b92c28;
188 | }
189 | .btn-danger:disabled,
190 | .btn-danger[disabled] {
191 | background-color: #c12e2a;
192 | background-image: none;
193 | }
194 | .thumbnail,
195 | .img-thumbnail {
196 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
197 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
198 | }
199 | .dropdown-menu > li > a:hover,
200 | .dropdown-menu > li > a:focus {
201 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
202 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
203 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
204 | background-repeat: repeat-x;
205 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
206 | background-color: #e8e8e8;
207 | }
208 | .dropdown-menu > .active > a,
209 | .dropdown-menu > .active > a:hover,
210 | .dropdown-menu > .active > a:focus {
211 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
212 | background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
213 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
214 | background-repeat: repeat-x;
215 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
216 | background-color: #357ebd;
217 | }
218 | .navbar-default {
219 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
220 | background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
221 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
222 | background-repeat: repeat-x;
223 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
224 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
225 | border-radius: 4px;
226 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
227 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
228 | }
229 | .navbar-default .navbar-nav > .open > a,
230 | .navbar-default .navbar-nav > .active > a {
231 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
232 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
233 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
234 | background-repeat: repeat-x;
235 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
236 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
237 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
238 | }
239 | .navbar-brand,
240 | .navbar-nav > li > a {
241 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
242 | }
243 | .navbar-inverse {
244 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);
245 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%);
246 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
247 | background-repeat: repeat-x;
248 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
249 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
250 | }
251 | .navbar-inverse .navbar-nav > .open > a,
252 | .navbar-inverse .navbar-nav > .active > a {
253 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
254 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
255 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
256 | background-repeat: repeat-x;
257 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
258 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
259 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
260 | }
261 | .navbar-inverse .navbar-brand,
262 | .navbar-inverse .navbar-nav > li > a {
263 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
264 | }
265 | .navbar-static-top,
266 | .navbar-fixed-top,
267 | .navbar-fixed-bottom {
268 | border-radius: 0;
269 | }
270 | .alert {
271 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
272 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
273 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
274 | }
275 | .alert-success {
276 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
277 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
278 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
279 | background-repeat: repeat-x;
280 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
281 | border-color: #b2dba1;
282 | }
283 | .alert-info {
284 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
285 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
286 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
287 | background-repeat: repeat-x;
288 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
289 | border-color: #9acfea;
290 | }
291 | .alert-warning {
292 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
293 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
294 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
295 | background-repeat: repeat-x;
296 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
297 | border-color: #f5e79e;
298 | }
299 | .alert-danger {
300 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
301 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
302 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
303 | background-repeat: repeat-x;
304 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
305 | border-color: #dca7a7;
306 | }
307 | .progress {
308 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
309 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
310 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
311 | background-repeat: repeat-x;
312 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
313 | }
314 | .progress-bar {
315 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
316 | background-image: -o-linear-gradient(top, #428bca 0%, #3071a9 100%);
317 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
318 | background-repeat: repeat-x;
319 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
320 | }
321 | .progress-bar-success {
322 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
323 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
324 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
325 | background-repeat: repeat-x;
326 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
327 | }
328 | .progress-bar-info {
329 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
330 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
331 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
332 | background-repeat: repeat-x;
333 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
334 | }
335 | .progress-bar-warning {
336 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
337 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
338 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
339 | background-repeat: repeat-x;
340 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
341 | }
342 | .progress-bar-danger {
343 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
344 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
345 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
346 | background-repeat: repeat-x;
347 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
348 | }
349 | .progress-bar-striped {
350 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
351 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
352 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
353 | }
354 | .list-group {
355 | border-radius: 4px;
356 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
357 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
358 | }
359 | .list-group-item.active,
360 | .list-group-item.active:hover,
361 | .list-group-item.active:focus {
362 | text-shadow: 0 -1px 0 #3071a9;
363 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
364 | background-image: -o-linear-gradient(top, #428bca 0%, #3278b3 100%);
365 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
366 | background-repeat: repeat-x;
367 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
368 | border-color: #3278b3;
369 | }
370 | .panel {
371 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
372 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
373 | }
374 | .panel-default > .panel-heading {
375 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
376 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
377 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
378 | background-repeat: repeat-x;
379 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
380 | }
381 | .panel-primary > .panel-heading {
382 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
383 | background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
384 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
385 | background-repeat: repeat-x;
386 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
387 | }
388 | .panel-success > .panel-heading {
389 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
390 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
391 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
392 | background-repeat: repeat-x;
393 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
394 | }
395 | .panel-info > .panel-heading {
396 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
397 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
398 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
399 | background-repeat: repeat-x;
400 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
401 | }
402 | .panel-warning > .panel-heading {
403 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
404 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
405 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
406 | background-repeat: repeat-x;
407 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
408 | }
409 | .panel-danger > .panel-heading {
410 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
411 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
412 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
413 | background-repeat: repeat-x;
414 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
415 | }
416 | .well {
417 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
418 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
419 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
420 | background-repeat: repeat-x;
421 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
422 | border-color: #dcdcdc;
423 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
424 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
425 | }
426 |
--------------------------------------------------------------------------------
/test/appium/www/css/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.0 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | /*!
8 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=109fa5b75f9e34ab14e2)
9 | * Config saved to config.json and https://gist.github.com/109fa5b75f9e34ab14e2
10 | */.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-default .badge,.btn-primary .badge,.btn-success .badge,.btn-info .badge,.btn-warning .badge,.btn-danger .badge{text-shadow:none}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top, #fff 0, #e0e0e0 100%);background-image:-o-linear-gradient(top, #fff 0, #e0e0e0 100%);background-image:linear-gradient(to bottom, #fff 0, #e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top, #428bca 0, #2d6ca2 100%);background-image:-o-linear-gradient(top, #428bca 0, #2d6ca2 100%);background-image:linear-gradient(to bottom, #428bca 0, #2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-primary:disabled,.btn-primary[disabled]{background-color:#2d6ca2;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top, #5cb85c 0, #419641 100%);background-image:-o-linear-gradient(top, #5cb85c 0, #419641 100%);background-image:linear-gradient(to bottom, #5cb85c 0, #419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top, #5bc0de 0, #2aabd2 100%);background-image:-o-linear-gradient(top, #5bc0de 0, #2aabd2 100%);background-image:linear-gradient(to bottom, #5bc0de 0, #2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top, #f0ad4e 0, #eb9316 100%);background-image:-o-linear-gradient(top, #f0ad4e 0, #eb9316 100%);background-image:linear-gradient(to bottom, #f0ad4e 0, #eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top, #d9534f 0, #c12e2a 100%);background-image:-o-linear-gradient(top, #d9534f 0, #c12e2a 100%);background-image:linear-gradient(to bottom, #d9534f 0, #c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:-o-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:-o-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:linear-gradient(to bottom, #428bca 0, #357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top, #fff 0, #f8f8f8 100%);background-image:-o-linear-gradient(top, #fff 0, #f8f8f8 100%);background-image:linear-gradient(to bottom, #fff 0, #f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top, #dbdbdb 0, #e2e2e2 100%);background-image:-o-linear-gradient(top, #dbdbdb 0, #e2e2e2 100%);background-image:linear-gradient(to bottom, #dbdbdb 0, #e2e2e2 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.075);box-shadow:inset 0 3px 9px rgba(0,0,0,0.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top, #3c3c3c 0, #222 100%);background-image:-o-linear-gradient(top, #3c3c3c 0, #222 100%);background-image:linear-gradient(to bottom, #3c3c3c 0, #222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top, #080808 0, #0f0f0f 100%);background-image:-o-linear-gradient(top, #080808 0, #0f0f0f 100%);background-image:linear-gradient(to bottom, #080808 0, #0f0f0f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.25);box-shadow:inset 0 3px 9px rgba(0,0,0,0.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);background-image:-o-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);background-image:linear-gradient(to bottom, #dff0d8 0, #c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top, #d9edf7 0, #b9def0 100%);background-image:-o-linear-gradient(top, #d9edf7 0, #b9def0 100%);background-image:linear-gradient(to bottom, #d9edf7 0, #b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);background-image:-o-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);background-image:linear-gradient(to bottom, #fcf8e3 0, #f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top, #f2dede 0, #e7c3c3 100%);background-image:-o-linear-gradient(top, #f2dede 0, #e7c3c3 100%);background-image:linear-gradient(to bottom, #f2dede 0, #e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top, #ebebeb 0, #f5f5f5 100%);background-image:-o-linear-gradient(top, #ebebeb 0, #f5f5f5 100%);background-image:linear-gradient(to bottom, #ebebeb 0, #f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top, #428bca 0, #3071a9 100%);background-image:-o-linear-gradient(top, #428bca 0, #3071a9 100%);background-image:linear-gradient(to bottom, #428bca 0, #3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top, #5cb85c 0, #449d44 100%);background-image:-o-linear-gradient(top, #5cb85c 0, #449d44 100%);background-image:linear-gradient(to bottom, #5cb85c 0, #449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top, #5bc0de 0, #31b0d5 100%);background-image:-o-linear-gradient(top, #5bc0de 0, #31b0d5 100%);background-image:linear-gradient(to bottom, #5bc0de 0, #31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top, #f0ad4e 0, #ec971f 100%);background-image:-o-linear-gradient(top, #f0ad4e 0, #ec971f 100%);background-image:linear-gradient(to bottom, #f0ad4e 0, #ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top, #d9534f 0, #c9302c 100%);background-image:-o-linear-gradient(top, #d9534f 0, #c9302c 100%);background-image:linear-gradient(to bottom, #d9534f 0, #c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top, #428bca 0, #3278b3 100%);background-image:-o-linear-gradient(top, #428bca 0, #3278b3 100%);background-image:linear-gradient(to bottom, #428bca 0, #3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:-o-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:-o-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:linear-gradient(to bottom, #428bca 0, #357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top, #dff0d8 0, #d0e9c6 100%);background-image:-o-linear-gradient(top, #dff0d8 0, #d0e9c6 100%);background-image:linear-gradient(to bottom, #dff0d8 0, #d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top, #d9edf7 0, #c4e3f3 100%);background-image:-o-linear-gradient(top, #d9edf7 0, #c4e3f3 100%);background-image:linear-gradient(to bottom, #d9edf7 0, #c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top, #fcf8e3 0, #faf2cc 100%);background-image:-o-linear-gradient(top, #fcf8e3 0, #faf2cc 100%);background-image:linear-gradient(to bottom, #fcf8e3 0, #faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top, #f2dede 0, #ebcccc 100%);background-image:-o-linear-gradient(top, #f2dede 0, #ebcccc 100%);background-image:linear-gradient(to bottom, #f2dede 0, #ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top, #e8e8e8 0, #f5f5f5 100%);background-image:-o-linear-gradient(top, #e8e8e8 0, #f5f5f5 100%);background-image:linear-gradient(to bottom, #e8e8e8 0, #f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)}
--------------------------------------------------------------------------------
/test/appium/www/css/main.css:
--------------------------------------------------------------------------------
1 | .main {
2 | border-top: 24px solid #a7a7a7;
3 | }
4 |
5 | .btn {
6 | margin: 10px 0;
7 | }
8 |
9 | .status {
10 | padding: 4px;
11 | border-radius: 4px;
12 | color: #fff;
13 | text-align: center;
14 | font-size: 90%;
15 | }
16 |
17 | .main p {
18 | margin: 0;
19 | }
20 |
21 | .pass {
22 | background: #789048;
23 | }
24 |
25 | .fail {
26 | background: #7D1A0C;
27 | }
28 |
--------------------------------------------------------------------------------
/test/appium/www/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timflapper/cordova-spotify-plugin/424f3a900c4915a0cdf0e20fccd51c7185349bb7/test/appium/www/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/test/appium/www/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timflapper/cordova-spotify-plugin/424f3a900c4915a0cdf0e20fccd51c7185349bb7/test/appium/www/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/test/appium/www/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timflapper/cordova-spotify-plugin/424f3a900c4915a0cdf0e20fccd51c7185349bb7/test/appium/www/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/test/appium/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
81 |
82 |
83 |
Login Player
84 |
85 |
Play song
86 |
87 |
Mute audio
88 |
89 |
Get progress
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/test/ios/MockCommandDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // MockCommandDelegate.h
3 | // SpotifyPlugin
4 | //
5 | // Created by Tim Flapper on 08/05/14.
6 | //
7 | //
8 |
9 | #import "Cordova/CDV.h"
10 |
11 | typedef void (^mockPluginResultCallback)(CDVPluginResult *result, NSString *callbackId);
12 |
13 | @interface MockCommandDelegate : NSObject
14 |
15 | - (void)mockPluginResult:(mockPluginResultCallback)callback;
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/test/ios/MockCommandDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // MockCommandDelegate.m
3 | // SpotifyPlugin
4 | //
5 | // Created by Tim Flapper on 08/05/14.
6 | //
7 | //
8 |
9 | #import "MockCommandDelegate.h"
10 |
11 | @interface MockCommandDelegate()
12 | @property (copy, nonatomic) mockPluginResultCallback callback;
13 | @end
14 |
15 | @implementation MockCommandDelegate
16 |
17 | - (id)init
18 | {
19 | self = [super init];
20 |
21 | if (self) {
22 |
23 | }
24 |
25 | return self;
26 | }
27 |
28 | - (void)mockPluginResult:(mockPluginResultCallback)callback
29 | {
30 | self.callback = callback;
31 | }
32 |
33 | - (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId
34 | {
35 |
36 | double delayInSeconds = 0.01;
37 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
38 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
39 | if (self.callback)
40 | self.callback(result, callbackId);
41 | });
42 | }
43 |
44 | #pragma mark neccesary but unused protocol methods
45 |
46 | - (NSString*)pathForResource:(NSString*)resourcepath
47 | {
48 | return @"";
49 | }
50 |
51 | - (id)getCommandInstance:(NSString*)pluginName
52 | {
53 | return nil;
54 | }
55 |
56 | - (BOOL)execute:(CDVInvokedUrlCommand*)command
57 | {
58 | return YES;
59 | }
60 |
61 | - (void)evalJs:(NSString*)js
62 | {
63 |
64 | }
65 |
66 | - (void)evalJs:(NSString*)js scheduledOnRunLoop:(BOOL)scheduledOnRunLoop
67 | {
68 |
69 | }
70 |
71 | - (void)runInBackground:(void (^)())block
72 | {
73 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
74 | }
75 |
76 | - (NSString*)userAgent
77 | {
78 | return @"";
79 | }
80 |
81 | - (BOOL)URLIsWhitelisted:(NSURL*)url
82 | {
83 | return YES;
84 | }
85 |
86 | - (NSDictionary*)settings
87 | {
88 | return @{};
89 | }
90 | @end
91 |
--------------------------------------------------------------------------------
/test/ios/SpotifyAudioPlayer+Mock.h:
--------------------------------------------------------------------------------
1 | //
2 | // SpotifyAudioPlayer+Mock.h
3 | // SpotifyPlugin
4 | //
5 | // Created by Tim Flapper on 09/05/14.
6 | //
7 | //
8 |
9 | #import "SpotifyAudioPlayer.h"
10 |
11 | typedef void (^mockResultCallback)(id callback);
12 |
13 | @interface SpotifyAudioPlayer (Mock)
14 | + (void)clearTestValues;
15 |
16 | + (void)setNextCallback:(mockResultCallback)block;
17 |
18 | + (void)setNextCallback:(mockResultCallback)block afterDelayInSeconds:(NSTimeInterval)delayInSeconds;
19 |
20 | + (void)setNextMethodReturn:(id)returnValue;
21 |
22 | + (void)setNextEvent:(NSDictionary *)event;
23 | @end
24 |
--------------------------------------------------------------------------------
/test/ios/SpotifyAudioPlayer+Mock.m:
--------------------------------------------------------------------------------
1 | //
2 | // SpotifyAudioPlayer+Mock.m
3 | // SpotifyPlugin
4 | //
5 | // Created by Tim Flapper on 09/05/14.
6 | //
7 | //
8 |
9 | #import "SpotifyAudioPlayer+Mock.h"
10 | #import
11 |
12 | static char const * const nextCallbackKey = "__nextCallbackForTesting";
13 | static char const * const delayInSecondsKey = "__delayInSecondsTesting";
14 | static char const * const nextReturnKey = "__nextReturnForTesting";
15 | static char const * const nextEventKey = "__nextEventForTesting";
16 |
17 |
18 | void runBlockAfterDelayInSeconds(NSTimeInterval delayInSeconds, dispatch_block_t block) {
19 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
20 | dispatch_after(popTime, dispatch_get_main_queue(), block);
21 | }
22 |
23 | @implementation SpotifyAudioPlayer (Mock)
24 | + (void)load
25 | {
26 | static dispatch_once_t onceToken;
27 | dispatch_once(&onceToken, ^{
28 | [self clearTestValues];
29 | });
30 |
31 | [super load];
32 | }
33 |
34 | + (void)clearTestValues
35 | {
36 | objc_setAssociatedObject([self class], nextCallbackKey, nil, OBJC_ASSOCIATION_RETAIN);
37 | objc_setAssociatedObject([self class], delayInSecondsKey, @0, OBJC_ASSOCIATION_RETAIN);
38 | objc_setAssociatedObject([self class], nextReturnKey, nil, OBJC_ASSOCIATION_RETAIN);
39 | objc_setAssociatedObject([self class], nextEventKey, nil, OBJC_ASSOCIATION_RETAIN);
40 | }
41 |
42 | + (void)setNextCallback:(mockResultCallback)block
43 | {
44 | objc_setAssociatedObject([self class], nextCallbackKey, block, OBJC_ASSOCIATION_COPY);
45 | }
46 |
47 | + (void)setNextCallback:(mockResultCallback)block afterDelayInSeconds:(NSTimeInterval)delayInSeconds
48 | {
49 | objc_setAssociatedObject([self class], delayInSecondsKey, [NSNumber numberWithDouble:delayInSeconds], OBJC_ASSOCIATION_RETAIN);
50 | objc_setAssociatedObject([self class], nextCallbackKey, block, OBJC_ASSOCIATION_COPY);
51 | }
52 |
53 | + (void)setNextMethodReturn:(id)returnValue
54 | {
55 | objc_setAssociatedObject([self class], nextReturnKey, returnValue, OBJC_ASSOCIATION_RETAIN);
56 | }
57 |
58 | + (void)setNextEvent:(NSDictionary *)event
59 | {
60 | objc_setAssociatedObject([self class], nextEventKey, event, OBJC_ASSOCIATION_RETAIN);
61 | }
62 |
63 | + (void)invokeNextCallback:(id)block
64 | {
65 | mockResultCallback callback = objc_getAssociatedObject([self class], nextCallbackKey);
66 | NSTimeInterval delayInSeconds = ((NSNumber *)objc_getAssociatedObject([self class], delayInSecondsKey)).doubleValue;
67 |
68 | if (! callback)
69 | return;
70 |
71 | if (delayInSeconds > 0) {
72 | runBlockAfterDelayInSeconds(delayInSeconds, ^{
73 | callback(block);
74 | });
75 | } else {
76 | callback(block);
77 | }
78 | }
79 |
80 | + (id)getNextMethodReturn
81 | {
82 | return objc_getAssociatedObject([self class], nextReturnKey);
83 | }
84 |
85 | + (NSDictionary *)getNextEvent
86 | {
87 | return objc_getAssociatedObject([self class], nextEventKey);
88 | }
89 |
90 | - (void)loginWithSession:(SPTSession *)session callback:(SPTErrorableOperationCallback)block
91 | {
92 | [[self class] invokeNextCallback:block];
93 |
94 | [self audioStreamingDidLogin:self];
95 | }
96 |
97 | - (void)playURI:(NSURL *)uri callback:(SPTErrorableOperationCallback)block
98 | {
99 | [[self class] invokeNextCallback:block];
100 |
101 | if ([[self class] getNextMethodReturn] != nil)
102 | [self audioStreaming:self didChangeToTrack:[[self class] getNextMethodReturn]];
103 |
104 | [self audioStreaming:self didChangePlaybackStatus:YES];
105 | }
106 |
107 | - (void)playURI:(NSURL *)uri fromIndex:(int)index callback:(SPTErrorableOperationCallback)block
108 | {
109 | [self playURI:uri callback:block];
110 | }
111 |
112 | - (void)playURIs:(NSArray *)uris fromIndex:(int)index callback:(SPTErrorableOperationCallback)block
113 | {
114 | [[self class] invokeNextCallback:block];
115 |
116 | if ([[self class] getNextMethodReturn] != nil)
117 | [self audioStreaming:self didChangeToTrack:[[self class] getNextMethodReturn]];
118 |
119 | [self audioStreaming:self didChangePlaybackStatus:YES];
120 | }
121 |
122 | - (void)setURIs:(NSArray *)uris callback:(SPTErrorableOperationCallback)block
123 | {
124 | [[self class] invokeNextCallback:block];
125 | }
126 |
127 | - (void)playURIsFromIndex:(int)index callback:(SPTErrorableOperationCallback)block
128 | {
129 | [[self class] invokeNextCallback:block];
130 |
131 | if ([[self class] getNextMethodReturn] != nil)
132 | [self audioStreaming:self didChangeToTrack:[[self class] getNextMethodReturn]];
133 |
134 | [self audioStreaming:self didChangePlaybackStatus:YES];
135 | }
136 |
137 | - (void)queueURI:(NSURL *)uri callback:(SPTErrorableOperationCallback)block
138 | {
139 | [[self class] invokeNextCallback:block];
140 | }
141 |
142 | - (void)queueURI:(NSURL *)uri clearQueue:(BOOL)clear callback:(SPTErrorableOperationCallback)block
143 | {
144 | [[self class] invokeNextCallback:block];
145 | }
146 |
147 | - (void)queueURIs:(NSArray *)uris clearQueue:(BOOL)clear callback:(SPTErrorableOperationCallback)block
148 | {
149 | [[self class] invokeNextCallback:block];
150 | }
151 |
152 | - (void)queuePlay:(SPTErrorableOperationCallback)block
153 | {
154 | [[self class] invokeNextCallback:block];
155 |
156 | if ([[self class] getNextMethodReturn] != nil)
157 | [self audioStreaming:self didChangeToTrack:[[self class] getNextMethodReturn]];
158 |
159 | [self audioStreaming:self didChangePlaybackStatus:YES];
160 | }
161 |
162 | - (void)queueClear:(SPTErrorableOperationCallback)block
163 | {
164 | [[self class] invokeNextCallback:block];
165 | }
166 |
167 | - (void)stop:(SPTErrorableOperationCallback)block
168 | {
169 | [[self class] invokeNextCallback:block];
170 |
171 | if ([[self class] getNextMethodReturn] != nil)
172 | [self audioStreaming:self didChangeToTrack:[[self class] getNextMethodReturn]];
173 |
174 | [self audioStreaming:self didChangePlaybackStatus:YES];
175 | }
176 |
177 | - (void)skipNext:(SPTErrorableOperationCallback)block
178 | {
179 | [[self class] invokeNextCallback:block];
180 |
181 | if ([[self class] getNextMethodReturn] != nil)
182 | [self audioStreaming:self didChangeToTrack:[[self class] getNextMethodReturn]];
183 |
184 | [self audioStreaming:self didChangePlaybackStatus:YES];
185 |
186 | }
187 |
188 | - (void)skipPrevious:(SPTErrorableOperationCallback)block
189 | {
190 | [[self class] invokeNextCallback:block];
191 |
192 | if ([[self class] getNextMethodReturn] != nil)
193 | [self audioStreaming:self didChangeToTrack:[[self class] getNextMethodReturn]];
194 |
195 | [self audioStreaming:self didChangePlaybackStatus:YES];
196 |
197 | }
198 |
199 | - (void)seekToOffset:(NSTimeInterval)offset callback:(SPTErrorableOperationCallback)block
200 | {
201 | [[self class] invokeNextCallback:block];
202 | }
203 |
204 | - (BOOL)isPlaying
205 | {
206 | NSNumber *methodReturn = [[self class] getNextMethodReturn];
207 |
208 | return methodReturn.boolValue;
209 | }
210 |
211 | - (void)setIsPlaying:(BOOL)playing callback:(SPTErrorableOperationCallback)block
212 | {
213 | [self audioStreaming:self didChangePlaybackStatus:playing];
214 |
215 | [[self class] invokeNextCallback:block];
216 | }
217 |
218 | - (SPTBitrate)targetBitrate
219 | {
220 | NSNumber *methodReturn = [[self class] getNextMethodReturn];
221 |
222 | return methodReturn.intValue;
223 | }
224 |
225 | - (void)setTargetBitrate:(SPTBitrate)bitrate callback:(SPTErrorableOperationCallback)block
226 | {
227 | [[self class] invokeNextCallback:block];
228 | }
229 |
230 | - (NSUInteger)diskCacheSizeLimit
231 | {
232 | NSNumber *methodReturn = [[self class] getNextMethodReturn];
233 |
234 | return methodReturn.intValue;
235 | }
236 |
237 | - (void)setDiskCacheSizeLimit:(NSUInteger)diskCacheSizeLimit
238 | {
239 | return;
240 | }
241 |
242 | - (SPTVolume)volume
243 | {
244 | NSNumber *methodReturn = [[self class] getNextMethodReturn];
245 |
246 | return methodReturn.doubleValue;
247 | }
248 |
249 | - (void)setVolume:(SPTVolume)volume callback:(SPTErrorableOperationCallback)block
250 | {
251 | [[self class] invokeNextCallback:block];
252 | [self audioStreaming:self didChangeVolume:volume];
253 | }
254 |
255 | - (BOOL)repeat
256 | {
257 | NSNumber *methodReturn = [[self class] getNextMethodReturn];
258 |
259 | return methodReturn.boolValue;
260 | }
261 |
262 | - (void)setRepeat:(BOOL)repeat
263 | {
264 | [self audioStreaming:self didChangeRepeatStatus:repeat];
265 | }
266 |
267 | - (BOOL)shuffle
268 | {
269 | NSNumber *methodReturn = [[self class] getNextMethodReturn];
270 |
271 | return methodReturn.boolValue;
272 | }
273 |
274 | - (void)setShuffle:(BOOL)shuffle
275 | {
276 | [self audioStreaming:self didChangeShuffleStatus:shuffle];
277 | }
278 |
279 | - (int)queueSize
280 | {
281 | NSNumber *methodReturn = [[self class] getNextMethodReturn];
282 |
283 | return methodReturn.intValue;
284 | }
285 |
286 | - (int)trackListSize
287 | {
288 | NSNumber *methodReturn = [[self class] getNextMethodReturn];
289 |
290 | return methodReturn.intValue;
291 | }
292 |
293 | - (NSDictionary *)currentTrackMetadata
294 | {
295 | return [[self class] getNextMethodReturn];
296 | }
297 |
298 | - (void)getRelativeTrackMetadata:(int)index callback:(void (^)(NSDictionary *))block
299 | {
300 | [[self class] invokeNextCallback:block];
301 | }
302 |
303 | - (void)getAbsoluteTrackMetadata:(int)index callback:(void (^)(NSDictionary *))block
304 | {
305 | [[self class] invokeNextCallback:block];
306 | }
307 |
308 | - (NSTimeInterval)currentPlaybackPosition
309 | {
310 | NSNumber *methodReturn = [[self class] getNextMethodReturn];
311 |
312 | return methodReturn.doubleValue;
313 | }
314 |
315 | @end
316 |
--------------------------------------------------------------------------------
/test/ios/SpotifyPluginTests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | com.timflapper.${PRODUCT_NAME:rfc1034identifier}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/ios/TestData/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "external_urls" : {
3 | "spotify" : "https://open.spotify.com/user/awesomeuser"
4 | },
5 | "href" : "https://api.spotify.com/v1/users/awesomeuser",
6 | "id" : "awesomeuser",
7 | "type" : "user",
8 | "uri" : "spotify:user:awesomeuser"
9 | }
10 |
--------------------------------------------------------------------------------
/test/ios/TestData/refresh.json:
--------------------------------------------------------------------------------
1 | {"access_token":"Ab4wVEt-33dSMLfD_Nd_GLjBGX5TQc0VrQRIXW2WVP69Z5I_bIw4YIumYsOPXNA-1-6HLdS_XdfX9FXrtezc-ltUAT6cj69scFrqxJPWV12mK-224W0ekpsi-WQe_T1OYSZbyv00abgBopOzx9AOH5sd","token_type":"Bearer","expires_in":3600}
2 |
--------------------------------------------------------------------------------
/test/ios/TestData/refresh_failed.json:
--------------------------------------------------------------------------------
1 | {"error":"unauthorized"}
2 |
--------------------------------------------------------------------------------
/test/ios/TestData/session.json:
--------------------------------------------------------------------------------
1 | {"access_token":"Ab4wVEt-33dSMLfD_Nd_GLjBGX5TQc0VrQRIXW2WVP69Z5I_bIw4YIumYsOPXNA-1-6HLdS_XdfX9FXrtezc-ltUAT6cj69scFrqxJPWV12mK-224W0ekpsi-WQe_T1OYSZbyv00abgBopOzx9AOH5sd","token_type":"Bearer","expires_in":3600,"refresh_token":"XXXX_qPeacCRHWujLagqGV0khtZ_jaF_Ek8VI80g7HpAzjmbQZHz1j5_0YbcpSvi31mE7AMipJcYGQ9_p_65elCf_OS6vIhhNJRCmlOPc3RJVjuNdadQTR9sucB413X4Xx"}
2 |
--------------------------------------------------------------------------------
/test/ios/testShared.h:
--------------------------------------------------------------------------------
1 | //
2 | // shared.h
3 | // SpotifyPlugin
4 | //
5 | // Created by Tim Flapper on 07/05/14.
6 | //
7 | //
8 |
9 | #import
10 |
11 | NSData *getDataFromTestDataFile(NSString *filename);
12 |
13 | void waitForSecondsOrDone(NSTimeInterval noOfSeconds, BOOL *done);
14 |
15 | NSError *errorForTesting();
--------------------------------------------------------------------------------
/test/ios/testShared.m:
--------------------------------------------------------------------------------
1 | //
2 | // shared.m
3 | // SpotifyPlugin
4 | //
5 | // Created by Tim Flapper on 07/05/14.
6 | //
7 | //
8 |
9 | #import "SpotifyPlugin.h"
10 | #import
11 |
12 | NSData *getDataFromTestDataFile(NSString *filename) {
13 | NSString *path = [[NSBundle bundleForClass: [SpotifyPlugin class]] pathForResource:filename ofType:@"" inDirectory:@"TestData"];
14 |
15 | return [NSData dataWithContentsOfFile:path];
16 | }
17 |
18 | void waitForSecondsOrDone(NSTimeInterval noOfSeconds, BOOL *done) {
19 | NSDate* timeoutDate = [NSDate dateWithTimeIntervalSinceNow:noOfSeconds];
20 | while (!*done && ([timeoutDate timeIntervalSinceNow]>0))
21 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.005, YES);
22 | }
23 |
24 |
25 | NSError *errorForTesting() {
26 | return [NSError errorWithDomain:@"for.testing.ErrorDomain" code:42 userInfo:@{NSLocalizedDescriptionKey: @"Nope"}];
27 | }
--------------------------------------------------------------------------------
/test/js/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/test/js/spec/lib/event-dispatcher.test.js:
--------------------------------------------------------------------------------
1 | describe('EventDispatcher', function() {
2 | var EventDispatcher;
3 |
4 | beforeEach(function() {
5 | EventDispatcher = EventDispatcher || require('com.timflapper.spotify.event-dispatcher');
6 |
7 | this.eventCallback = sinon.spy();
8 |
9 | this.eventDispatcher = new EventDispatcher();
10 | this.eventDispatcher.addEventListener('test', this.eventCallback);
11 | });
12 |
13 | describe('listening to event', function() {
14 | beforeEach(function() {
15 | this.eventDispatcher.dispatchEvent('test');
16 | });
17 |
18 | it('should have been called', function() {
19 | expect(this.eventCallback).to.have.been.calledWith();
20 | });
21 | });
22 |
23 | describe('removing event', function() {
24 | beforeEach(function() {
25 | this.eventDispatcher.removeEventListener('test', this.eventCallback);
26 | this.eventDispatcher.dispatchEvent('test');
27 | });
28 |
29 | it('should not have been called', function() {
30 | expect(this.eventCallback).to.not.have.been.called;
31 | });
32 | });
33 |
34 | });
35 |
--------------------------------------------------------------------------------
/test/js/spec/lib/remote.test.js:
--------------------------------------------------------------------------------
1 | function createExpirationDate(date) {
2 | date = date || new Date();
3 |
4 | return date.getUTCFullYear() + "-" + date.getUTCMonth() + "-" + date.getUTCDate() + " " + date.getUTCHours() + ":" + date.getUTCMinutes() + ":" + date.getUTCSeconds() + " GMT";
5 | }
6 |
7 | var session = {canonicalUsername: 'antman', accessToken: 'Xcd4r234234fdfa_dfsadf3', encryptedRefreshToken: 'sdfsdfds724dfsdf234dsf', expirationDate: createExpirationDate()};
8 |
9 | function sharedRemoteTests() {
10 | it('should load the request', function() {
11 | expect(this.callback).to.have.been.calledWith(null, this.request.response);
12 | });
13 | }
14 |
15 | function sharedHooks() {
16 | beforeEach(function() {
17 | var self = this;
18 | this.server.respondWith(function(xhr, id) {
19 | var request = self.request;
20 |
21 | if (xhr.method === request.method && xhr.url === request.url) {
22 | self.requestHeaders = xhr.requestHeaders;
23 | self.requestBody = xhr.requestBody;
24 | xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(request.response));
25 | }
26 | });
27 | });
28 | }
29 |
30 | describe('remote', function() {
31 | var remote;
32 |
33 | before(function() { remote = require('com.timflapper.spotify.remote'); });
34 |
35 | beforeEach(function() {
36 | this.callback = sinon.spy();
37 | this.server = sinon.fakeServer.create();
38 | });
39 |
40 | afterEach(function () { this.server.restore(); });
41 |
42 | describe('empty options', function() {
43 | it('should throw an error', function() {
44 | var subject = function() { remote(); }
45 |
46 | expect(subject).to.throw('This method requires two arguments (options, callback)');
47 | });
48 | });
49 |
50 | describe('basic options', function() {
51 | sharedHooks();
52 |
53 | beforeEach(function() {
54 | this.request = { method: 'GET', url: 'https://api.spotify.com/v1/bla', response: {test: 'get'} };
55 | remote({uri: '/bla'}, this.callback);
56 | this.server.respond();
57 | });
58 |
59 | sharedRemoteTests();
60 | });
61 |
62 | describe('different method', function() {
63 | sharedHooks();
64 |
65 | beforeEach(function() {
66 | this.request = { method: 'POST', url: 'https://api.spotify.com/v1/blabla', response: {test: 'post'} };
67 | remote({uri: '/blabla', method: 'post'}, this.callback);
68 | this.server.respond();
69 | });
70 |
71 | sharedRemoteTests();
72 | });
73 |
74 | describe('url instead of uri', function() {
75 | sharedHooks();
76 |
77 | beforeEach(function() {
78 | this.request = { method: 'GET', url: 'http://google.com/blabla', response: {test: 'url'} };
79 | remote({url: 'http://google.com/blabla'}, this.callback);
80 | this.server.respond();
81 | });
82 |
83 | sharedRemoteTests();
84 | });
85 |
86 | describe('with session', function() {
87 | sharedHooks();
88 |
89 | beforeEach(function() {
90 | this.request = { method: 'GET', url: 'https://api.spotify.com/v1/session', response: {test: 'session'} };
91 | remote({uri: '/session', session: session}, this.callback);
92 | this.server.respond();
93 | });
94 |
95 | sharedRemoteTests();
96 |
97 | it('should send the Bearer token as part of the request', function() {
98 | expect(this.requestHeaders).to.have.property('Authorization', 'Bearer '+session.accessToken);
99 | });
100 | });
101 |
102 | describe('with data', function() {
103 | sharedHooks();
104 |
105 | beforeEach(function() {
106 | this.request = { method: 'POST', url: 'https://api.spotify.com/v1/data', response: {test: 'data'} };
107 | remote({uri: '/data', method: 'POST', data: 'DATA'}, this.callback);
108 | this.server.respond();
109 | });
110 |
111 | sharedRemoteTests();
112 |
113 | it('should send the Bearer token as part of the request', function() {
114 | expect(this.requestBody).to.equal('DATA');
115 | });
116 | });
117 |
118 | describe('pagination', function() {
119 | beforeEach(function() {
120 | this.nextUrl = 'http://next.url';
121 | this.prevUrl ='http://previous.url';
122 | });
123 |
124 | describe('on root object', function() {
125 | beforeEach(function() {
126 | this.responseBody = { next: this.nextUrl, prev: this.prevUrl };
127 | this.findPaginated = function(data) { return data; };
128 | });
129 |
130 | shared.paginatedTests();
131 | });
132 |
133 | describe('artists', function() {
134 | beforeEach(function() {
135 | this.responseBody = { artists: { next: this.nextUrl, prev: this.prevUrl } };
136 | this.findPaginated = function(data) { return data.artists; };
137 | });
138 |
139 | shared.paginatedTests();
140 | });
141 |
142 | describe('albums', function() {
143 | beforeEach(function() {
144 | this.responseBody = { albums: { next: this.nextUrl, prev: this.prevUrl } };
145 | this.findPaginated = function(data) { return data.albums; };
146 | });
147 |
148 | shared.paginatedTests();
149 | });
150 |
151 | describe('tracks', function() {
152 | beforeEach(function() {
153 | this.responseBody = { tracks: { next: this.nextUrl, prev: this.prevUrl } };
154 | this.findPaginated = function(data) { return data.tracks; };
155 | });
156 |
157 | shared.paginatedTests();
158 | });
159 |
160 | describe('tracks in an array of albums', function() {
161 | beforeEach(function() {
162 | var self = this;
163 | this.responseBody = { albums: [{ tracks: { next: this.nextUrl, prev: this.prevUrl } }, { tracks: {next: null, prev: null} }, { tracks: { next: this.nextUrl, prev: this.prevUrl } }] };
164 | });
165 |
166 | describe('first album', function() {
167 | beforeEach(function() { this.findPaginated = function(data) { return data.albums[0].tracks; }; });
168 | shared.paginatedTests();
169 | });
170 |
171 | describe('second album', function() {
172 | beforeEach(function() { this.findPaginated = function(data) { return data.albums[1].tracks; }; });
173 |
174 | shared.hooksForPagination();
175 |
176 | it('should not modify the previous and next objects', function(done) {
177 | var findPaginated = this.findPaginated;
178 | remote({uri: '/paginate'}, function(err, data) {
179 | data = findPaginated(data);
180 | expect(data.next).to.be.null;
181 | expect(data.prev).to.be.null;
182 | done();
183 | });
184 | this.server.respond();
185 | });
186 | });
187 |
188 | describe('third album', function() {
189 | beforeEach(function() { this.findPaginated = function(data) { return data.albums[2].tracks; }; });
190 | shared.paginatedTests();
191 | });
192 | });
193 | });
194 | });
195 |
--------------------------------------------------------------------------------
/test/js/spec/lib/utils.test.js:
--------------------------------------------------------------------------------
1 | describe('utils', function() {
2 | var utils;
3 |
4 | before(function() {
5 | utils = require('com.timflapper.spotify.utils');
6 | });
7 |
8 | describe('#exec', function() {
9 | context('no parameters', function() {
10 | it('should call xhr and return a message', function() {
11 | var onRequest = sinon.spy()
12 | , callback = sinon.spy();
13 |
14 | mockExec(1, true, onRequest);
15 | utils.exec('test', callback);
16 |
17 | expect(onRequest).to.have.been.calledWith(['SpotifyPlugin', 'test', []]);
18 |
19 | expect(callback).to.have.been.calledWith(null, true);
20 | });
21 | });
22 |
23 | context('with parameters', function() {
24 | it('should call xhr and return a message', function() {
25 | var onRequest = sinon.spy()
26 | , callback = sinon.spy();
27 |
28 | mockExec(1, 'message', onRequest);
29 | utils.exec('test', 'something', callback);
30 |
31 | expect(onRequest).to.have.been.calledWith(['SpotifyPlugin', 'test', ['something']]);
32 |
33 | expect(callback).to.have.been.calledWith(null, 'message');
34 | });
35 | });
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/test/js/spec/shared/paginate.js:
--------------------------------------------------------------------------------
1 | (function(window) {
2 |
3 | var shared = window.shared = window.shared || {};
4 |
5 | shared.hooksForPagination = function() {
6 | beforeEach(function() {
7 | var body = JSON.stringify(this.responseBody);
8 | this.server.respondWith('GET', 'https://api.spotify.com/v1/paginate',
9 | [200, { 'Content-Type': 'application/json' },
10 | body]);
11 | });
12 | }
13 |
14 | shared.paginatedTests = function() {
15 | var remote;
16 |
17 | before(function() { remote = require('com.timflapper.spotify.remote'); });
18 |
19 | beforeEach(function() {
20 | this.server.respondWith('GET', this.prevUrl,
21 | [200, { 'Content-Type': 'application/json' },
22 | JSON.stringify({page: 'previous'})]);
23 |
24 | this.server.respondWith('GET', this.nextUrl,
25 | [200, { 'Content-Type': 'application/json' },
26 | JSON.stringify({page: 'next'})]);
27 | });
28 |
29 | shared.hooksForPagination();
30 |
31 | it('should modify the previous and next objects', function(done) {
32 | var findPaginated = this.findPaginated;
33 | remote({uri: '/paginate'}, function(err, data) {
34 | data = findPaginated(data);
35 | expect(data.next).to.be.a('function');
36 | expect(data.prev).to.be.a('function');
37 | done();
38 | });
39 | this.server.respond();
40 | });
41 |
42 | it('should be calling the correct previous url', function(done) {
43 | var self = this;
44 | var findPaginated = this.findPaginated;
45 |
46 | remote({uri: '/paginate'}, function(err, data) {
47 | data = findPaginated(data);
48 | data.prev(self.callback);
49 | self.server.respond();
50 | expect(self.callback).to.have.been.calledWith(null, {page: 'previous'});
51 | done();
52 | });
53 | this.server.respond();
54 | });
55 |
56 | it('should be calling the correct next url', function(done) {
57 | var self = this;
58 | var findPaginated = this.findPaginated;
59 |
60 | remote({uri: '/paginate'}, function(err, data) {
61 | data = findPaginated(data);
62 | data.next(self.callback);
63 | self.server.respond();
64 | expect(self.callback).to.have.been.calledWith(null, {page: 'next'});
65 | done();
66 | });
67 | this.server.respond();
68 | });
69 | }
70 |
71 | })(window);
72 |
--------------------------------------------------------------------------------
/test/js/spec/spotify/audio-player.test.js:
--------------------------------------------------------------------------------
1 | describe('AudioPlayer', function() {
2 | function createExpirationDate(date) {
3 | date = date || new Date();
4 |
5 | return date.getUTCFullYear() + "-" + date.getUTCMonth() + "-" + date.getUTCDate() + " " + date.getUTCHours() + ":" + date.getUTCMinutes() + ":" + date.getUTCSeconds() + " GMT";
6 | }
7 |
8 | var session = {canonicalUsername: 'antman', accessToken: 'Xcd4r234234fdfa_dfsadf3', encryptedRefreshToken: 'sdfsdfds724dfsdf234dsf', expirationDate: createExpirationDate()};
9 |
10 | beforeEach(function() {
11 | this.player = spotify.createAudioPlayer('randomClientId');
12 | this.callback = sinon.spy();
13 | this.onRequest = sinon.spy();
14 | });
15 |
16 | afterEach(function() {
17 | restoreMockExec();
18 | });
19 |
20 | describe('spotify.createAudioPlayer', function() {
21 | var EventDispatcher;
22 |
23 | before(function() {
24 | EventDispatcher = require('com.timflapper.spotify.event-dispatcher');
25 | });
26 |
27 | it('should be an EventDispatcher', function() {
28 | expect(this.player).to.be.an.instanceof(EventDispatcher);
29 | });
30 | });
31 |
32 | describe('#login', function() {
33 | describe('with callback', function() {
34 | beforeEach(function() {
35 | this.eventCallback = sinon.spy();
36 | this.onMessageResult = sinon.spy();
37 | this.player.addEventListener(spotify.AudioPlayer.EVENT_MESSAGE, this.eventCallback);
38 | mockExec(1, 3, this.onRequest);
39 | mockExec(1, {type: spotify.AudioPlayer.EVENT_MESSAGE, args: ['Test Message']}, this.onMessageResult);
40 | this.player.login(session, this.callback);
41 | });
42 |
43 | it('should send the correct parameters to native', function() {
44 | expect(this.onRequest).to.have.been.calledWith([
45 | 'SpotifyPlugin', 'createAudioPlayerAndLogin', ['randomClientId', session]
46 | ]);
47 | });
48 |
49 | it('should login successfully', function() {
50 | expect(this.callback).to.have.been.calledWith(null);
51 | });
52 |
53 | it('should subscribe to native events', function() {
54 | expect(this.onMessageResult).to.have.been.calledWith([
55 | 'SpotifyPlugin', 'addAudioPlayerEventListener', [3]
56 | ]);
57 | });
58 |
59 | it('should be able to dispatch events', function() {
60 | expect(this.eventCallback).to.have.been.calledWith('Test Message');
61 | });
62 | });
63 |
64 | describe('without callback', function() {
65 | beforeEach(function() {
66 | this.loginEventCallback = sinon.spy();
67 | this.messageEventCallback = sinon.spy();
68 | this.onMessageResult = sinon.spy();
69 | this.player.addEventListener(spotify.AudioPlayer.EVENT_LOGIN, this.loginEventCallback);
70 | this.player.addEventListener(spotify.AudioPlayer.EVENT_MESSAGE, this.messageEventCallback);
71 | mockExec(1, 3, this.onRequest);
72 | mockExec(1, {type: spotify.AudioPlayer.EVENT_MESSAGE, args: ['Test Message']}, this.onMessageResult);
73 | this.player.login(session);
74 | });
75 |
76 | it('should send the correct parameters to native', function() {
77 | expect(this.onRequest).to.have.been.calledWith([
78 | 'SpotifyPlugin', 'createAudioPlayerAndLogin', ['randomClientId', session]
79 | ]);
80 | });
81 |
82 | it('should login successfully', function() {
83 | expect(this.loginEventCallback).to.have.been.calledWith();
84 | });
85 |
86 | it('should subscribe to native events', function() {
87 | expect(this.onMessageResult).to.have.been.calledWith([
88 | 'SpotifyPlugin', 'addAudioPlayerEventListener', [3]
89 | ]);
90 | });
91 |
92 | it('should be able to dispatch events', function() {
93 | expect(this.messageEventCallback).to.have.been.calledWith('Test Message');
94 | });
95 | });
96 | });
97 |
98 | describe('#logout', function() {
99 | beforeEach(function() {
100 | this.eventCallback = sinon.spy();
101 | mockExec(1, null, this.onRequest);
102 | this.player.id = 1;
103 | });
104 |
105 | describe('with callback', function() {
106 | beforeEach(function() {
107 | this.player.logout(this.callback);
108 | });
109 |
110 | it('should send the correct parameters to native', function() {
111 | expect(this.onRequest).to.have.been.calledWith([
112 | 'SpotifyPlugin', 'audioPlayerLogout', [1]
113 | ]);
114 | });
115 |
116 | it('should logout successfully', function() {
117 | expect(this.callback).to.have.been.calledWith(null);
118 | });
119 | });
120 |
121 | describe('without callback', function() {
122 | beforeEach(function() {
123 | this.player.addEventListener(spotify.AudioPlayer.EVENT_LOGOUT, this.eventCallback);
124 | this.player.logout();
125 | });
126 |
127 | it('should send the correct parameters to native', function() {
128 | expect(this.onRequest).to.have.been.calledWith([
129 | 'SpotifyPlugin', 'audioPlayerLogout', [1]
130 | ]);
131 | });
132 |
133 | it('should logout successfully', function() {
134 | expect(this.eventCallback).to.have.been.calledWith();
135 | });
136 | });
137 | });
138 |
139 | describe('#play', function() {
140 | beforeEach(function() {
141 | mockExec(1, null, this.onRequest);
142 | this.player.id = 1;
143 | });
144 |
145 | describe('playing single object', function() {
146 | beforeEach(function() {
147 | this.player.play('spotify:track:3XpXhVtZwqh2eM5d9ieXT5', this.callback);
148 | });
149 |
150 | it('should send the correct parameters to native', function() {
151 | expect(this.onRequest).to.have.been.calledWith([
152 | 'SpotifyPlugin', 'play', [1, 'spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 0]
153 | ]);
154 | });
155 |
156 | it('should play successfully', function() {
157 | expect(this.callback).to.have.been.calledWith(null);
158 | });
159 | });
160 |
161 | describe('playing single object with index', function() {
162 | beforeEach(function() {
163 | this.player.play('spotify:album:36k5aXpxffjVGcNce12GLZ', 4, this.callback);
164 | });
165 |
166 | it('should send the correct parameters to native', function() {
167 | expect(this.onRequest).to.have.been.calledWith([
168 | 'SpotifyPlugin', 'play', [1, 'spotify:album:36k5aXpxffjVGcNce12GLZ', 4]
169 | ]);
170 | });
171 |
172 | it('should play successfully', function() {
173 | expect(this.callback).to.have.been.calledWith(null);
174 | });
175 | });
176 |
177 | describe('playing array of objects', function() {
178 | beforeEach(function() {
179 | this.player.play(['spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 'spotify:track:0IqKeD8ZSP72KbGYyzEcAs'], this.callback);
180 | });
181 |
182 | it('should send the correct parameters to native', function() {
183 | expect(this.onRequest).to.have.been.calledWith([
184 | 'SpotifyPlugin', 'play', [1, ['spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 'spotify:track:0IqKeD8ZSP72KbGYyzEcAs'], 0]
185 | ]);
186 | });
187 |
188 | it('should play successfully', function() {
189 | expect(this.callback).to.have.been.calledWith(null);
190 | });
191 | });
192 |
193 | describe('playing array of objects with index', function() {
194 | beforeEach(function() {
195 | this.player.play(['spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 'spotify:track:0IqKeD8ZSP72KbGYyzEcAs'], 1, this.callback);
196 | });
197 |
198 | it('should send the correct parameters to native', function() {
199 | expect(this.onRequest).to.have.been.calledWith([
200 | 'SpotifyPlugin', 'play', [1, ['spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 'spotify:track:0IqKeD8ZSP72KbGYyzEcAs'], 1]
201 | ]);
202 | });
203 |
204 | it('should play successfully', function() {
205 | expect(this.callback).to.have.been.calledWith(null);
206 | });
207 | });
208 | });
209 |
210 | describe('#setURIs', function() {
211 | beforeEach(function() {
212 | mockExec(1, null, this.onRequest);
213 | this.player.id = 1;
214 | this.player.setURIs(['spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 'spotify:track:0IqKeD8ZSP72KbGYyzEcAs'], this.callback);
215 | });
216 |
217 | it('should send the correct parameters to native', function() {
218 | expect(this.onRequest).to.have.been.calledWith([
219 | 'SpotifyPlugin', 'setURIs', [1, ['spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 'spotify:track:0IqKeD8ZSP72KbGYyzEcAs']]
220 | ]);
221 | });
222 |
223 | it('should setURIs successfully', function() {
224 | expect(this.callback).to.have.been.calledWith(null);
225 | });
226 | });
227 |
228 | describe('#playURIsFromIndex', function() {
229 | beforeEach(function() {
230 | mockExec(1, null, this.onRequest);
231 | this.player.id = 1;
232 | this.player.playURIsFromIndex(65, this.callback);
233 | });
234 |
235 | it('should send the correct parameters to native', function() {
236 | expect(this.onRequest).to.have.been.calledWith([
237 | 'SpotifyPlugin', 'playURIsFromIndex', [1, 65]
238 | ]);
239 | });
240 |
241 | it('should playURIsFromIndex successfully', function() {
242 | expect(this.callback).to.have.been.calledWith(null);
243 | });
244 | });
245 |
246 | describe('#queue', function() {
247 | describe('without clearQueue', function() {
248 | beforeEach(function() {
249 | mockExec(1, null, this.onRequest);
250 | this.player.id = 1;
251 | this.player.queue(['spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 'spotify:track:0IqKeD8ZSP72KbGYyzEcAs'], this.callback);
252 | });
253 |
254 | it('should send the correct parameters to native', function() {
255 | expect(this.onRequest).to.have.been.calledWith([
256 | 'SpotifyPlugin', 'queue', [1, ['spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 'spotify:track:0IqKeD8ZSP72KbGYyzEcAs'], false]
257 | ]);
258 | });
259 |
260 | it('should queue successfully', function() {
261 | expect(this.callback).to.have.been.calledWith(null);
262 | });
263 | });
264 |
265 | describe('with clearQueue', function() {
266 | beforeEach(function() {
267 | mockExec(1, null, this.onRequest);
268 | this.player.id = 1;
269 | this.player.queue(['spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 'spotify:track:0IqKeD8ZSP72KbGYyzEcAs'], true, this.callback);
270 | });
271 |
272 | it('should send the correct parameters to native', function() {
273 | expect(this.onRequest).to.have.been.calledWith([
274 | 'SpotifyPlugin', 'queue', [1, ['spotify:track:3XpXhVtZwqh2eM5d9ieXT5', 'spotify:track:0IqKeD8ZSP72KbGYyzEcAs'], true]
275 | ]);
276 | });
277 |
278 | it('should queue successfully', function() {
279 | expect(this.callback).to.have.been.calledWith(null);
280 | });
281 | });
282 | });
283 |
284 | describe('#queuePlay', function() {
285 | beforeEach(function() {
286 | mockExec(1, null, this.onRequest);
287 | this.player.id = 1;
288 | this.player.queuePlay(this.callback);
289 | });
290 |
291 | it('should send the correct parameters to native', function() {
292 | expect(this.onRequest).to.have.been.calledWith([
293 | 'SpotifyPlugin', 'queuePlay', [1]
294 | ]);
295 | });
296 |
297 | it('should queuePlay successfully', function() {
298 | expect(this.callback).to.have.been.calledWith(null);
299 | });
300 | });
301 |
302 | describe('#queueClear', function() {
303 | beforeEach(function() {
304 | mockExec(1, null, this.onRequest);
305 | this.player.id = 1;
306 | this.player.queueClear(this.callback);
307 | });
308 |
309 | it('should send the correct parameters to native', function() {
310 | expect(this.onRequest).to.have.been.calledWith([
311 | 'SpotifyPlugin', 'queueClear', [1]
312 | ]);
313 | });
314 |
315 | it('should queuePlay successfully', function() {
316 | expect(this.callback).to.have.been.calledWith(null);
317 | });
318 | });
319 |
320 | describe('#stop', function() {
321 | beforeEach(function() {
322 | mockExec(1, null, this.onRequest);
323 | this.player.id = 1;
324 | this.player.stop(this.callback);
325 | });
326 |
327 | it('should send the correct parameters to native', function() {
328 | expect(this.onRequest).to.have.been.calledWith([
329 | 'SpotifyPlugin', 'stop', [1]
330 | ]);
331 | });
332 |
333 | it('should stop successfully', function() {
334 | expect(this.callback).to.have.been.calledWith(null);
335 | });
336 | });
337 |
338 | describe('#skipNext', function() {
339 | beforeEach(function() {
340 | mockExec(1, null, this.onRequest);
341 | this.player.id = 1;
342 | this.player.skipNext(this.callback);
343 | });
344 |
345 | it('should send the correct parameters to native', function() {
346 | expect(this.onRequest).to.have.been.calledWith([
347 | 'SpotifyPlugin', 'skipNext', [1]
348 | ]);
349 | });
350 |
351 | it('should skipNext successfully', function() {
352 | expect(this.callback).to.have.been.calledWith(null);
353 | });
354 | });
355 |
356 | describe('#skipPrevious', function() {
357 | beforeEach(function() {
358 | mockExec(1, null, this.onRequest);
359 | this.player.id = 1;
360 | this.player.skipPrevious(this.callback);
361 | });
362 |
363 | it('should send the correct parameters to native', function() {
364 | expect(this.onRequest).to.have.been.calledWith([
365 | 'SpotifyPlugin', 'skipPrevious', [1]
366 | ]);
367 | });
368 |
369 | it('should skipPrevious successfully', function() {
370 | expect(this.callback).to.have.been.calledWith(null);
371 | });
372 | });
373 |
374 | describe('#seekToOffset', function() {
375 | beforeEach(function() {
376 | mockExec(1, null, this.onRequest);
377 | this.player.id = 1;
378 | this.player.seekToOffset(3, this.callback);
379 | });
380 |
381 | it('should send the correct parameters to native', function() {
382 | expect(this.onRequest).to.have.been.calledWith([
383 | 'SpotifyPlugin', 'seekToOffset', [1, 3]
384 | ]);
385 | });
386 |
387 | it('should seekToOffset successfully', function() {
388 | expect(this.callback).to.have.been.calledWith(null);
389 | });
390 | });
391 |
392 | describe('#getIsPlaying', function() {
393 | beforeEach(function() {
394 | mockExec(1, true, this.onRequest);
395 | this.player.id = 1;
396 | this.player.getIsPlaying(this.callback);
397 | });
398 |
399 | it('should send the correct parameters to native', function() {
400 | expect(this.onRequest).to.have.been.calledWith([
401 | 'SpotifyPlugin', 'getIsPlaying', [1]
402 | ]);
403 | });
404 |
405 | it('should getIsPlaying successfully', function() {
406 | expect(this.callback).to.have.been.calledWith(null, true);
407 | });
408 | });
409 |
410 | describe('#setIsPlaying', function() {
411 | beforeEach(function() {
412 | mockExec(1, null, this.onRequest);
413 | this.player.id = 1;
414 | this.player.setIsPlaying(true, this.callback);
415 | });
416 |
417 | it('should send the correct parameters to native', function() {
418 | expect(this.onRequest).to.have.been.calledWith([
419 | 'SpotifyPlugin', 'setIsPlaying', [1, true]
420 | ]);
421 | });
422 |
423 | it('should setIsPlaying successfully', function() {
424 | expect(this.callback).to.have.been.calledWith(null);
425 | });
426 | });
427 |
428 | describe('#getVolume', function() {
429 | beforeEach(function() {
430 | mockExec(1, 0.5, this.onRequest);
431 | this.player.id = 1;
432 | this.player.getVolume(this.callback);
433 | });
434 |
435 | it('should send the correct parameters to native', function() {
436 | expect(this.onRequest).to.have.been.calledWith([
437 | 'SpotifyPlugin', 'getVolume', [1]
438 | ]);
439 | });
440 |
441 | it('should getVolume successfully', function() {
442 | expect(this.callback).to.have.been.calledWith(null, 0.5);
443 | });
444 | });
445 |
446 | describe('#setVolume', function() {
447 | beforeEach(function() {
448 | mockExec(1, null, this.onRequest);
449 | this.player.id = 1;
450 | this.player.setVolume(0.75, this.callback);
451 | });
452 |
453 | it('should send the correct parameters to native', function() {
454 | expect(this.onRequest).to.have.been.calledWith([
455 | 'SpotifyPlugin', 'setVolume', [1, 0.75]
456 | ]);
457 | });
458 |
459 | it('should setVolume successfully', function() {
460 | expect(this.callback).to.have.been.calledWith(null);
461 | });
462 | });
463 |
464 | describe('#getRepeat', function() {
465 | beforeEach(function() {
466 | mockExec(1, true, this.onRequest);
467 | this.player.id = 1;
468 | this.player.getRepeat(this.callback);
469 | });
470 |
471 | it('should send the correct parameters to native', function() {
472 | expect(this.onRequest).to.have.been.calledWith([
473 | 'SpotifyPlugin', 'getRepeat', [1]
474 | ]);
475 | });
476 |
477 | it('should getRepeat successfully', function() {
478 | expect(this.callback).to.have.been.calledWith(null, true);
479 | });
480 | });
481 |
482 | describe('#setRepeat', function() {
483 | beforeEach(function() {
484 | mockExec(1, null, this.onRequest);
485 | this.player.id = 1;
486 | this.player.setRepeat(true, this.callback);
487 | });
488 |
489 | it('should send the correct parameters to native', function() {
490 | expect(this.onRequest).to.have.been.calledWith([
491 | 'SpotifyPlugin', 'setRepeat', [1, true]
492 | ]);
493 | });
494 |
495 | it('should setRepeat successfully', function() {
496 | expect(this.callback).to.have.been.calledWith(null);
497 | });
498 | });
499 |
500 | describe('#getShuffle', function() {
501 | beforeEach(function() {
502 | mockExec(1, true, this.onRequest);
503 | this.player.id = 1;
504 | this.player.getShuffle(this.callback);
505 | });
506 |
507 | it('should send the correct parameters to native', function() {
508 | expect(this.onRequest).to.have.been.calledWith([
509 | 'SpotifyPlugin', 'getShuffle', [1]
510 | ]);
511 | });
512 |
513 | it('should getShuffle successfully', function() {
514 | expect(this.callback).to.have.been.calledWith(null, true);
515 | });
516 | });
517 |
518 | describe('#setShuffle', function() {
519 | beforeEach(function() {
520 | mockExec(1, null, this.onRequest);
521 | this.player.id = 1;
522 | this.player.setShuffle(true, this.callback);
523 | });
524 |
525 | it('should send the correct parameters to native', function() {
526 | expect(this.onRequest).to.have.been.calledWith([
527 | 'SpotifyPlugin', 'setShuffle', [1, true]
528 | ]);
529 | });
530 |
531 | it('should setShuffle successfully', function() {
532 | expect(this.callback).to.have.been.calledWith(null);
533 | });
534 | });
535 |
536 | describe('#getDiskCacheSizeLimit', function() {
537 | beforeEach(function() {
538 | mockExec(1, 3000, this.onRequest);
539 | this.player.id = 1;
540 | this.player.getDiskCacheSizeLimit(this.callback);
541 | });
542 |
543 | it('should send the correct parameters to native', function() {
544 | expect(this.onRequest).to.have.been.calledWith([
545 | 'SpotifyPlugin', 'getDiskCacheSizeLimit', [1]
546 | ]);
547 | });
548 |
549 | it('should getDiskCacheSizeLimit successfully', function() {
550 | expect(this.callback).to.have.been.calledWith(null, 3000);
551 | });
552 | });
553 |
554 | describe('#setDiskCacheSizeLimit', function() {
555 | beforeEach(function() {
556 | mockExec(1, null, this.onRequest);
557 | this.player.id = 1;
558 | this.player.setDiskCacheSizeLimit(2524, this.callback);
559 | });
560 |
561 | it('should send the correct parameters to native', function() {
562 | expect(this.onRequest).to.have.been.calledWith([
563 | 'SpotifyPlugin', 'setDiskCacheSizeLimit', [1, 2524]
564 | ]);
565 | });
566 |
567 | it('should setDiskCacheSizeLimit successfully', function() {
568 | expect(this.callback).to.have.been.calledWith(null);
569 | });
570 | });
571 |
572 | describe('#getTargetBitrate', function() {
573 | beforeEach(function() {
574 | mockExec(1, 2, this.onRequest);
575 | this.player.id = 1;
576 | this.player.getTargetBitrate(this.callback);
577 | });
578 |
579 | it('should send the correct parameters to native', function() {
580 | expect(this.onRequest).to.have.been.calledWith([
581 | 'SpotifyPlugin', 'getTargetBitrate', [1]
582 | ]);
583 | });
584 |
585 | it('should getTargetBitrate successfully', function() {
586 | expect(this.callback).to.have.been.calledWith(null, 2);
587 | });
588 | });
589 |
590 | describe('#setTargetBitrate', function() {
591 | beforeEach(function() {
592 | mockExec(1, null, this.onRequest);
593 | this.player.id = 1;
594 | this.player.setTargetBitrate(1, this.callback);
595 | });
596 |
597 | it('should send the correct parameters to native', function() {
598 | expect(this.onRequest).to.have.been.calledWith([
599 | 'SpotifyPlugin', 'setTargetBitrate', [1, 1]
600 | ]);
601 | });
602 |
603 | it('should setTargetBitrate successfully', function() {
604 | expect(this.callback).to.have.been.calledWith(null);
605 | });
606 | });
607 |
608 | describe('#getLoggedIn', function() {
609 | beforeEach(function() {
610 | mockExec(1, false, this.onRequest);
611 | this.player.id = 1;
612 | this.player.getLoggedIn(this.callback);
613 | });
614 |
615 | it('should send the correct parameters to native', function() {
616 | expect(this.onRequest).to.have.been.calledWith([
617 | 'SpotifyPlugin', 'getLoggedIn', [1]
618 | ]);
619 | });
620 |
621 | it('should getLoggedIn successfully', function() {
622 | expect(this.callback).to.have.been.calledWith(null, false);
623 | });
624 | });
625 |
626 | describe('#getQueueSize', function() {
627 | beforeEach(function() {
628 | mockExec(1, 25, this.onRequest);
629 | this.player.id = 1;
630 | this.player.getQueueSize(this.callback);
631 | });
632 |
633 | it('should send the correct parameters to native', function() {
634 | expect(this.onRequest).to.have.been.calledWith([
635 | 'SpotifyPlugin', 'getQueueSize', [1]
636 | ]);
637 | });
638 |
639 | it('should getQueueSize successfully', function() {
640 | expect(this.callback).to.have.been.calledWith(null, 25);
641 | });
642 | });
643 |
644 | describe('#getTrackListSize', function() {
645 | beforeEach(function() {
646 | mockExec(1, 5, this.onRequest);
647 | this.player.id = 1;
648 | this.player.getTrackListSize(this.callback);
649 | });
650 |
651 | it('should send the correct parameters to native', function() {
652 | expect(this.onRequest).to.have.been.calledWith([
653 | 'SpotifyPlugin', 'getTrackListSize', [1]
654 | ]);
655 | });
656 |
657 | it('should getQueueSize successfully', function() {
658 | expect(this.callback).to.have.been.calledWith(null, 5);
659 | });
660 | });
661 |
662 | describe('#getTrackMetadata', function() {
663 | beforeEach(function() {
664 | this.currentTrack = {
665 | name: 'Song 2',
666 | uri: 'spotify:track:3GfOAdcoc3X5GPiiXmpBjK',
667 | artist: {
668 | name: 'Blur',
669 | uri: 'spotify:artist:7MhMgCo0Bl0Kukl93PZbYS',
670 | },
671 | album: {
672 | name: 'Blur: The Best Of',
673 | uri: 'spotify:album:1bgkxe4t0HNeLn9rhrx79x',
674 | },
675 | duration: 122
676 | };
677 |
678 | this.player.id = 1;
679 |
680 | mockExec(1, this.currentTrack, this.onRequest);
681 | });
682 |
683 | describe('current track', function() {
684 | beforeEach(function() {
685 | this.player.getTrackMetadata(this.callback);
686 | });
687 |
688 | it('should send the correct parameters to native', function() {
689 | expect(this.onRequest).to.have.been.calledWith([
690 | 'SpotifyPlugin', 'getTrackMetadata', [1]
691 | ]);
692 | });
693 |
694 | it('should getTrackMetadata successfully', function() {
695 | expect(this.callback).to.have.been.calledWith(null, this.currentTrack);
696 | });
697 | });
698 |
699 | describe('with trackID (absolute)', function() {
700 | beforeEach(function() {
701 | this.player.getTrackMetadata(3, this.callback);
702 | });
703 |
704 | it('should send the correct parameters to native', function() {
705 | expect(this.onRequest).to.have.been.calledWith([
706 | 'SpotifyPlugin', 'getTrackMetadata', [1, 3]
707 | ]);
708 | });
709 |
710 | it('should getTrackMetadata successfully', function() {
711 | expect(this.callback).to.have.been.calledWith(null, this.currentTrack);
712 | });
713 | });
714 |
715 | describe('with trackID (relative)', function() {
716 | beforeEach(function() {
717 | this.player.getTrackMetadata(2, true, this.callback);
718 | });
719 |
720 | it('should send the correct parameters to native', function() {
721 | expect(this.onRequest).to.have.been.calledWith([
722 | 'SpotifyPlugin', 'getTrackMetadata', [1, 2, true]
723 | ]);
724 | });
725 |
726 | it('should getTrackMetadata successfully', function() {
727 | expect(this.callback).to.have.been.calledWith(null, this.currentTrack);
728 | });
729 | });
730 | });
731 |
732 | describe('#getCurrentPlaybackPosition', function() {
733 | beforeEach(function() {
734 | mockExec(1, 139, this.onRequest);
735 | this.player.id = 1;
736 | this.player.getCurrentPlaybackPosition(this.callback);
737 | });
738 |
739 | it('should send the correct parameters to native', function() {
740 | expect(this.onRequest).to.have.been.calledWith([
741 | 'SpotifyPlugin', 'getCurrentPlaybackPosition', [1]
742 | ]);
743 | });
744 |
745 | it('should getCurrentPlaybackPosition successfully', function() {
746 | expect(this.callback).to.have.been.calledWith(null, 139);
747 | });
748 | });
749 | });
750 |
--------------------------------------------------------------------------------
/test/js/spec/spotify/auth.test.js:
--------------------------------------------------------------------------------
1 | describe('auth', function() {
2 | function createExpirationDate(date) {
3 | date = date || new Date();
4 |
5 | return date.getUTCFullYear() + "-" + date.getUTCMonth() + "-" + date.getUTCDate() + " " + date.getUTCHours() + ":" + date.getUTCMinutes() + ":" + date.getUTCSeconds() + " GMT";
6 | }
7 |
8 | var session = {canonicalUsername: 'antman', accessToken: 'Xcd4r234234fdfa_dfsadf3', encryptedRefreshToken: 'sdfsdfds724dfsdf234dsf', expirationDate: createExpirationDate()}
9 | , newSession = {canonicalUsername: 'antman', accessToken: 'XASDFasdfd4r234234fdfa_dfsdsadfsdf3', encryptedRefreshToken: '4334fsdfdSDF234', expirationDate: createExpirationDate()}
10 |
11 | var auth;
12 |
13 | before(function() {
14 | auth = require('com.timflapper.spotify.auth');
15 | });
16 |
17 | beforeEach(function() {
18 | this.callback = sinon.spy();
19 | this.onRequest = sinon.spy();
20 | });
21 |
22 | afterEach(function() {
23 | restoreMockExec();
24 | });
25 |
26 | describe('#authenticate', function() {
27 | describe('succesful authentication', function() {
28 | beforeEach(function() {
29 | mockExec(1, session, this.onRequest);
30 | });
31 |
32 | describe('without scope', function() {
33 | beforeEach(function() {
34 | spotify.authenticate('test-scheme', 'aRandomClientId1234', 'code', 'http://tok.en', this.callback);
35 | });
36 |
37 | it('should send the "streaming" scope to native', function() {
38 | expect(this.onRequest).to.have.been.calledWith([
39 | 'SpotifyPlugin', 'authenticate', ['test-scheme', 'aRandomClientId1234', 'code', 'http://tok.en', ['streaming']]
40 | ]);
41 | });
42 |
43 | it('should call callback with the session', function() {
44 | expect(this.callback).to.have.been.calledWith(null, session);
45 | })
46 | });
47 |
48 | describe('with scope', function() {
49 | beforeEach(function() {
50 | spotify.authenticate('test-scheme', 'aRandomClientId1234', 'code', 'http://tok.en', ['somescope'], this.callback);
51 | });
52 |
53 | it('should send the "somescope" scope to native', function() {
54 | expect(this.onRequest).to.have.been.calledWith([
55 | 'SpotifyPlugin', 'authenticate', ['test-scheme', 'aRandomClientId1234', 'code', 'http://tok.en', ['somescope']]
56 | ]);
57 | });
58 |
59 | it('should call callback with the session', function() {
60 | expect(this.callback).to.have.been.calledWith(null, session);
61 | });
62 | });
63 | });
64 |
65 | describe('failed authentication', function() {
66 | beforeEach(function() {
67 | mockExec(9, 'Login to Spotify failed because of invalid credentials.', this.onRequest);
68 | spotify.authenticate('test-scheme', 'aRandomClientId1234', 'code', 'http://tok.en', this.callback);
69 | });
70 |
71 | it('should send back "Login to Spotify failed because of invalid credentials."', function() {
72 | expect(this.callback).to.have.been.calledWith('Login to Spotify failed because of invalid credentials.');
73 | });
74 | });
75 | });
76 |
77 | describe('#isSessionValid', function() {
78 | describe('session is valid', function() {
79 | beforeEach(function() {
80 | mockExec(1, true, this.onRequest);
81 | spotify.isSessionValid(session, this.callback);
82 | });
83 |
84 | it('should send the session to native', function() {
85 | expect(this.onRequest).to.have.been.calledWith([
86 | 'SpotifyPlugin', 'isSessionValid', [session]
87 | ]);
88 | });
89 |
90 | it('should send back true', function() {
91 | expect(this.callback).to.have.been.calledWith(null, true);
92 | });
93 | });
94 |
95 | describe('session is not valid', function() {
96 | beforeEach(function() {
97 | mockExec(1, false, this.onRequest);
98 | spotify.isSessionValid(session, this.callback);
99 | });
100 |
101 | it('should send the session to native', function() {
102 | expect(this.onRequest).to.have.been.calledWith([
103 | 'SpotifyPlugin', 'isSessionValid', [session]
104 | ]);
105 | });
106 |
107 | it('should send back true', function() {
108 | expect(this.callback).to.have.been.calledWith(null, false);
109 | });
110 | });
111 | });
112 |
113 | describe('#renewSession', function() {
114 | describe('when session is still valid', function() {
115 | beforeEach(function() {
116 | sinon.stub(auth, "isSessionValid", function(session, callback) {
117 | callback(null, true);
118 | });
119 | mockExec(1, session, this.onRequest);
120 | spotify.renewSession(session, 'http://tok.en/refresh', this.callback);
121 | });
122 |
123 | afterEach(function() {
124 | auth.isSessionValid.restore();
125 | });
126 |
127 | it('should not send the session and tokenRefreshURL to native', function() {
128 | expect(this.onRequest).to.not.have.been.calledWith([
129 | 'SpotifyPlugin', 'renewSession', [session, 'http://tok.en/refresh']
130 | ]);
131 | });
132 |
133 | it('should send back the same session', function() {
134 | expect(this.callback).to.have.been.calledWith(null, session);
135 | });
136 | });
137 |
138 | describe('when session is invalid', function() {
139 | beforeEach(function() {
140 | sinon.stub(auth, "isSessionValid", function(session, callback) {
141 | callback(null, false);
142 | });
143 | mockExec(1, newSession, this.onRequest);
144 | spotify.renewSession(session, 'http://tok.en/refresh', this.callback);
145 | });
146 |
147 | afterEach(function() {
148 | auth.isSessionValid.restore();
149 | });
150 |
151 | it('should send the session and tokenRefreshURL to native', function() {
152 | expect(this.onRequest).to.have.been.calledWith([
153 | 'SpotifyPlugin', 'renewSession', [session, 'http://tok.en/refresh']
154 | ]);
155 | });
156 |
157 | it('should send back a different session', function() {
158 | expect(this.callback).to.have.been.calledWith(null, newSession);
159 | });
160 | });
161 |
162 |
163 | describe('force renew session', function() {
164 | beforeEach(function() {
165 | mockExec(1, newSession, this.onRequest);
166 | spotify.renewSession(session, 'http://tok.en/refresh', true, this.callback);
167 | });
168 |
169 | it('should send the session and tokenRefreshURL to native', function() {
170 | expect(this.onRequest).to.have.been.calledWith([
171 | 'SpotifyPlugin', 'renewSession', [session, 'http://tok.en/refresh']
172 | ]);
173 | });
174 |
175 | it('should send back a different session', function() {
176 | expect(this.callback).to.have.been.calledWith(null, newSession);
177 | });
178 | });
179 | });
180 | });
181 |
--------------------------------------------------------------------------------
/test/js/spec/support/android/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timflapper/cordova-spotify-plugin/424f3a900c4915a0cdf0e20fccd51c7185349bb7/test/js/spec/support/android/.gitkeep
--------------------------------------------------------------------------------
/test/js/spec/support/ios/mock-exec.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var exec = require('cordova/exec');
3 |
4 | var xhr, mockResults = [], first = true;
5 |
6 | window.mockExec = function(status, result, onRequest, noCallback) {
7 | noCallback = noCallback || false;
8 | if (first) {
9 | first = false;
10 | exec.setJsToNativeBridgeMode(exec.jsToNativeModes.XHR_NO_PAYLOAD);
11 | }
12 |
13 | mockResults.push({
14 | status: status,
15 | result: result,
16 | onRequest: onRequest,
17 | noCallback: noCallback
18 | });
19 |
20 | if (! xhr) {
21 | xhr = sinon.useFakeXMLHttpRequest();
22 |
23 | xhr.onCreate = function(req) {
24 | var payloads = JSON.parse(exec.nativeFetchMessages());
25 |
26 | while (mockResults.length > 0) {
27 | var mockResult = mockResults.shift()
28 | , payload = payloads.shift()
29 | , callbackId = payload.shift();
30 |
31 | if (mockResult.onRequest) mockResult.onRequest(payload);
32 | if (! mockResult.noCallback) {
33 | var more = exec.nativeCallback(callbackId, mockResult.status, mockResult.result, false);
34 |
35 | if (more) {
36 | payloads = payloads.concat(JSON.parse(more));
37 | }
38 | }
39 | }
40 |
41 | xhr.restore();
42 | xhr = null;
43 | };
44 | }
45 | };
46 |
47 | window.restoreMockExec = function() {
48 | if (xhr) {
49 | xhr.restore();
50 | xhr = null;
51 | }
52 |
53 | mockResults = [];
54 | }
55 | })();
56 |
--------------------------------------------------------------------------------
/test/js/spec/support/test-helper.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | if (cordova.platformId === 'ios') {
3 | loadScript('spec/support/ios/mock-exec.js');
4 | } else if (cordova.platformId === 'android') {
5 | console.error('Platform ' + cordova.platformId + ' is not yet supported.');
6 | } else {
7 | console.error('Platform ' + cordova.platformId + ' is not a valid platform.');
8 | }
9 |
10 | function onScriptError(err) {
11 | console.error('The script ' + err.target.src + ' is not accessible.');
12 | }
13 |
14 | function loadScript(url, callback) {
15 | var script = document.createElement("script");
16 | script.onload = callback;
17 | script.onerror = onScriptError;
18 | script.src = url;
19 | document.head.appendChild(script);
20 | }
21 | })();
22 |
--------------------------------------------------------------------------------
/www/lib/event-dispatcher.js:
--------------------------------------------------------------------------------
1 | function EventDispatcher() {
2 | var self = this
3 | , events = {};
4 |
5 | this.dispatchEvent = function(event, args) {
6 | var i, listeners;
7 |
8 | args = args || [];
9 |
10 | if ((event in events) === false) {
11 | if (event === 'error')
12 | throw new Error(args[0]);
13 |
14 | if (event === 'message')
15 | alert(args[0]);
16 |
17 | return;
18 | }
19 |
20 | listeners = events[event];
21 |
22 | if (listeners.length === 1) {
23 | listeners[0].apply(self, args);
24 | } else if (listeners.length > 1) {
25 | listeners = events[event].slice();
26 |
27 | listeners.forEach(function(item) {
28 | item.apply(self, args);
29 | });
30 | }
31 | };
32 |
33 | this.addEventListener = function(event, listener) {
34 | if (typeof listener !== 'function')
35 | throw new Error('listener must be a function');
36 |
37 | if ((event in events) === false)
38 | events[event] = [];
39 |
40 | events[event].push(listener);
41 | };
42 |
43 | this.removeEventListener = function(event, listener) {
44 | if (typeof listener !== 'function')
45 | throw new Error('listener must be a function');
46 |
47 | if ((event in events) === false)
48 | return;
49 |
50 | var updatedArray = [];
51 |
52 | events[event].forEach(function(func, index) {
53 | if (func === listener)
54 | return;
55 |
56 | updatedArray.push(func);
57 | });
58 |
59 | events[event] = updatedArray;
60 | };
61 | }
62 | module.exports = EventDispatcher;
63 |
--------------------------------------------------------------------------------
/www/lib/remote.js:
--------------------------------------------------------------------------------
1 | var reqwest = require('./vendors/reqwest');
2 |
3 | var apiUrl = 'https://api.spotify.com/v1';
4 |
5 | module.exports = remote;
6 |
7 | function remote(options, callback) {
8 | if (options === undefined)
9 | throw new Error('This method requires two arguments (options, callback)');
10 |
11 | var req = {
12 | type: 'json',
13 | contentType: 'application/json',
14 | method: options.method || 'get',
15 | crossOrigin: true,
16 | headers: {}
17 | };
18 |
19 | if (options.uri) {
20 | req.url = apiUrl + options.uri;
21 | } else if (options.url) {
22 | req.url = options.url;
23 | } else {
24 | return callback('No URL or URI set');
25 | }
26 |
27 | if (options.session)
28 | req.headers.Authorization = 'Bearer ' + options.session.accessToken;
29 |
30 | if (options.data)
31 | req.data = options.data;
32 |
33 | reqwest(req)
34 | .then(function (data) {
35 | paginate(data, session);
36 |
37 | callback(null, data);
38 | })
39 | .fail(function (err, msg) {
40 | if (err) return callback(err.statusText);
41 | if (msg) return callback(msg);
42 | callback("An unkown error occurred");
43 | });
44 | }
45 |
46 | function paginate(data, session) {
47 | if (Array.isArray(data)) {
48 | data.forEach(function(item) {
49 | paginate(item, session);
50 | });
51 |
52 | return;
53 | }
54 |
55 | if (data.next) {
56 | var nextOpts = {url: data.next};
57 |
58 | if (session)
59 | nextOpts.session = session;
60 |
61 | data.next = function(callback) {
62 | remote(nextOpts, onRemoteResult);
63 |
64 | function onRemoteResult(err, data) {
65 | if (err) return callback(err);
66 |
67 | paginate(data, session);
68 |
69 | callback(null, data);
70 | }
71 | }
72 | }
73 |
74 | if (data.prev) {
75 | var prevOpts = {url: data.prev};
76 |
77 | if (session)
78 | prevOpts.session = session;
79 |
80 | data.prev = function(callback) {
81 | remote(prevOpts, onRemoteResult);
82 |
83 | function onRemoteResult(err, data) {
84 | if (err) return callback(err);
85 |
86 | paginate(data, session);
87 |
88 | callback(null, data);
89 | }
90 | }
91 | }
92 |
93 | if (data.artists) paginate(data.artists, session);
94 | if (data.tracks) paginate(data.tracks, session);
95 | if (data.albums) paginate(data.albums, session);
96 | }
97 |
--------------------------------------------------------------------------------
/www/lib/utils.js:
--------------------------------------------------------------------------------
1 | var exec = require('cordova/exec');
2 |
3 | var noop = function() {};
4 |
5 | var utils = module.exports = {
6 | noop: noop,
7 | exec: function(/*action, [params], [callback]*/) {
8 | var action, args = [], callback = noop;
9 | var i, argsLength = arguments.length;
10 |
11 | if (arguments.length === 0) throw new Error('No arguments received 1 or more expected.');
12 | action = arguments[0];
13 |
14 | for (i=1;i < argsLength;i++) args[i] = arguments[i];
15 |
16 | if (args.length > 0 && typeof args.slice(-1)[0] === 'function') {
17 | callback = args.splice(-1, 1)[0];
18 | }
19 |
20 | function onSuccess(result) { callback(null, result); }
21 | function onError(error) { callback(error); }
22 |
23 | exec(onSuccess, onError, 'SpotifyPlugin', action, args);
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/www/spotify/audio-player.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | , EventDispatcher = require('./event-dispatcher');
3 |
4 | var exec = utils.exec
5 | , noop = utils.noop;
6 |
7 | function AudioPlayer(clientId) {
8 | EventDispatcher.call(this);
9 |
10 | this.id = undefined;
11 | this.clientId = clientId;
12 | }
13 |
14 | AudioPlayer.prototype = Object.create(EventDispatcher.prototype);
15 | AudioPlayer.prototype.constructor = AudioPlayer;
16 |
17 | module.exports.createAudioPlayer = function(clientId) {
18 | return new AudioPlayer(clientId);
19 | }
20 |
21 | var events = module.exports.AudioPlayer = {
22 | EVENT_LOGIN: 'login',
23 | EVENT_LOGOUT: 'logout',
24 | EVENT_PERMISSION_LOST: 'permissionLost',
25 | EVENT_ERROR: 'error',
26 | EVENT_MESSAGE: 'message',
27 | EVENT_PLAYBACK_STATUS: 'playbackStatus',
28 | EVENT_SEEK_TO_OFFSET: 'seekToOffset'
29 | };
30 |
31 | AudioPlayer.prototype.login = function(session, callback) {
32 | var self = this;
33 |
34 | exec('createAudioPlayerAndLogin', self.clientId, session, loginCallback);
35 |
36 | function loginCallback(error, id) {
37 | if (error) {
38 | if (! callback)
39 | return self.dispatchEvent(events.EVENT_ERROR, [error]);
40 |
41 | return callback(error);
42 | }
43 |
44 | self.id = id;
45 |
46 | exec('addAudioPlayerEventListener', self.id, onEventCallback);
47 |
48 | if (! callback)
49 | return self.dispatchEvent(events.EVENT_LOGIN);
50 |
51 | callback(null);
52 | };
53 |
54 | function onEventCallback(error, result) {
55 | if (error)
56 | return self.dispatchEvent.call(self, events.EVENT_ERROR, [error]);
57 |
58 | self.dispatchEvent.call(self, result.type, result.args);
59 | }
60 | };
61 |
62 | AudioPlayer.prototype.logout = function(callback) {
63 | var self = this;
64 |
65 | exec('audioPlayerLogout', self.id, logoutCallback);
66 |
67 | function logoutCallback(error) {
68 | if (error) {
69 | if (! callback)
70 | return self.dispatchEvent.call(self, events.EVENT_ERROR, [error]);
71 |
72 | return callback(error);
73 | }
74 |
75 | if (! callback)
76 | return self.dispatchEvent.call(self, events.EVENT_LOGOUT);
77 |
78 | callback(null);
79 | }
80 | };
81 |
82 | AudioPlayer.prototype.play = function(data, fromIndex, callback) {
83 | if (callback === undefined && typeof fromIndex === 'function')
84 | callback = fromIndex, fromIndex = null;
85 |
86 | if (typeof fromIndex !== 'number') fromIndex = 0;
87 |
88 | exec('play', this.id, data, fromIndex, callback);
89 | };
90 |
91 | AudioPlayer.prototype.setURIs = function(data, callback) {
92 | exec('setURIs', this.id, data, callback);
93 | };
94 |
95 | AudioPlayer.prototype.playURIsFromIndex = function(fromIndex, callback) {
96 | exec('playURIsFromIndex', this.id, fromIndex, callback);
97 | }
98 |
99 | AudioPlayer.prototype.queue = function(data, clearQueue, callback) {
100 | if (callback === undefined && typeof clearQueue === 'function')
101 | callback = clearQueue, clearQueue = false;
102 |
103 | exec('queue', this.id, data, clearQueue, callback);
104 | };
105 |
106 | AudioPlayer.prototype.queuePlay = function(callback) {
107 | exec('queuePlay', this.id, callback);
108 | }
109 |
110 | AudioPlayer.prototype.queueClear = function(callback) {
111 | exec('queueClear', this.id, callback);
112 | }
113 |
114 | AudioPlayer.prototype.stop = function(callback) {
115 | exec('stop', this.id, callback);
116 | }
117 |
118 | AudioPlayer.prototype.skipNext = function(callback) {
119 | exec('skipNext', this.id, callback);
120 | };
121 |
122 | AudioPlayer.prototype.skipPrevious = function(callback) {
123 | exec('skipPrevious', this.id, callback);
124 | };
125 |
126 | AudioPlayer.prototype.seekToOffset = function(offset, callback) {
127 | exec('seekToOffset', this.id, offset, callback);
128 | };
129 |
130 | AudioPlayer.prototype.getIsPlaying = function(callback) {
131 | exec('getIsPlaying', this.id, callback);
132 | };
133 |
134 | AudioPlayer.prototype.setIsPlaying = function(status, callback) {
135 | exec('setIsPlaying', this.id, status, callback);
136 | };
137 |
138 | AudioPlayer.prototype.getVolume = function(callback) {
139 | exec('getVolume', this.id, callback);
140 | };
141 |
142 | AudioPlayer.prototype.setVolume = function(volume, callback) {
143 | exec('setVolume', this.id, volume, callback);
144 | };
145 |
146 | AudioPlayer.prototype.getRepeat = function(callback) {
147 | exec('getRepeat', this.id, callback);
148 | };
149 |
150 | AudioPlayer.prototype.setRepeat = function(repeat, callback) {
151 | exec('setRepeat', this.id, repeat, callback);
152 | };
153 |
154 | AudioPlayer.prototype.getShuffle = function(callback) {
155 | exec('getShuffle', this.id, callback);
156 | };
157 |
158 | AudioPlayer.prototype.setShuffle = function(shuffle, callback) {
159 | exec('setShuffle', this.id, shuffle, callback);
160 | };
161 |
162 | AudioPlayer.prototype.getDiskCacheSizeLimit = function(callback) {
163 | exec('getDiskCacheSizeLimit', this.id, callback);
164 | };
165 |
166 | AudioPlayer.prototype.setDiskCacheSizeLimit = function(diskCacheSizeLimit, callback) {
167 | exec('setDiskCacheSizeLimit', this.id, diskCacheSizeLimit, callback);
168 | };
169 |
170 | AudioPlayer.prototype.getTargetBitrate = function(callback) {
171 | exec('getTargetBitrate', this.id, callback);
172 | }
173 |
174 | AudioPlayer.prototype.setTargetBitrate = function(bitrate, callback) {
175 | exec('setTargetBitrate', this.id, bitrate, callback);
176 | }
177 |
178 | AudioPlayer.prototype.getLoggedIn = function(callback) {
179 | exec('getLoggedIn', this.id, callback);
180 | };
181 |
182 | AudioPlayer.prototype.getQueueSize = function(callback) {
183 | exec('getQueueSize', this.id, callback);
184 | }
185 |
186 | AudioPlayer.prototype.getTrackListSize = function(callback) {
187 | exec('getTrackListSize', this.id, callback);
188 | }
189 |
190 | AudioPlayer.prototype.getTrackMetadata = function(trackID, relative, callback) {
191 | if (callback === undefined) {
192 | if (relative && typeof relative === 'function')
193 | callback = relative, relative = null;
194 | else if (trackID && typeof trackID === 'function')
195 | callback = trackID, trackID = null;
196 | }
197 |
198 | var args = ['getTrackMetadata', this.id];
199 |
200 | if (trackID) {
201 | args.push(trackID);
202 |
203 | if (relative) args.push(relative);
204 | }
205 |
206 | args.push(callback);
207 |
208 | exec.apply(this, args);
209 | };
210 |
211 | AudioPlayer.prototype.getCurrentPlaybackPosition = function(callback) {
212 | exec('getCurrentPlaybackPosition', this.id, callback);
213 | };
214 |
--------------------------------------------------------------------------------
/www/spotify/auth.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | , exec = utils.exec
3 | , noop = utils.noop;
4 |
5 | var auth = exports;
6 |
7 | auth.authenticate = function(urlScheme, clientId, responseType, tokenExchangeURL, scopes, callback) {
8 | if (callback === undefined) {
9 | if (scopes === undefined) {
10 | callback = tokenExchangeURL;
11 | tokenExchangeURL = null;
12 | } else {
13 | callback = scopes;
14 | }
15 |
16 | scopes = ['streaming'];
17 | }
18 |
19 | args = ['authenticate', urlScheme, clientId, responseType];
20 |
21 | if(responseType == 'code') args.push(tokenExchangeURL);
22 |
23 | args.push(scopes);
24 | args.push(callback);
25 |
26 | exec.apply(this, args);
27 | };
28 |
29 | auth.renewSession = function(session, tokenRefreshURL, forceRenew, callback) {
30 | if (callback === undefined && typeof forceRenew === 'function')
31 | callback = forceRenew, forceRenew = false;
32 |
33 | if (forceRenew === true)
34 | return renewSession(session, tokenRefreshURL, callback);
35 |
36 | auth.isSessionValid(session, function(err, valid) {
37 | if (err) return callback(err);
38 | if (valid) return callback(null, session);
39 |
40 | renewSession(session, tokenRefreshURL, callback);
41 | });
42 | };
43 |
44 | auth.isSessionValid = function(session, callback) {
45 | exec('isSessionValid', session, callback);
46 | };
47 |
48 | /* Private methods */
49 | function renewSession(session, tokenRefreshURL, callback) {
50 | exec('renewSession', session, tokenRefreshURL, callback);
51 | };
52 |
--------------------------------------------------------------------------------
/www/spotify/request.js:
--------------------------------------------------------------------------------
1 | var remote = require('./remote');
2 |
3 | var REMOTE_IDS = 0x0001
4 | , REMOTE_ID_IN_URL = 0x0002
5 | , REMOTE_USERNAME_IN_URL = 0x0004
6 | , REMOTE_DATA = 0x0008
7 | , REMOTE_DATA_JSON = REMOTE_DATA + 0x0010
8 | , REMOTE_SESSION = 0x0100
9 | , REMOTE_POST = 0x1000
10 | , REMOTE_PUT = 0x2000
11 | , REMOTE_DELETE = 0x4000;
12 |
13 | var requestMethods = {
14 | search: ['/search', REMOTE_DATA],
15 | getAlbum: ['/albums/$I', REMOTE_ID_IN_URL],
16 | getAlbums: ['/albums', REMOTE_IDS],
17 | getArtist: ['/artists/$I', REMOTE_ID_IN_URL],
18 | getArtists: ['/artists', REMOTE_IDS],
19 | getAlbumsOfArtist: ['/artists/$I/albums', REMOTE_ID_IN_URL],
20 | getTrack: ['/tracks/$I', REMOTE_ID_IN_URL],
21 | getTracks: ['/tracks', REMOTE_IDS],
22 | getProfile: ['/me', REMOTE_SESSION],
23 | getSavedTracks: ['/me/tracks', REMOTE_SESSION],
24 | getSavedTracksContain: ['/me/tracks/contains', REMOTE_SESSION + REMOTE_IDS],
25 | saveTracks: ['/me/tracks', REMOTE_SESSION + REMOTE_IDS + REMOTE_PUT],
26 | removeTracks: ['/me/tracks', REMOTE_SESSION + REMOTE_IDS + REMOTE_DELETE],
27 | getStarred: ['/users/$U/starred', REMOTE_SESSION],
28 | getPlaylists: ['/users/$U/playlists', REMOTE_SESSION],
29 | getPlaylist: ['/users/$U/playlists/$I', REMOTE_SESSION + REMOTE_ID_IN_URL],
30 | createPlaylist: ['/users/$U/playlists', REMOTE_SESSION + REMOTE_DATA_JSON + REMOTE_POST],
31 | changePlaylistDetails: ['/users/$U/playlists/$I', REMOTE_SESSION + REMOTE_DATA_JSON + REMOTE_PUT + REMOTE_ID_IN_URL],
32 | addTracksToPlaylist: ['/users/$U/playlists/$I/tracks', REMOTE_SESSION + REMOTE_DATA_JSON + REMOTE_POST + REMOTE_ID_IN_URL, ['uris']],
33 | replaceTracksOnPlaylist: ['/users/$U/playlists/$I/tracks', REMOTE_SESSION + REMOTE_DATA_JSON + REMOTE_PUT + REMOTE_ID_IN_URL, ['uris']],
34 | removeTracksFromPlaylist: ['/users/$U/playlists/$I/tracks', REMOTE_SESSION + REMOTE_DATA_JSON + REMOTE_DELETE + REMOTE_ID_IN_URL, ['tracks']]
35 | };
36 |
37 | Object.keys(requestMethods).forEach(function(key) {
38 | module.exports[key] = createRemoteMethod.apply(null, requestMethods[key]);
39 | });
40 |
41 | function createRemoteMethod(uri, type, dataKeys) {
42 | return function() {
43 | var options = {uri: uri}
44 | , args = Array.prototype.slice.call(arguments)
45 | , callback = args.pop();
46 |
47 | if (type & REMOTE_POST) {
48 | options.method = 'post';
49 | } else if (type & REMOTE_PUT) {
50 | options.method = 'put';
51 | } else if (type & REMOTE_DELETE) {
52 | options.method = 'delete';
53 | }
54 |
55 | if (type & REMOTE_SESSION) {
56 | options.session = args.pop();
57 | options.uri = options.uri.replace('$U', session.canonicalUsername);
58 | }
59 |
60 | if (type & REMOTE_ID_IN_URL)
61 | options.uri = options.uri.replace('$I', spotifyUriToId(args.shift()));
62 |
63 | if (type & REMOTE_IDS)
64 | options.uri = uriWithIds(options.uri, args.shift());
65 |
66 | if (type & REMOTE_DATA) {
67 | if (dataKeys) {
68 | options.data = {};
69 |
70 | dataKeys.forEach(function(key) {
71 | options.data[key] = args.shift();
72 | });
73 | } else {
74 | options.data = args.shift();
75 | }
76 |
77 | if (type & REMOTE_DATA_JSON)
78 | options.data = JSON.stringify(options.data);
79 | }
80 |
81 | remote(options, callback);
82 | };
83 | }
84 |
85 | function uriWithIds(uri, ids) {
86 | return uri + "?ids=" + spotifyUriToId(ids).join(',');;
87 | }
88 |
89 | function spotifyUriToId(uri) {
90 | if (Array.isArray(uri)) {
91 | var id, result = [];
92 |
93 | items.forEach(function(uri) {
94 | if (id = spotifyUriToId(uri))
95 | result.push(id);
96 | });
97 |
98 | return result;
99 | }
100 |
101 | if (matches = /^spotify:[^:]*:(.*)$/.exec(uri))
102 | return matches[1];
103 |
104 | return uri;
105 | }
106 |
--------------------------------------------------------------------------------
/www/vendors/reqwest/reqwest.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Reqwest! A general purpose XHR connection manager
3 | * license MIT (c) Dustin Diaz 2014
4 | * https://github.com/ded/reqwest
5 | */
6 |
7 | !function (name, context, definition) {
8 | if (typeof module != 'undefined' && module.exports) module.exports = definition()
9 | else if (typeof define == 'function' && define.amd) define(definition)
10 | else context[name] = definition()
11 | }('reqwest', this, function () {
12 |
13 | var win = window
14 | , doc = document
15 | , httpsRe = /^http/
16 | , twoHundo = /^(20\d|1223)$/
17 | , byTag = 'getElementsByTagName'
18 | , readyState = 'readyState'
19 | , contentType = 'Content-Type'
20 | , requestedWith = 'X-Requested-With'
21 | , head = doc[byTag]('head')[0]
22 | , uniqid = 0
23 | , callbackPrefix = 'reqwest_' + (+new Date())
24 | , lastValue // data stored by the most recent JSONP callback
25 | , xmlHttpRequest = 'XMLHttpRequest'
26 | , xDomainRequest = 'XDomainRequest'
27 | , noop = function () {}
28 |
29 | , isArray = typeof Array.isArray == 'function'
30 | ? Array.isArray
31 | : function (a) {
32 | return a instanceof Array
33 | }
34 |
35 | , defaultHeaders = {
36 | 'contentType': 'application/x-www-form-urlencoded'
37 | , 'requestedWith': xmlHttpRequest
38 | , 'accept': {
39 | '*': 'text/javascript, text/html, application/xml, text/xml, */*'
40 | , 'xml': 'application/xml, text/xml'
41 | , 'html': 'text/html'
42 | , 'text': 'text/plain'
43 | , 'json': 'application/json, text/javascript'
44 | , 'js': 'application/javascript, text/javascript'
45 | }
46 | }
47 |
48 | , xhr = function(o) {
49 | // is it x-domain
50 | if (o['crossOrigin'] === true) {
51 | var xhr = win[xmlHttpRequest] ? new XMLHttpRequest() : null
52 | if (xhr && 'withCredentials' in xhr) {
53 | return xhr
54 | } else if (win[xDomainRequest]) {
55 | return new XDomainRequest()
56 | } else {
57 | throw new Error('Browser does not support cross-origin requests')
58 | }
59 | } else if (win[xmlHttpRequest]) {
60 | return new XMLHttpRequest()
61 | } else {
62 | return new ActiveXObject('Microsoft.XMLHTTP')
63 | }
64 | }
65 | , globalSetupOptions = {
66 | dataFilter: function (data) {
67 | return data
68 | }
69 | }
70 |
71 | function succeed(request) {
72 | return httpsRe.test(window.location.protocol) ? twoHundo.test(request.status) : !!request.response;
73 | }
74 |
75 | function handleReadyState(r, success, error) {
76 | return function () {
77 | // use _aborted to mitigate against IE err c00c023f
78 | // (can't read props on aborted request objects)
79 | if (r._aborted) return error(r.request)
80 | if (r.request && r.request[readyState] == 4) {
81 | r.request.onreadystatechange = noop
82 | if (succeed(r.request)) success(r.request)
83 | else
84 | error(r.request)
85 | }
86 | }
87 | }
88 |
89 | function setHeaders(http, o) {
90 | var headers = o['headers'] || {}
91 | , h
92 |
93 | headers['Accept'] = headers['Accept']
94 | || defaultHeaders['accept'][o['type']]
95 | || defaultHeaders['accept']['*']
96 |
97 | var isAFormData = typeof FormData === "function" && (o['data'] instanceof FormData);
98 | // breaks cross-origin requests with legacy browsers
99 | if (!o['crossOrigin'] && !headers[requestedWith]) headers[requestedWith] = defaultHeaders['requestedWith']
100 | if (!headers[contentType] && !isAFormData) headers[contentType] = o['contentType'] || defaultHeaders['contentType']
101 | for (h in headers)
102 | headers.hasOwnProperty(h) && 'setRequestHeader' in http && http.setRequestHeader(h, headers[h])
103 | }
104 |
105 | function setCredentials(http, o) {
106 | if (typeof o['withCredentials'] !== 'undefined' && typeof http.withCredentials !== 'undefined') {
107 | http.withCredentials = !!o['withCredentials']
108 | }
109 | }
110 |
111 | function generalCallback(data) {
112 | lastValue = data
113 | }
114 |
115 | function urlappend (url, s) {
116 | return url + (/\?/.test(url) ? '&' : '?') + s
117 | }
118 |
119 | function handleJsonp(o, fn, err, url) {
120 | var reqId = uniqid++
121 | , cbkey = o['jsonpCallback'] || 'callback' // the 'callback' key
122 | , cbval = o['jsonpCallbackName'] || reqwest.getcallbackPrefix(reqId)
123 | , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)')
124 | , match = url.match(cbreg)
125 | , script = doc.createElement('script')
126 | , loaded = 0
127 | , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1
128 |
129 | if (match) {
130 | if (match[3] === '?') {
131 | url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name
132 | } else {
133 | cbval = match[3] // provided callback func name
134 | }
135 | } else {
136 | url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em
137 | }
138 |
139 | win[cbval] = generalCallback
140 |
141 | script.type = 'text/javascript'
142 | script.src = url
143 | script.async = true
144 | if (typeof script.onreadystatechange !== 'undefined' && !isIE10) {
145 | // need this for IE due to out-of-order onreadystatechange(), binding script
146 | // execution to an event listener gives us control over when the script
147 | // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
148 | script.htmlFor = script.id = '_reqwest_' + reqId
149 | }
150 |
151 | script.onload = script.onreadystatechange = function () {
152 | if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {
153 | return false
154 | }
155 | script.onload = script.onreadystatechange = null
156 | script.onclick && script.onclick()
157 | // Call the user callback with the last value stored and clean up values and scripts.
158 | fn(lastValue)
159 | lastValue = undefined
160 | head.removeChild(script)
161 | loaded = 1
162 | }
163 |
164 | // Add the script to the DOM head
165 | head.appendChild(script)
166 |
167 | // Enable JSONP timeout
168 | return {
169 | abort: function () {
170 | script.onload = script.onreadystatechange = null
171 | err({}, 'Request is aborted: timeout', {})
172 | lastValue = undefined
173 | head.removeChild(script)
174 | loaded = 1
175 | }
176 | }
177 | }
178 |
179 | function getRequest(fn, err) {
180 | var o = this.o
181 | , method = (o['method'] || 'GET').toUpperCase()
182 | , url = typeof o === 'string' ? o : o['url']
183 | // convert non-string objects to query-string form unless o['processData'] is false
184 | , data = (o['processData'] !== false && o['data'] && typeof o['data'] !== 'string')
185 | ? reqwest.toQueryString(o['data'])
186 | : (o['data'] || null)
187 | , http
188 | , sendWait = false
189 |
190 | // if we're working on a GET request and we have data then we should append
191 | // query string to end of URL and not post data
192 | if ((o['type'] == 'jsonp' || method == 'GET') && data) {
193 | url = urlappend(url, data)
194 | data = null
195 | }
196 |
197 | if (o['type'] == 'jsonp') return handleJsonp(o, fn, err, url)
198 |
199 | // get the xhr from the factory if passed
200 | // if the factory returns null, fall-back to ours
201 | http = (o.xhr && o.xhr(o)) || xhr(o)
202 |
203 | http.open(method, url, o['async'] === false ? false : true)
204 | setHeaders(http, o)
205 | setCredentials(http, o)
206 | if (win[xDomainRequest] && http instanceof win[xDomainRequest]) {
207 | http.onload = fn
208 | http.onerror = err
209 | // NOTE: see
210 | // http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e
211 | http.onprogress = function() {}
212 | sendWait = true
213 | } else {
214 | http.onreadystatechange = handleReadyState(this, fn, err)
215 | }
216 | o['before'] && o['before'](http)
217 | if (sendWait) {
218 | setTimeout(function () {
219 | http.send(data)
220 | }, 200)
221 | } else {
222 | http.send(data)
223 | }
224 | return http
225 | }
226 |
227 | function Reqwest(o, fn) {
228 | this.o = o
229 | this.fn = fn
230 |
231 | init.apply(this, arguments)
232 | }
233 |
234 | function setType(header) {
235 | // json, javascript, text/plain, text/html, xml
236 | if (header.match('json')) return 'json'
237 | if (header.match('javascript')) return 'js'
238 | if (header.match('text')) return 'html'
239 | if (header.match('xml')) return 'xml'
240 | }
241 |
242 | function init(o, fn) {
243 |
244 | this.url = typeof o == 'string' ? o : o['url']
245 | this.timeout = null
246 |
247 | // whether request has been fulfilled for purpose
248 | // of tracking the Promises
249 | this._fulfilled = false
250 | // success handlers
251 | this._successHandler = function(){}
252 | this._fulfillmentHandlers = []
253 | // error handlers
254 | this._errorHandlers = []
255 | // complete (both success and fail) handlers
256 | this._completeHandlers = []
257 | this._erred = false
258 | this._responseArgs = {}
259 |
260 | var self = this
261 |
262 | fn = fn || function () {}
263 |
264 | if (o['timeout']) {
265 | this.timeout = setTimeout(function () {
266 | self.abort()
267 | }, o['timeout'])
268 | }
269 |
270 | if (o['success']) {
271 | this._successHandler = function () {
272 | o['success'].apply(o, arguments)
273 | }
274 | }
275 |
276 | if (o['error']) {
277 | this._errorHandlers.push(function () {
278 | o['error'].apply(o, arguments)
279 | })
280 | }
281 |
282 | if (o['complete']) {
283 | this._completeHandlers.push(function () {
284 | o['complete'].apply(o, arguments)
285 | })
286 | }
287 |
288 | function complete (resp) {
289 | o['timeout'] && clearTimeout(self.timeout)
290 | self.timeout = null
291 | while (self._completeHandlers.length > 0) {
292 | self._completeHandlers.shift()(resp)
293 | }
294 | }
295 |
296 | function success (resp) {
297 | var type = o['type'] || setType(resp.getResponseHeader('Content-Type'))
298 | resp = (type !== 'jsonp') ? self.request : resp
299 | // use global data filter on response text
300 | var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type)
301 | , r = filteredResponse
302 | try {
303 | resp.responseText = r
304 | } catch (e) {
305 | // can't assign this in IE<=8, just ignore
306 | }
307 | if (r) {
308 | switch (type) {
309 | case 'json':
310 | try {
311 | resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')')
312 | } catch (err) {
313 | return error(resp, 'Could not parse JSON in response', err)
314 | }
315 | break
316 | case 'js':
317 | resp = eval(r)
318 | break
319 | case 'html':
320 | resp = r
321 | break
322 | case 'xml':
323 | resp = resp.responseXML
324 | && resp.responseXML.parseError // IE trololo
325 | && resp.responseXML.parseError.errorCode
326 | && resp.responseXML.parseError.reason
327 | ? null
328 | : resp.responseXML
329 | break
330 | }
331 | }
332 |
333 | self._responseArgs.resp = resp
334 | self._fulfilled = true
335 | fn(resp)
336 | self._successHandler(resp)
337 | while (self._fulfillmentHandlers.length > 0) {
338 | resp = self._fulfillmentHandlers.shift()(resp)
339 | }
340 |
341 | complete(resp)
342 | }
343 |
344 | function error(resp, msg, t) {
345 | resp = self.request
346 | self._responseArgs.resp = resp
347 | self._responseArgs.msg = msg
348 | self._responseArgs.t = t
349 | self._erred = true
350 | while (self._errorHandlers.length > 0) {
351 | self._errorHandlers.shift()(resp, msg, t)
352 | }
353 | complete(resp)
354 | }
355 |
356 | this.request = getRequest.call(this, success, error)
357 | }
358 |
359 | Reqwest.prototype = {
360 | abort: function () {
361 | this._aborted = true
362 | this.request.abort()
363 | }
364 |
365 | , retry: function () {
366 | init.call(this, this.o, this.fn)
367 | }
368 |
369 | /**
370 | * Small deviation from the Promises A CommonJs specification
371 | * http://wiki.commonjs.org/wiki/Promises/A
372 | */
373 |
374 | /**
375 | * `then` will execute upon successful requests
376 | */
377 | , then: function (success, fail) {
378 | success = success || function () {}
379 | fail = fail || function () {}
380 | if (this._fulfilled) {
381 | this._responseArgs.resp = success(this._responseArgs.resp)
382 | } else if (this._erred) {
383 | fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
384 | } else {
385 | this._fulfillmentHandlers.push(success)
386 | this._errorHandlers.push(fail)
387 | }
388 | return this
389 | }
390 |
391 | /**
392 | * `always` will execute whether the request succeeds or fails
393 | */
394 | , always: function (fn) {
395 | if (this._fulfilled || this._erred) {
396 | fn(this._responseArgs.resp)
397 | } else {
398 | this._completeHandlers.push(fn)
399 | }
400 | return this
401 | }
402 |
403 | /**
404 | * `fail` will execute when the request fails
405 | */
406 | , fail: function (fn) {
407 | if (this._erred) {
408 | fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
409 | } else {
410 | this._errorHandlers.push(fn)
411 | }
412 | return this
413 | }
414 | , catch: function (fn) {
415 | return this.fail(fn)
416 | }
417 | }
418 |
419 | function reqwest(o, fn) {
420 | return new Reqwest(o, fn)
421 | }
422 |
423 | // normalize newline variants according to spec -> CRLF
424 | function normalize(s) {
425 | return s ? s.replace(/\r?\n/g, '\r\n') : ''
426 | }
427 |
428 | function serial(el, cb) {
429 | var n = el.name
430 | , t = el.tagName.toLowerCase()
431 | , optCb = function (o) {
432 | // IE gives value="" even where there is no value attribute
433 | // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273
434 | if (o && !o['disabled'])
435 | cb(n, normalize(o['attributes']['value'] && o['attributes']['value']['specified'] ? o['value'] : o['text']))
436 | }
437 | , ch, ra, val, i
438 |
439 | // don't serialize elements that are disabled or without a name
440 | if (el.disabled || !n) return
441 |
442 | switch (t) {
443 | case 'input':
444 | if (!/reset|button|image|file/i.test(el.type)) {
445 | ch = /checkbox/i.test(el.type)
446 | ra = /radio/i.test(el.type)
447 | val = el.value
448 | // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here
449 | ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val))
450 | }
451 | break
452 | case 'textarea':
453 | cb(n, normalize(el.value))
454 | break
455 | case 'select':
456 | if (el.type.toLowerCase() === 'select-one') {
457 | optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null)
458 | } else {
459 | for (i = 0; el.length && i < el.length; i++) {
460 | el.options[i].selected && optCb(el.options[i])
461 | }
462 | }
463 | break
464 | }
465 | }
466 |
467 | // collect up all form elements found from the passed argument elements all
468 | // the way down to child elements; pass a '