├── .editorconfig
├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .jshintignore
├── .jshintrc
├── .npmignore
├── LICENSE
├── README.md
├── client
├── css
│ └── styles.css
├── index.html
├── js
│ └── pubsub-client.js
├── modules
│ ├── app
│ │ └── app.js
│ ├── player
│ │ ├── player.directive.js
│ │ └── player.html
│ ├── playlist
│ │ ├── playlist.directive.js
│ │ ├── playlist.html
│ │ └── playlist.services.js
│ ├── progress
│ │ ├── progress.directive.js
│ │ └── progress.html
│ ├── pubsub
│ │ └── pubsub.services.js
│ └── song
│ │ ├── new-song.html
│ │ ├── song.directive.js
│ │ ├── song.html
│ │ └── song.services.js
└── test
│ ├── index.html
│ └── test.controller.js
├── common
└── models
│ ├── song.js
│ └── song.json
├── gulpfile.js
├── package.json
├── pubsub-client.js
├── pubsub-mqtt.js
├── pubsub-primus.js
├── server
├── boot
│ ├── authentication.js
│ ├── changes.js
│ ├── primus.js
│ └── pubsub-client.js
├── component-config.json
├── config.json
├── datasources.json
├── middleware.json
├── model-config.json
└── server.js
└── test
└── ci.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # http://editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | indent_style = space
9 | indent_size = 2
10 | end_of_line = lf
11 | charset = utf-8
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | ### Bug or feature request
10 |
11 |
14 |
15 | - [ ] Bug
16 | - [ ] Feature request
17 |
18 | ### Description of feature (or steps to reproduce if bug)
19 |
20 |
21 |
22 | ### Link to sample repo to reproduce issue (if bug)
23 |
24 |
25 |
26 | ### Expected result
27 |
28 |
29 |
30 | ### Actual result (if bug)
31 |
32 |
33 |
34 | ### Additional information (Node.js version, LoopBack version, etc)
35 |
36 |
37 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Description
2 |
3 |
4 | #### Related issues
5 |
6 |
12 |
13 | - None
14 |
15 | ### Checklist
16 |
17 |
21 |
22 | - [ ] New tests added or existing tests modified to cover all changes
23 | - [ ] Code conforms with the [style
24 | guide](http://loopback.io/doc/en/contrib/style-guide.html)
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | client/js/lb-services.js
3 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | /client/
2 | /node_modules/
3 | pubsub-client.js
4 | pubsub-mqtt.js
5 | pubsub-primus.js
6 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "bitwise": true,
5 | "camelcase": true,
6 | "eqeqeq": true,
7 | "eqnull": true,
8 | "immed": true,
9 | "indent": 2,
10 | "latedef": "nofunc",
11 | "newcap": true,
12 | "nonew": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": false,
18 | "trailing": true,
19 | "sub": true,
20 | "maxlen": 80,
21 | "globals": {
22 | // mocha
23 | "describe": false,
24 | "it": false,
25 | "before": false,
26 | "beforeEach": false,
27 | "after": false,
28 | "afterEach": false
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .project
3 | .strong-pm
4 | *.sublime-*
5 | .DS_Store
6 | *.seed
7 | *.log
8 | *.csv
9 | *.dat
10 | *.out
11 | *.pid
12 | *.swp
13 | *.swo
14 | node_modules
15 | coverage
16 | *.tgz
17 | *.xml
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) IBM Corp. 2015,2016. All Rights Reserved.
2 | Node module: loopback-example-pubsub
3 | This project is licensed under the MIT License, full text below.
4 |
5 | --------
6 |
7 | MIT license
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy
10 | of this software and associated documentation files (the "Software"), to deal
11 | in the Software without restriction, including without limitation the rights
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | copies of the Software, and to permit persons to whom the Software is
14 | furnished to do so, subject to the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be included in
17 | all copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # loopback-example-pubsub
2 |
3 | Example app for LoopBack Pub-sub.
4 |
5 | See also:
6 | - [Pub-sub documentation](http://docs.strongloop.com/display/MSG/Pub-sub)
7 | - [StrongLoop Pub-sub repository](https://github.com/strongloop/strong-pubsub)
8 |
9 | ## Install
10 |
11 | The example requires `node`, `npm` and [`mosquitto`](http://mosquitto.org/download/).
12 |
13 | ```
14 | $ git clone https://github.com/strongloop/loopback-example-pubsub.git
15 | $ cd loopback-example-pubsub
16 | $ npm install
17 | $ gulp # manually exit via ctrl+c here, it is a known issue ATM
18 | $ mosquitto & # run the mosquitto server
19 | $ node server/server.js
20 | ```
21 |
22 | The project is generated by [LoopBack](http://loopback.io).
23 |
24 | ---
25 |
26 | [More LoopBack examples](https://loopback.io/doc/en/lb3/Tutorials-and-examples.html)
27 |
--------------------------------------------------------------------------------
/client/css/styles.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | padding: 0;
6 | margin: 0;
7 | height: 100%;
8 | width: 100%;
9 | background-image: -moz-radial-gradient(circle, #eeeeee, #bdc3c7 80%);
10 | background-image: -webkit-radial-gradient(circle, #eeeeee, #bdc3c7 80%);
11 | background-image: radial-gradient(circle, #eeeeee, #bdc3c7 80%);
12 | font-family: 'Open Sans',sans-serif;
13 | }
14 |
15 | /***
16 | * ps-player
17 | ***/
18 | ps-player {
19 | width: 320px;
20 | height: 320px;
21 | display: inline-block;
22 | position: relative;
23 | box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.3);
24 | }
25 | ps-player a, ps-player a:hover, ps-player a:link, ps-player a:active {
26 | color: #fff;
27 | text-decoration: none;
28 | }
29 | ps-player p, ps-player h1 {
30 | padding: 0;
31 | margin: 0;
32 | }
33 | ps-player h1 {
34 | font-size: 1em;
35 | font-weight: 100;
36 | }
37 | ps-player p {
38 | font-size: 0.75em;
39 | }
40 | ps-player .album-art {
41 | width: 100%;
42 | height: 100%;
43 | background-size: cover;
44 | background-position: center;
45 | }
46 | ps-player .wave {
47 | position: absolute;
48 | top: 0;
49 | width: 100%;
50 | height: 100%;
51 | background-size: cover;
52 | background-position: center;
53 | background-repeat: no-repeat;
54 | opacity: 0.0;
55 | }
56 | ps-player .details {
57 | width: 100%;
58 | height: 64px;
59 | background-color: rgba(0, 0, 0, 0.5);
60 | color: #fff;
61 | }
62 | ps-player .track-info {
63 | float: left;
64 | padding-left: 4px;
65 | }
66 | ps-player .play {
67 | float: right;
68 | width: 64px;
69 | height: 64px;
70 | line-height: 64px;
71 | text-align: center;
72 | font-size: 24px;
73 | cursor: pointer;
74 | background: #2CDE50;
75 | transition: all 0.3s ease 0s;
76 | }
77 | ps-player .play:hover {
78 | background: #6be784;
79 | }
80 | ps-player .play:active {
81 | background: #27c748;
82 | box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.5) inset;
83 | }
84 |
85 | ps-player .playhead {
86 | background-color: rgba(0, 0, 0, 0.3);
87 | height: 5px;
88 | position: absolute;
89 | top: 5px;
90 | left: 5px;
91 | padding: 5px;
92 | line-height: 5px;
93 | }
94 |
95 | ps-progress .currentTime {
96 | background-color: rgba(0, 0, 0, 0.9);
97 | height: 18px;
98 | position: absolute;
99 | top: 5px;
100 | right: 5px;
101 | color: #fff;
102 | text-align: center;
103 | min-width: 50px;
104 | padding: 5px;
105 | line-height: 18px;
106 | }
107 |
108 | ps-progress .playing {
109 | background: #ff0000;
110 | }
111 |
112 |
113 | .add-input {
114 | display: inline-block;
115 | height: 32px;
116 | width: 320px;
117 | float: left;
118 | padding: 0;
119 | margin: 0;
120 | border: none;
121 | }
122 |
123 | .add-btn {
124 | display: inline-block;
125 | float: left;
126 | width: 32px;
127 | height: 32px;
128 | padding: 0;
129 | margin: 0;
130 | text-align: center;
131 | font-size: 24px;
132 | cursor: pointer;
133 | background: #2CDE50;
134 | transition: all 0.3s ease 0s;
135 | color: #fff;
136 | border: none;
137 | }
138 | .add-btn:hover {
139 | background: #6be784;
140 | }
141 | .add-btn:active {
142 | background: #27c748;
143 | box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.5) inset;
144 | }
145 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | LoopBack Example PubSub
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/client/js/pubsub-client.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | var Client = require('strong-pubsub');
7 | var Adapter = require('strong-pubsub-mqtt');
8 | var duplex = require('duplex');
9 |
10 | Primus.Stream = require('stream');
11 |
12 | module.exports = function(PORT) {
13 |
14 | var client = new Client({port: PORT, host: 'localhost'}, Adapter, {
15 | createConnection: function(port, host) {
16 | var connection = duplex();
17 |
18 | var primus = Primus.connect('http://localhost:3000', {
19 | transformer: 'engine.io',
20 | parser: 'binary'
21 | });
22 |
23 | connection.on('_data', function(chunk) {
24 | // someone called `connection.write(buf)`
25 | primus.write(chunk);
26 | });
27 |
28 | primus.on('data', function(chunk) {
29 | if (chunk && !Buffer.isBuffer(chunk)) {
30 | // chunk is an arrayBuffer
31 | connection._data(toBuffer(chunk));
32 | }
33 | });
34 |
35 | primus.on('open', function() {
36 | connection.emit('connect');
37 | });
38 |
39 | connection.on('_end', function() {
40 | primus.end();
41 | this._end();
42 | });
43 |
44 | return connection;
45 | }
46 | });
47 |
48 | return client;
49 | }
50 |
51 |
52 | function toBuffer(ab) {
53 | var buffer = new Buffer(ab.byteLength);
54 | var view = new Uint8Array(ab);
55 | for(var i = 0; i < buffer.length; ++i) {
56 | buffer[i] = view[i];
57 | }
58 | return buffer;
59 | }
60 |
--------------------------------------------------------------------------------
/client/modules/app/app.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | var app = angular.module('ps', ['lbServices']);
7 |
8 | app.controller('AppController', function($scope, Playlist) {
9 | var playlistId;
10 |
11 | if(window.location.hash) {
12 | playlistId = window.location.hash.replace('#', '');
13 | }
14 |
15 | var playlist = $scope.playlist = new Playlist(playlistId);
16 |
17 | window.location.hash = playlist.id;
18 |
19 | // update the playlist
20 | playlist.getSongs();
21 | });
22 |
--------------------------------------------------------------------------------
/client/modules/player/player.directive.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | app.directive('psPlayer', function ($http, $interval, Song) {
7 | function link(scope) {
8 | var clientid = 'b23455855ab96a4556cbd0a98397ae8c';
9 | var song = scope.song;
10 |
11 | if(!song) return;
12 |
13 | $http({
14 | method: 'GET',
15 | url: 'https://api.soundcloud.com/resolve.json?client_id='+ clientid + '&url=' + song.scURL
16 | })
17 | .success(function (trackData) {
18 | $http({
19 | method: 'GET',
20 | url: 'http://api.soundcloud.com/tracks/'+trackData.id+'.json?client_id='+clientid
21 | })
22 | .success(function (data) {
23 | scope.band = data.user.username;
24 | scope.bandUrl = data.user.permalink_url;
25 | scope.title = data.title;
26 | scope.trackUrl = data.permalink_url;
27 | if(data.artwork_url) {
28 | scope.albumArt = data.artwork_url.replace("large", "t500x500");
29 | }
30 | scope.wave = data.waveform_url;
31 | scope.stream = data.stream_url + '?client_id=' + clientid;
32 |
33 | var audio = scope.audio = new Audio();
34 | audio.onended = function() {
35 | scope.onEnded();
36 | }
37 | audio.ontimeupdate = function() {
38 | scope.onProgress();
39 | scope.progress();
40 | }
41 |
42 | onPlayChanged();
43 | });
44 | });
45 |
46 | song.playing = song.playing || false;
47 |
48 | // watch song.playing
49 | scope.$watch('song.playing', function(newPlaying, oldPlaying) {
50 | if(newPlaying !== oldPlaying) {
51 | onPlayChanged();
52 | }
53 | });
54 |
55 | scope.togglePlay = function() {
56 | song.playing = !song.playing;
57 | console.log('updateAttributes', song.id);
58 |
59 | Song.prototype$updateAttributes({ id: song.id }, {
60 | playing: song.playing
61 | });
62 | }
63 |
64 | function onPlayChanged() {
65 | if (song.playing) {
66 | if (!scope.audio.src) {
67 | scope.audio.src = scope.stream;
68 | }
69 | scope.audio.play();
70 |
71 | if (scope.onPlay) {
72 | scope.onPlay();
73 | }
74 | } else {
75 | scope.audio.pause();
76 |
77 | if (scope.onPause) {
78 | scope.onPause();
79 | }
80 | }
81 | }
82 |
83 | scope.formatTime = function(time) {
84 | var minutes = Math.floor(time / 60);
85 | var seconds = Math.round(time - minutes * 60);
86 |
87 | if(minutes < 10) {
88 | minutes = '0' + minutes;
89 | }
90 | if(seconds < 10) {
91 | seconds = '0' + seconds;
92 | }
93 | return minutes + ':' + seconds;
94 | }
95 |
96 | scope.currentTime = 0;
97 |
98 | scope.progress = function() {
99 | scope.currentTime = scope.audio.currentTime;
100 | scope.duration = scope.audio.duration;
101 | scope.$apply();
102 | }
103 | }
104 | return {
105 | restrict: 'E',
106 | scope: {
107 | song: '=',
108 | onPlay: '&',
109 | onPause: '&',
110 | onProgress: '&',
111 | onEnded: '&'
112 | },
113 | templateUrl: '/modules/player/player.html',
114 | link: link
115 | };
116 | });
117 |
--------------------------------------------------------------------------------
/client/modules/player/player.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{formatTime(currentTime)}}
6 |
7 |
14 |
--------------------------------------------------------------------------------
/client/modules/playlist/playlist.directive.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | app.directive('psPlaylist', function ($http, $interval) {
7 | function link(scope) {
8 | }
9 | return {
10 | restrict: 'E',
11 | scope: {
12 | playlist: '='
13 | },
14 | templateUrl: '/modules/playlist/playlist.html',
15 | link: link
16 | };
17 | });
18 |
--------------------------------------------------------------------------------
/client/modules/playlist/playlist.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/client/modules/playlist/playlist.services.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | app.factory('Playlist', function($http, Song, pubsubClient) {
7 | function Playlist(id) {
8 | var playlist = this;
9 | this.id = id || generateId();
10 | this.songs = [];
11 |
12 | this.subscribe();
13 |
14 | pubsubClient.on('message', function(topic, msg) {
15 | var id;
16 | if(topic.indexOf(playlist.getTopic()) === 0) {
17 | id = parseInt(msg.toString());
18 | playlist.onSongChanged(id);
19 | }
20 | });
21 | }
22 |
23 | Playlist.prototype.onSongChanged = function(id) {
24 | var songExists = false;
25 | var playlist = this;
26 |
27 | this.songs.forEach(function(song) {
28 | if(song.id === id) {
29 | songExists = true;
30 | playlist.updateSong(song);
31 | }
32 | });
33 |
34 | if(!songExists) {
35 | playlist.addSongById(id);
36 | }
37 | }
38 |
39 | Playlist.prototype.updateSong = function(song) {
40 | console.log('updateSong');
41 | Song.findById({
42 | id: song.id
43 | }, function(latest) {
44 | Object.keys(latest).forEach(function(key) {
45 | // update in place (angular will update the ui)
46 | song[key] = latest[key];
47 | });
48 | });
49 | }
50 |
51 | Playlist.prototype.addSongById = function(id) {
52 | var playlist = this;
53 |
54 | Song.findById({
55 | id: id
56 | }, function(song) {
57 | playlist.songs.push(song);
58 | });
59 | }
60 |
61 | Playlist.prototype.addSong = function(url) {
62 | var id = this.id;
63 | var song = {
64 | playlist: id,
65 | scURL: url
66 | };
67 | var playlist = this;
68 |
69 |
70 | return Song.create(song, function(createdSong) {
71 | playlist.songs.push(createdSong);
72 | });
73 | }
74 |
75 | Playlist.prototype.getSongs = function() {
76 | this.songs = Song.find({
77 | filter: {where: {playlist: this.id}}
78 | });
79 | }
80 |
81 | Playlist.prototype.subscribe = function() {
82 | pubsubClient.subscribe(this.getTopic());
83 | }
84 |
85 | Playlist.prototype.getTopic = function() {
86 | return '/playlists/' + this.id;
87 | }
88 |
89 | function generateId() {
90 | var n = Math.random();
91 | return n.toString().split('.')[1].toString();
92 | }
93 |
94 | return Playlist;
95 | });
96 |
--------------------------------------------------------------------------------
/client/modules/progress/progress.directive.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | app.directive('psProgress', function ($http, $interval) {
7 | function link(scope) {
8 | console.log('song', scope.song);
9 | scope.formatTime = function(time) {
10 | var minutes = Math.floor(time / 60);
11 | var seconds = Math.round(time - minutes * 60);
12 |
13 | if(minutes < 10) {
14 | minutes = '0' + minutes;
15 | }
16 | if(seconds < 10) {
17 | seconds = '0' + seconds;
18 | }
19 | return minutes + ':' + seconds;
20 | }
21 | }
22 | return {
23 | restrict: 'E',
24 | scope: {
25 | song: '='
26 | },
27 | templateUrl: '/modules/progress/progress.html',
28 | link: link
29 | };
30 | });
31 |
--------------------------------------------------------------------------------
/client/modules/progress/progress.html:
--------------------------------------------------------------------------------
1 |
2 | {{formatTime(song.currentTime)}}
3 |
--------------------------------------------------------------------------------
/client/modules/pubsub/pubsub.services.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | app.factory('pubsubClient', function() {
7 | return require('pubsub-client')(3000);
8 | });
9 |
--------------------------------------------------------------------------------
/client/modules/song/new-song.html:
--------------------------------------------------------------------------------
1 | Enter a URL for a SoundCloud track:
2 |
3 | +
4 |
--------------------------------------------------------------------------------
/client/modules/song/song.directive.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | app.directive('psSong', function () {
7 | function link(scope) {
8 | }
9 | return {
10 | restrict: 'E',
11 | scope: {
12 | song: '='
13 | },
14 | templateUrl: '/modules/song/song.html',
15 | link: link
16 | };
17 | });
18 |
19 | app.directive('psNewSong', function () {
20 | function link(scope) {
21 | scope.add = function() {
22 | var playlist = scope.playlist;
23 |
24 | playlist.addSong(scope.url);
25 |
26 | delete scope.url;
27 | }
28 | }
29 | return {
30 | restrict: 'E',
31 | scope: {
32 | playlist: '='
33 | },
34 | templateUrl: '/modules/song/new-song.html',
35 | link: link
36 | };
37 | });
38 |
--------------------------------------------------------------------------------
/client/modules/song/song.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/client/modules/song/song.services.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | app.factory('Song', function() {
7 | function Song(url) {
8 | this.url = url;
9 | }
10 |
11 | Song.prototype.getMeta = function() {
12 | return $http.get(this.url);
13 | }
14 |
15 | return Song;
16 | });
17 |
18 |
19 |
--------------------------------------------------------------------------------
/client/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | LoopBack Example PubSub
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Player
19 |
20 |
21 |
22 |
23 |
Song
24 |
25 |
26 |
27 |
28 |
Playlist
29 |
30 |
31 |
32 |
33 |
34 |
New Song
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/client/test/test.controller.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | app.controller('TestController', ['$scope', 'Playlist', function($scope, Playlist) {
7 | var testURL = 'https://soundcloud.com/cosmoknot/to-u';
8 |
9 | $scope.testSongs = [$scope.testSong];
10 |
11 | var playlist = $scope.playlist = new Playlist('1234567');
12 |
13 | playlist.addSong(testURL);
14 |
15 | }]);
16 |
--------------------------------------------------------------------------------
/common/models/song.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | module.exports = function(Song) {
7 |
8 | };
9 |
--------------------------------------------------------------------------------
/common/models/song.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Song",
3 | "base": "PersistedModel",
4 | "idInjection": true,
5 | "properties": {},
6 | "validations": [],
7 | "relations": {},
8 | "acls": [],
9 | "methods": []
10 | }
11 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015,2016. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | var gulp = require('gulp');
7 | var rename = require('gulp-rename');
8 | var loopbackAngular = require('gulp-loopback-sdk-angular');
9 |
10 | gulp.task('default', function() {
11 | return gulp.src('./server/server.js')
12 | .pipe(loopbackAngular())
13 | .pipe(rename('lb-services.js'))
14 | .pipe(gulp.dest('./client/js'));
15 | });
16 |
17 | gulp.doneCallback = function(err) {
18 | process.exit(err ? 1 : 0);
19 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "loopback-example-pubsub",
3 | "version": "1.0.0",
4 | "main": "server/server.js",
5 | "scripts": {
6 | "lint": "jshint .",
7 | "start": "node .",
8 | "test": "mocha",
9 | "posttest": "npm run lint"
10 | },
11 | "dependencies": {
12 | "binary-pack": "0.0.3",
13 | "compression": "^1.0.3",
14 | "debug": "^2.1.3",
15 | "duplex": "^1.0.0",
16 | "engine.io": "^1.5.1",
17 | "strong-error-handler": "^1.1.0",
18 | "loopback": "^3.0.0",
19 | "loopback-boot": "^2.4.0",
20 | "primus": "2.4.12",
21 | "serve-favicon": "^2.0.1",
22 | "strong-pubsub": "^0.1.0",
23 | "strong-pubsub-connection-mqtt": "^0.1.0",
24 | "strong-pubsub-mqtt": "^0.1.0",
25 | "strong-pubsub-primus": "^0.1.0",
26 | "strong-pubsub-proxy": "^0.1.0",
27 | "loopback-component-explorer": "^2.1.0"
28 | },
29 | "devDependencies": {
30 | "browserify": "^11.0.1",
31 | "gulp": "^3.8.11",
32 | "gulp-loopback-sdk-angular": "^0.1.3",
33 | "gulp-rename": "^1.2.2",
34 | "jshint": "^2.5.6",
35 | "mocha": "^2.3.4"
36 | },
37 | "repository": {
38 | "type": "git",
39 | "url": "https://github.com/strongloop/loopback-example-pubsub"
40 | },
41 | "description": "strong-pubsub-angular-loopback",
42 | "license": "MIT"
43 | }
44 |
--------------------------------------------------------------------------------
/pubsub-client.js:
--------------------------------------------------------------------------------
1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.PubSubClient = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && this._events[type].length > m) {
371 | this._events[type].warned = true;
372 | console.error('(node) warning: possible EventEmitter memory ' +
373 | 'leak detected. %d listeners added. ' +
374 | 'Use emitter.setMaxListeners() to increase limit.',
375 | this._events[type].length);
376 | if (typeof console.trace === 'function') {
377 | // not supported in IE 10
378 | console.trace();
379 | }
380 | }
381 | }
382 |
383 | return this;
384 | };
385 |
386 | EventEmitter.prototype.on = EventEmitter.prototype.addListener;
387 |
388 | EventEmitter.prototype.once = function(type, listener) {
389 | if (!isFunction(listener))
390 | throw TypeError('listener must be a function');
391 |
392 | var fired = false;
393 |
394 | function g() {
395 | this.removeListener(type, g);
396 |
397 | if (!fired) {
398 | fired = true;
399 | listener.apply(this, arguments);
400 | }
401 | }
402 |
403 | g.listener = listener;
404 | this.on(type, g);
405 |
406 | return this;
407 | };
408 |
409 | // emits a 'removeListener' event iff the listener was removed
410 | EventEmitter.prototype.removeListener = function(type, listener) {
411 | var list, position, length, i;
412 |
413 | if (!isFunction(listener))
414 | throw TypeError('listener must be a function');
415 |
416 | if (!this._events || !this._events[type])
417 | return this;
418 |
419 | list = this._events[type];
420 | length = list.length;
421 | position = -1;
422 |
423 | if (list === listener ||
424 | (isFunction(list.listener) && list.listener === listener)) {
425 | delete this._events[type];
426 | if (this._events.removeListener)
427 | this.emit('removeListener', type, listener);
428 |
429 | } else if (isObject(list)) {
430 | for (i = length; i-- > 0;) {
431 | if (list[i] === listener ||
432 | (list[i].listener && list[i].listener === listener)) {
433 | position = i;
434 | break;
435 | }
436 | }
437 |
438 | if (position < 0)
439 | return this;
440 |
441 | if (list.length === 1) {
442 | list.length = 0;
443 | delete this._events[type];
444 | } else {
445 | list.splice(position, 1);
446 | }
447 |
448 | if (this._events.removeListener)
449 | this.emit('removeListener', type, listener);
450 | }
451 |
452 | return this;
453 | };
454 |
455 | EventEmitter.prototype.removeAllListeners = function(type) {
456 | var key, listeners;
457 |
458 | if (!this._events)
459 | return this;
460 |
461 | // not listening for removeListener, no need to emit
462 | if (!this._events.removeListener) {
463 | if (arguments.length === 0)
464 | this._events = {};
465 | else if (this._events[type])
466 | delete this._events[type];
467 | return this;
468 | }
469 |
470 | // emit removeListener for all listeners on all events
471 | if (arguments.length === 0) {
472 | for (key in this._events) {
473 | if (key === 'removeListener') continue;
474 | this.removeAllListeners(key);
475 | }
476 | this.removeAllListeners('removeListener');
477 | this._events = {};
478 | return this;
479 | }
480 |
481 | listeners = this._events[type];
482 |
483 | if (isFunction(listeners)) {
484 | this.removeListener(type, listeners);
485 | } else {
486 | // LIFO order
487 | while (listeners.length)
488 | this.removeListener(type, listeners[listeners.length - 1]);
489 | }
490 | delete this._events[type];
491 |
492 | return this;
493 | };
494 |
495 | EventEmitter.prototype.listeners = function(type) {
496 | var ret;
497 | if (!this._events || !this._events[type])
498 | ret = [];
499 | else if (isFunction(this._events[type]))
500 | ret = [this._events[type]];
501 | else
502 | ret = this._events[type].slice();
503 | return ret;
504 | };
505 |
506 | EventEmitter.listenerCount = function(emitter, type) {
507 | var ret;
508 | if (!emitter._events || !emitter._events[type])
509 | ret = 0;
510 | else if (isFunction(emitter._events[type]))
511 | ret = 1;
512 | else
513 | ret = emitter._events[type].length;
514 | return ret;
515 | };
516 |
517 | function isFunction(arg) {
518 | return typeof arg === 'function';
519 | }
520 |
521 | function isNumber(arg) {
522 | return typeof arg === 'number';
523 | }
524 |
525 | function isObject(arg) {
526 | return typeof arg === 'object' && arg !== null;
527 | }
528 |
529 | function isUndefined(arg) {
530 | return arg === void 0;
531 | }
532 |
533 | },{}],3:[function(require,module,exports){
534 | if (typeof Object.create === 'function') {
535 | // implementation from standard node.js 'util' module
536 | module.exports = function inherits(ctor, superCtor) {
537 | ctor.super_ = superCtor
538 | ctor.prototype = Object.create(superCtor.prototype, {
539 | constructor: {
540 | value: ctor,
541 | enumerable: false,
542 | writable: true,
543 | configurable: true
544 | }
545 | });
546 | };
547 | } else {
548 | // old school shim for old browsers
549 | module.exports = function inherits(ctor, superCtor) {
550 | ctor.super_ = superCtor
551 | var TempCtor = function () {}
552 | TempCtor.prototype = superCtor.prototype
553 | ctor.prototype = new TempCtor()
554 | ctor.prototype.constructor = ctor
555 | }
556 | }
557 |
558 | },{}],4:[function(require,module,exports){
559 | // shim for using process in browser
560 |
561 | var process = module.exports = {};
562 | var queue = [];
563 | var draining = false;
564 |
565 | function drainQueue() {
566 | if (draining) {
567 | return;
568 | }
569 | draining = true;
570 | var currentQueue;
571 | var len = queue.length;
572 | while(len) {
573 | currentQueue = queue;
574 | queue = [];
575 | var i = -1;
576 | while (++i < len) {
577 | currentQueue[i]();
578 | }
579 | len = queue.length;
580 | }
581 | draining = false;
582 | }
583 | process.nextTick = function (fun) {
584 | queue.push(fun);
585 | if (!draining) {
586 | setTimeout(drainQueue, 0);
587 | }
588 | };
589 |
590 | process.title = 'browser';
591 | process.browser = true;
592 | process.env = {};
593 | process.argv = [];
594 | process.version = ''; // empty string to avoid regexp issues
595 | process.versions = {};
596 |
597 | function noop() {}
598 |
599 | process.on = noop;
600 | process.addListener = noop;
601 | process.once = noop;
602 | process.off = noop;
603 | process.removeListener = noop;
604 | process.removeAllListeners = noop;
605 | process.emit = noop;
606 |
607 | process.binding = function (name) {
608 | throw new Error('process.binding is not supported');
609 | };
610 |
611 | // TODO(shtylman)
612 | process.cwd = function () { return '/' };
613 | process.chdir = function (dir) {
614 | throw new Error('process.chdir is not supported');
615 | };
616 | process.umask = function() { return 0; };
617 |
618 | },{}],5:[function(require,module,exports){
619 | module.exports = function isBuffer(arg) {
620 | return arg && typeof arg === 'object'
621 | && typeof arg.copy === 'function'
622 | && typeof arg.fill === 'function'
623 | && typeof arg.readUInt8 === 'function';
624 | }
625 | },{}],6:[function(require,module,exports){
626 | (function (process,global){
627 | // Copyright Joyent, Inc. and other Node contributors.
628 | //
629 | // Permission is hereby granted, free of charge, to any person obtaining a
630 | // copy of this software and associated documentation files (the
631 | // "Software"), to deal in the Software without restriction, including
632 | // without limitation the rights to use, copy, modify, merge, publish,
633 | // distribute, sublicense, and/or sell copies of the Software, and to permit
634 | // persons to whom the Software is furnished to do so, subject to the
635 | // following conditions:
636 | //
637 | // The above copyright notice and this permission notice shall be included
638 | // in all copies or substantial portions of the Software.
639 | //
640 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
641 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
642 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
643 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
644 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
645 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
646 | // USE OR OTHER DEALINGS IN THE SOFTWARE.
647 |
648 | var formatRegExp = /%[sdj%]/g;
649 | exports.format = function(f) {
650 | if (!isString(f)) {
651 | var objects = [];
652 | for (var i = 0; i < arguments.length; i++) {
653 | objects.push(inspect(arguments[i]));
654 | }
655 | return objects.join(' ');
656 | }
657 |
658 | var i = 1;
659 | var args = arguments;
660 | var len = args.length;
661 | var str = String(f).replace(formatRegExp, function(x) {
662 | if (x === '%%') return '%';
663 | if (i >= len) return x;
664 | switch (x) {
665 | case '%s': return String(args[i++]);
666 | case '%d': return Number(args[i++]);
667 | case '%j':
668 | try {
669 | return JSON.stringify(args[i++]);
670 | } catch (_) {
671 | return '[Circular]';
672 | }
673 | default:
674 | return x;
675 | }
676 | });
677 | for (var x = args[i]; i < len; x = args[++i]) {
678 | if (isNull(x) || !isObject(x)) {
679 | str += ' ' + x;
680 | } else {
681 | str += ' ' + inspect(x);
682 | }
683 | }
684 | return str;
685 | };
686 |
687 |
688 | // Mark that a method should not be used.
689 | // Returns a modified function which warns once by default.
690 | // If --no-deprecation is set, then it is a no-op.
691 | exports.deprecate = function(fn, msg) {
692 | // Allow for deprecating things in the process of starting up.
693 | if (isUndefined(global.process)) {
694 | return function() {
695 | return exports.deprecate(fn, msg).apply(this, arguments);
696 | };
697 | }
698 |
699 | if (process.noDeprecation === true) {
700 | return fn;
701 | }
702 |
703 | var warned = false;
704 | function deprecated() {
705 | if (!warned) {
706 | if (process.throwDeprecation) {
707 | throw new Error(msg);
708 | } else if (process.traceDeprecation) {
709 | console.trace(msg);
710 | } else {
711 | console.error(msg);
712 | }
713 | warned = true;
714 | }
715 | return fn.apply(this, arguments);
716 | }
717 |
718 | return deprecated;
719 | };
720 |
721 |
722 | var debugs = {};
723 | var debugEnviron;
724 | exports.debuglog = function(set) {
725 | if (isUndefined(debugEnviron))
726 | debugEnviron = process.env.NODE_DEBUG || '';
727 | set = set.toUpperCase();
728 | if (!debugs[set]) {
729 | if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) {
730 | var pid = process.pid;
731 | debugs[set] = function() {
732 | var msg = exports.format.apply(exports, arguments);
733 | console.error('%s %d: %s', set, pid, msg);
734 | };
735 | } else {
736 | debugs[set] = function() {};
737 | }
738 | }
739 | return debugs[set];
740 | };
741 |
742 |
743 | /**
744 | * Echos the value of a value. Trys to print the value out
745 | * in the best way possible given the different types.
746 | *
747 | * @param {Object} obj The object to print out.
748 | * @param {Object} opts Optional options object that alters the output.
749 | */
750 | /* legacy: obj, showHidden, depth, colors*/
751 | function inspect(obj, opts) {
752 | // default options
753 | var ctx = {
754 | seen: [],
755 | stylize: stylizeNoColor
756 | };
757 | // legacy...
758 | if (arguments.length >= 3) ctx.depth = arguments[2];
759 | if (arguments.length >= 4) ctx.colors = arguments[3];
760 | if (isBoolean(opts)) {
761 | // legacy...
762 | ctx.showHidden = opts;
763 | } else if (opts) {
764 | // got an "options" object
765 | exports._extend(ctx, opts);
766 | }
767 | // set default options
768 | if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
769 | if (isUndefined(ctx.depth)) ctx.depth = 2;
770 | if (isUndefined(ctx.colors)) ctx.colors = false;
771 | if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
772 | if (ctx.colors) ctx.stylize = stylizeWithColor;
773 | return formatValue(ctx, obj, ctx.depth);
774 | }
775 | exports.inspect = inspect;
776 |
777 |
778 | // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
779 | inspect.colors = {
780 | 'bold' : [1, 22],
781 | 'italic' : [3, 23],
782 | 'underline' : [4, 24],
783 | 'inverse' : [7, 27],
784 | 'white' : [37, 39],
785 | 'grey' : [90, 39],
786 | 'black' : [30, 39],
787 | 'blue' : [34, 39],
788 | 'cyan' : [36, 39],
789 | 'green' : [32, 39],
790 | 'magenta' : [35, 39],
791 | 'red' : [31, 39],
792 | 'yellow' : [33, 39]
793 | };
794 |
795 | // Don't use 'blue' not visible on cmd.exe
796 | inspect.styles = {
797 | 'special': 'cyan',
798 | 'number': 'yellow',
799 | 'boolean': 'yellow',
800 | 'undefined': 'grey',
801 | 'null': 'bold',
802 | 'string': 'green',
803 | 'date': 'magenta',
804 | // "name": intentionally not styling
805 | 'regexp': 'red'
806 | };
807 |
808 |
809 | function stylizeWithColor(str, styleType) {
810 | var style = inspect.styles[styleType];
811 |
812 | if (style) {
813 | return '\u001b[' + inspect.colors[style][0] + 'm' + str +
814 | '\u001b[' + inspect.colors[style][1] + 'm';
815 | } else {
816 | return str;
817 | }
818 | }
819 |
820 |
821 | function stylizeNoColor(str, styleType) {
822 | return str;
823 | }
824 |
825 |
826 | function arrayToHash(array) {
827 | var hash = {};
828 |
829 | array.forEach(function(val, idx) {
830 | hash[val] = true;
831 | });
832 |
833 | return hash;
834 | }
835 |
836 |
837 | function formatValue(ctx, value, recurseTimes) {
838 | // Provide a hook for user-specified inspect functions.
839 | // Check that value is an object with an inspect function on it
840 | if (ctx.customInspect &&
841 | value &&
842 | isFunction(value.inspect) &&
843 | // Filter out the util module, it's inspect function is special
844 | value.inspect !== exports.inspect &&
845 | // Also filter out any prototype objects using the circular check.
846 | !(value.constructor && value.constructor.prototype === value)) {
847 | var ret = value.inspect(recurseTimes, ctx);
848 | if (!isString(ret)) {
849 | ret = formatValue(ctx, ret, recurseTimes);
850 | }
851 | return ret;
852 | }
853 |
854 | // Primitive types cannot have properties
855 | var primitive = formatPrimitive(ctx, value);
856 | if (primitive) {
857 | return primitive;
858 | }
859 |
860 | // Look up the keys of the object.
861 | var keys = Object.keys(value);
862 | var visibleKeys = arrayToHash(keys);
863 |
864 | if (ctx.showHidden) {
865 | keys = Object.getOwnPropertyNames(value);
866 | }
867 |
868 | // IE doesn't make error fields non-enumerable
869 | // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
870 | if (isError(value)
871 | && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
872 | return formatError(value);
873 | }
874 |
875 | // Some type of object without properties can be shortcutted.
876 | if (keys.length === 0) {
877 | if (isFunction(value)) {
878 | var name = value.name ? ': ' + value.name : '';
879 | return ctx.stylize('[Function' + name + ']', 'special');
880 | }
881 | if (isRegExp(value)) {
882 | return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
883 | }
884 | if (isDate(value)) {
885 | return ctx.stylize(Date.prototype.toString.call(value), 'date');
886 | }
887 | if (isError(value)) {
888 | return formatError(value);
889 | }
890 | }
891 |
892 | var base = '', array = false, braces = ['{', '}'];
893 |
894 | // Make Array say that they are Array
895 | if (isArray(value)) {
896 | array = true;
897 | braces = ['[', ']'];
898 | }
899 |
900 | // Make functions say that they are functions
901 | if (isFunction(value)) {
902 | var n = value.name ? ': ' + value.name : '';
903 | base = ' [Function' + n + ']';
904 | }
905 |
906 | // Make RegExps say that they are RegExps
907 | if (isRegExp(value)) {
908 | base = ' ' + RegExp.prototype.toString.call(value);
909 | }
910 |
911 | // Make dates with properties first say the date
912 | if (isDate(value)) {
913 | base = ' ' + Date.prototype.toUTCString.call(value);
914 | }
915 |
916 | // Make error with message first say the error
917 | if (isError(value)) {
918 | base = ' ' + formatError(value);
919 | }
920 |
921 | if (keys.length === 0 && (!array || value.length == 0)) {
922 | return braces[0] + base + braces[1];
923 | }
924 |
925 | if (recurseTimes < 0) {
926 | if (isRegExp(value)) {
927 | return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
928 | } else {
929 | return ctx.stylize('[Object]', 'special');
930 | }
931 | }
932 |
933 | ctx.seen.push(value);
934 |
935 | var output;
936 | if (array) {
937 | output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
938 | } else {
939 | output = keys.map(function(key) {
940 | return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
941 | });
942 | }
943 |
944 | ctx.seen.pop();
945 |
946 | return reduceToSingleString(output, base, braces);
947 | }
948 |
949 |
950 | function formatPrimitive(ctx, value) {
951 | if (isUndefined(value))
952 | return ctx.stylize('undefined', 'undefined');
953 | if (isString(value)) {
954 | var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
955 | .replace(/'/g, "\\'")
956 | .replace(/\\"/g, '"') + '\'';
957 | return ctx.stylize(simple, 'string');
958 | }
959 | if (isNumber(value))
960 | return ctx.stylize('' + value, 'number');
961 | if (isBoolean(value))
962 | return ctx.stylize('' + value, 'boolean');
963 | // For some reason typeof null is "object", so special case here.
964 | if (isNull(value))
965 | return ctx.stylize('null', 'null');
966 | }
967 |
968 |
969 | function formatError(value) {
970 | return '[' + Error.prototype.toString.call(value) + ']';
971 | }
972 |
973 |
974 | function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
975 | var output = [];
976 | for (var i = 0, l = value.length; i < l; ++i) {
977 | if (hasOwnProperty(value, String(i))) {
978 | output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
979 | String(i), true));
980 | } else {
981 | output.push('');
982 | }
983 | }
984 | keys.forEach(function(key) {
985 | if (!key.match(/^\d+$/)) {
986 | output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
987 | key, true));
988 | }
989 | });
990 | return output;
991 | }
992 |
993 |
994 | function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
995 | var name, str, desc;
996 | desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
997 | if (desc.get) {
998 | if (desc.set) {
999 | str = ctx.stylize('[Getter/Setter]', 'special');
1000 | } else {
1001 | str = ctx.stylize('[Getter]', 'special');
1002 | }
1003 | } else {
1004 | if (desc.set) {
1005 | str = ctx.stylize('[Setter]', 'special');
1006 | }
1007 | }
1008 | if (!hasOwnProperty(visibleKeys, key)) {
1009 | name = '[' + key + ']';
1010 | }
1011 | if (!str) {
1012 | if (ctx.seen.indexOf(desc.value) < 0) {
1013 | if (isNull(recurseTimes)) {
1014 | str = formatValue(ctx, desc.value, null);
1015 | } else {
1016 | str = formatValue(ctx, desc.value, recurseTimes - 1);
1017 | }
1018 | if (str.indexOf('\n') > -1) {
1019 | if (array) {
1020 | str = str.split('\n').map(function(line) {
1021 | return ' ' + line;
1022 | }).join('\n').substr(2);
1023 | } else {
1024 | str = '\n' + str.split('\n').map(function(line) {
1025 | return ' ' + line;
1026 | }).join('\n');
1027 | }
1028 | }
1029 | } else {
1030 | str = ctx.stylize('[Circular]', 'special');
1031 | }
1032 | }
1033 | if (isUndefined(name)) {
1034 | if (array && key.match(/^\d+$/)) {
1035 | return str;
1036 | }
1037 | name = JSON.stringify('' + key);
1038 | if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
1039 | name = name.substr(1, name.length - 2);
1040 | name = ctx.stylize(name, 'name');
1041 | } else {
1042 | name = name.replace(/'/g, "\\'")
1043 | .replace(/\\"/g, '"')
1044 | .replace(/(^"|"$)/g, "'");
1045 | name = ctx.stylize(name, 'string');
1046 | }
1047 | }
1048 |
1049 | return name + ': ' + str;
1050 | }
1051 |
1052 |
1053 | function reduceToSingleString(output, base, braces) {
1054 | var numLinesEst = 0;
1055 | var length = output.reduce(function(prev, cur) {
1056 | numLinesEst++;
1057 | if (cur.indexOf('\n') >= 0) numLinesEst++;
1058 | return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
1059 | }, 0);
1060 |
1061 | if (length > 60) {
1062 | return braces[0] +
1063 | (base === '' ? '' : base + '\n ') +
1064 | ' ' +
1065 | output.join(',\n ') +
1066 | ' ' +
1067 | braces[1];
1068 | }
1069 |
1070 | return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
1071 | }
1072 |
1073 |
1074 | // NOTE: These type checking functions intentionally don't use `instanceof`
1075 | // because it is fragile and can be easily faked with `Object.create()`.
1076 | function isArray(ar) {
1077 | return Array.isArray(ar);
1078 | }
1079 | exports.isArray = isArray;
1080 |
1081 | function isBoolean(arg) {
1082 | return typeof arg === 'boolean';
1083 | }
1084 | exports.isBoolean = isBoolean;
1085 |
1086 | function isNull(arg) {
1087 | return arg === null;
1088 | }
1089 | exports.isNull = isNull;
1090 |
1091 | function isNullOrUndefined(arg) {
1092 | return arg == null;
1093 | }
1094 | exports.isNullOrUndefined = isNullOrUndefined;
1095 |
1096 | function isNumber(arg) {
1097 | return typeof arg === 'number';
1098 | }
1099 | exports.isNumber = isNumber;
1100 |
1101 | function isString(arg) {
1102 | return typeof arg === 'string';
1103 | }
1104 | exports.isString = isString;
1105 |
1106 | function isSymbol(arg) {
1107 | return typeof arg === 'symbol';
1108 | }
1109 | exports.isSymbol = isSymbol;
1110 |
1111 | function isUndefined(arg) {
1112 | return arg === void 0;
1113 | }
1114 | exports.isUndefined = isUndefined;
1115 |
1116 | function isRegExp(re) {
1117 | return isObject(re) && objectToString(re) === '[object RegExp]';
1118 | }
1119 | exports.isRegExp = isRegExp;
1120 |
1121 | function isObject(arg) {
1122 | return typeof arg === 'object' && arg !== null;
1123 | }
1124 | exports.isObject = isObject;
1125 |
1126 | function isDate(d) {
1127 | return isObject(d) && objectToString(d) === '[object Date]';
1128 | }
1129 | exports.isDate = isDate;
1130 |
1131 | function isError(e) {
1132 | return isObject(e) &&
1133 | (objectToString(e) === '[object Error]' || e instanceof Error);
1134 | }
1135 | exports.isError = isError;
1136 |
1137 | function isFunction(arg) {
1138 | return typeof arg === 'function';
1139 | }
1140 | exports.isFunction = isFunction;
1141 |
1142 | function isPrimitive(arg) {
1143 | return arg === null ||
1144 | typeof arg === 'boolean' ||
1145 | typeof arg === 'number' ||
1146 | typeof arg === 'string' ||
1147 | typeof arg === 'symbol' || // ES6 symbol
1148 | typeof arg === 'undefined';
1149 | }
1150 | exports.isPrimitive = isPrimitive;
1151 |
1152 | exports.isBuffer = require('./support/isBuffer');
1153 |
1154 | function objectToString(o) {
1155 | return Object.prototype.toString.call(o);
1156 | }
1157 |
1158 |
1159 | function pad(n) {
1160 | return n < 10 ? '0' + n.toString(10) : n.toString(10);
1161 | }
1162 |
1163 |
1164 | var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
1165 | 'Oct', 'Nov', 'Dec'];
1166 |
1167 | // 26 Feb 16:19:34
1168 | function timestamp() {
1169 | var d = new Date();
1170 | var time = [pad(d.getHours()),
1171 | pad(d.getMinutes()),
1172 | pad(d.getSeconds())].join(':');
1173 | return [d.getDate(), months[d.getMonth()], time].join(' ');
1174 | }
1175 |
1176 |
1177 | // log is just a thin wrapper to console.log that prepends a timestamp
1178 | exports.log = function() {
1179 | console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
1180 | };
1181 |
1182 |
1183 | /**
1184 | * Inherit the prototype methods from one constructor into another.
1185 | *
1186 | * The Function.prototype.inherits from lang.js rewritten as a standalone
1187 | * function (not on Function.prototype). NOTE: If this file is to be loaded
1188 | * during bootstrapping this function needs to be rewritten using some native
1189 | * functions as prototype setup using normal JavaScript does not work as
1190 | * expected during bootstrapping (see mirror.js in r114903).
1191 | *
1192 | * @param {function} ctor Constructor function which needs to inherit the
1193 | * prototype.
1194 | * @param {function} superCtor Constructor function to inherit prototype from.
1195 | */
1196 | exports.inherits = require('inherits');
1197 |
1198 | exports._extend = function(origin, add) {
1199 | // Don't do anything if add isn't an object
1200 | if (!add || !isObject(add)) return origin;
1201 |
1202 | var keys = Object.keys(add);
1203 | var i = keys.length;
1204 | while (i--) {
1205 | origin[keys[i]] = add[keys[i]];
1206 | }
1207 | return origin;
1208 | };
1209 |
1210 | function hasOwnProperty(obj, prop) {
1211 | return Object.prototype.hasOwnProperty.call(obj, prop);
1212 | }
1213 |
1214 | }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
1215 | },{"./support/isBuffer":5,"_process":4,"inherits":3}]},{},[1])(1)
1216 | });
--------------------------------------------------------------------------------
/server/boot/authentication.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | module.exports = function enableAuthentication(server) {
7 | // enable authentication
8 | server.enableAuth();
9 | };
10 |
--------------------------------------------------------------------------------
/server/boot/changes.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | var Client = require('strong-pubsub');
7 | var Adapter = require('strong-pubsub-mqtt');
8 | var MOSQUITTO_PORT = process.env.MOSQUITTO_PORT || 1883;
9 |
10 | module.exports = function(app) {
11 | var Song = app.models.Song;
12 | var client = new Client({port: MOSQUITTO_PORT}, Adapter);
13 |
14 | Song.observe('after save', function updateTimestamp(ctx, next) {
15 | var song = ctx.instance;
16 | if (song) {
17 | client.publish('/playlists/' + song.playlist, song.id.toString());
18 | }
19 | next();
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/server/boot/primus.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | var Primus = require('primus');
7 | var Client = require('strong-pubsub');
8 | var Connection = require('strong-pubsub-connection-mqtt');
9 | var PubSubProxy = require('strong-pubsub-proxy');
10 | var Adapter = require('strong-pubsub-mqtt');
11 | var MOSQUITTO_PORT = process.env.MOSQUITTO_PORT || 1883;
12 |
13 | module.exports = function(app) {
14 | app.on('started', function(server) {
15 | var primus = new Primus(server, {
16 | transformer: 'engine.io',
17 | parser: 'binary'
18 | });
19 |
20 | primus.on('connection', function(spark) {
21 | var client = new Client({port: MOSQUITTO_PORT}, Adapter);
22 | var proxy = new PubSubProxy(
23 | new Connection(spark),
24 | client
25 | );
26 | proxy.connect();
27 | });
28 | });
29 |
30 | var testClient = new Client({port: MOSQUITTO_PORT}, Adapter);
31 |
32 | setInterval(function() {
33 | testClient.publish('/my-topic', 'hello');
34 | }, 1000);
35 | };
36 |
--------------------------------------------------------------------------------
/server/boot/pubsub-client.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | var path = require('path');
7 | var browserify = require('browserify');
8 |
9 | module.exports = function(app) {
10 | app.get('/pubsub-client.js', function(req, res) {
11 | var b = browserify({
12 | basedir: __dirname,
13 | debug: true
14 | });
15 |
16 | b.require(path.join(__dirname, '..', '..',
17 | 'client', 'js', 'pubsub-client.js'), {expose: 'pubsub-client'});
18 |
19 | b.bundle().pipe(res);
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/server/component-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "loopback-component-explorer": {
3 | "mountPath": "/explorer"
4 | }
5 | }
--------------------------------------------------------------------------------
/server/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "restApiRoot": "/api",
3 | "host": "0.0.0.0",
4 | "port": 3000,
5 | "legacyExplorer": false,
6 | "remoting": {
7 | "context": false,
8 | "rest": {
9 | "normalizeHttpPath": false,
10 | "xml": false
11 | },
12 | "json": {
13 | "strict": false,
14 | "limit": "100kb"
15 | },
16 | "urlencoded": {
17 | "extended": true,
18 | "limit": "100kb"
19 | },
20 | "cors": false,
21 | "handleErrors": false
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/server/datasources.json:
--------------------------------------------------------------------------------
1 | {
2 | "db": {
3 | "name": "db",
4 | "connector": "memory"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/server/middleware.json:
--------------------------------------------------------------------------------
1 | {
2 | "initial:before": {
3 | "loopback#favicon": {}
4 | },
5 | "initial": {
6 | "compression": {},
7 | "cors": {
8 | "params": {
9 | "origin": true,
10 | "credentials": true,
11 | "maxAge": 86400
12 | }
13 | }
14 | },
15 | "session": {
16 | },
17 | "auth": {
18 | },
19 | "parse": {
20 | },
21 | "routes": {
22 | "loopback#rest": {
23 | "paths": ["${restApiRoot}"]
24 | }
25 | },
26 | "files": {
27 | },
28 | "final": {
29 | "loopback#urlNotFound": {}
30 | },
31 | "final:after": {
32 | "strong-error-handler": {}
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/server/model-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "sources": [
4 | "loopback/common/models",
5 | "loopback/server/models",
6 | "../common/models",
7 | "./models"
8 | ]
9 | },
10 | "User": {
11 | "dataSource": "db"
12 | },
13 | "AccessToken": {
14 | "dataSource": "db",
15 | "public": false
16 | },
17 | "ACL": {
18 | "dataSource": "db",
19 | "public": false
20 | },
21 | "RoleMapping": {
22 | "dataSource": "db",
23 | "public": false
24 | },
25 | "Role": {
26 | "dataSource": "db",
27 | "public": false
28 | },
29 | "Song": {
30 | "dataSource": "db",
31 | "public": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | var loopback = require('loopback');
7 | var boot = require('loopback-boot');
8 |
9 | var app = module.exports = loopback();
10 |
11 | // Bootstrap the application, configure models, datasources and middleware.
12 | // Sub-apps like REST API are mounted via boot scripts.
13 | boot(app, __dirname);
14 |
15 | app.use(loopback.static(require('path').join(__dirname, '..', 'client')));
16 |
17 | app.start = function() {
18 | // start the web server
19 | var server = app.listen(function() {
20 | app.emit('started', server);
21 | var baseUrl = app.get('url').replace(/\/$/, '');
22 | console.log('Web server listening at: %s', app.get('url'));
23 | if (app.get('loopback-component-explorer')) {
24 | var explorerPath = app.get('loopback-component-explorer').mountPath;
25 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
26 | }
27 | });
28 | return server;
29 | };
30 |
31 | // start the server if `$ node server.js`
32 | if (require.main === module) {
33 | app.start();
34 | }
35 |
--------------------------------------------------------------------------------
/test/ci.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2015. All Rights Reserved.
2 | // Node module: loopback-example-pubsub
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | describe('ci', function() {
7 | it('should pass a basic smoke test', function(done) {
8 | done();
9 | });
10 | });
11 |
--------------------------------------------------------------------------------