Skip to content

Commit 9df222b

Browse files
author
Eric Ingram
committed
initial commit
0 parents  commit 9df222b

File tree

7 files changed

+587
-0
lines changed

7 files changed

+587
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.fwd
2+
node_modules

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License
2+
3+
Copyright (c) 2014-2015 Schema
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
13+
all 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
21+
THE SOFTWARE.

README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
## Schema API Client for NodeJS
2+
3+
*Schema is the API-centric platform to build and scale ecommerce.*
4+
5+
Create an account at https://schema.io
6+
7+
## Usage example
8+
9+
npm install schema-client
10+
11+
## Usage example
12+
13+
var Schema = require('schema-client');
14+
15+
var client = new Schema.Client('client_id', 'client_key');
16+
17+
client.get('/categories/shoes/products', {color: 'blue'}, function(products) {
18+
console.log(products);
19+
});
20+
21+
## Documentation
22+
23+
See <http://schema.io/docs/clients#node> for more API docs and usage examples
24+
25+
## Contributing
26+
27+
Pull requests are welcome
28+
29+
## License
30+
31+
MIT
32+

lib/client.js

+330
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
/**
2+
* Forward Client
3+
*/
4+
5+
var events = require('events');
6+
var crypto = require('crypto');
7+
var inherits = require('util').inherits;
8+
9+
var Connection = require('./connection').Connection;
10+
var Collection = require('./schema').Collection;
11+
var Record = require('./schema').Record;
12+
13+
var defaults = {
14+
host: 'api.getfwd.com',
15+
port: 8443,
16+
verifyCert: true,
17+
version: 1
18+
};
19+
20+
/**
21+
* Client constructor
22+
*
23+
* @param string clientId
24+
* @param string clientKey
25+
* @param object options
26+
* @param function callback
27+
*/
28+
var Client = function(clientId, clientKey, options, callback) {
29+
30+
events.EventEmitter.call(this);
31+
32+
this.server = null;
33+
this.buffer = [];
34+
35+
this.init(clientId, clientKey, options, callback);
36+
this.connect(function(self) {
37+
callback && callback(self);
38+
});
39+
};
40+
41+
inherits(Client, events.EventEmitter);
42+
43+
/**
44+
* Initialize client parameters
45+
*
46+
* @param string clientId
47+
* @param string clientKey
48+
* @param object options
49+
* @param function callback
50+
*/
51+
Client.prototype.init = function(clientId, clientKey, options) {
52+
53+
options = options || {};
54+
55+
if (typeof options === 'function') {
56+
callback = options;
57+
options = {};
58+
} else if (typeof clientKey === 'function') {
59+
callback = clientKey;
60+
options = {};
61+
}
62+
if (typeof clientKey === 'object') {
63+
options = clientKey;
64+
clientKey = undefined;
65+
} else if (typeof clientId === 'object') {
66+
options = clientId;
67+
clientId = undefined;
68+
}
69+
70+
this.params = {
71+
host: options.host || defaults.host,
72+
port: options.port || defaults.port,
73+
clientId: clientId || options.id,
74+
clientKey: clientKey || options.key,
75+
clear: options.clear !== undefined ? options.clear : defaults.clear,
76+
verifyCert: options.verifyCert !== undefined ? options.verifyCert : defaults.verifyCert,
77+
version: options.version || defaults.version,
78+
session: options.session,
79+
api: options.api
80+
};
81+
};
82+
83+
/**
84+
* Connect to server
85+
*
86+
* @param function callback
87+
*/
88+
Client.prototype.connect = function(callback) {
89+
90+
var self = this;
91+
this.server = new Connection(
92+
this.params.host,
93+
this.params.port,
94+
{
95+
clear: this.params.clear,
96+
verifyCert: this.params.verifyCert
97+
},
98+
function() {
99+
self.flushBuffer();
100+
callback && callback(self);
101+
self.emit('connect', self);
102+
}
103+
);
104+
this.server.on('error', function(err, type) {
105+
self.emit('error', 'Error: '+err);
106+
});
107+
this.server.on('error.network', function(err) {
108+
self.emit('error', 'Network Error: '+err, 'network');
109+
});
110+
this.server.on('error.protocol', function(err) {
111+
self.emit('error', 'Protocol Error: '+err, 'protocol');
112+
});
113+
this.server.on('error.server', function(err) {
114+
self.emit('error', 'Server Error: '+err, 'server');
115+
});
116+
117+
// Fallback handler
118+
this.on('error', function(err) {
119+
var lcount = events.EventEmitter.listenerCount(self, 'error');
120+
if (lcount === 1) {
121+
throw 'Schema Client: '+err;
122+
}
123+
});
124+
};
125+
126+
/**
127+
* Client request helper
128+
*
129+
* @param string method
130+
* @param string url
131+
* @param mixed data
132+
* @param function callback
133+
*/
134+
Client.prototype.request = function(method, url, data, callback) {
135+
136+
if (typeof data === 'function') {
137+
callback = data;
138+
data = null;
139+
}
140+
if (!this.server.connected) {
141+
this.buffer.push(arguments);
142+
} else {
143+
url = url && url.toString ? url.toString() : '';
144+
data = {$data: data};
145+
var self = this;
146+
this.server.request(method, [url, data], function(result) {
147+
if (result.$auth) {
148+
if (result.$end) {
149+
// Connection ended, retry
150+
return self.request(method, url, data.$data, callback);
151+
} else {
152+
self.authed = true;
153+
return self.auth(result.$auth, function(result) {
154+
return self.response(method, url, result, callback);
155+
});
156+
}
157+
} else {
158+
return self.response(method, url, result, callback);
159+
}
160+
});
161+
// TODO: implement rescue request flow
162+
}
163+
};
164+
165+
/**
166+
* Client response handler
167+
*
168+
* @param string method
169+
* @param string url
170+
* @param mixed result
171+
* @param function callback
172+
*/
173+
Client.prototype.response = function(method, url, result, callback) {
174+
175+
var actualResult = null;
176+
177+
if (result
178+
&& result.$data
179+
&& (typeof result.$data === 'object')) {
180+
// TODO: use a header to determine url of a new record
181+
if (method.toLowerCase() === 'post') {
182+
url = url.replace(/\/$/, '') + '/' + result.$data.id;
183+
}
184+
actualResult = Client.createResource(url, result, this);
185+
} else {
186+
actualResult = result.$data;
187+
}
188+
return callback && callback.call(this, actualResult);
189+
};
190+
191+
/**
192+
* Client GET request
193+
*
194+
* @param string url
195+
* @param object data
196+
* @param function callback
197+
* @return mixed
198+
*/
199+
Client.prototype.get = function(url, data, callback) {
200+
return this.request('get', url, data, callback);
201+
};
202+
203+
/**
204+
* Client PUT request
205+
*/
206+
Client.prototype.put = function(url, data, callback) {
207+
return this.request('put', url, data, callback);
208+
};
209+
210+
/**
211+
* Client POST request
212+
*/
213+
Client.prototype.post = function(url, data, callback) {
214+
return this.request('post', url, data, callback);
215+
};
216+
217+
/**
218+
* Client DELETE request
219+
*/
220+
Client.prototype.delete = function(url, data, callback) {
221+
return this.request('delete', url, data, callback);
222+
};
223+
224+
/**
225+
* Client auth request
226+
*
227+
* @param object params
228+
* @param function callback
229+
* @return mixed;
230+
*/
231+
Client.prototype.auth = function(nonce, callback) {
232+
233+
var self = this;
234+
var clientId = this.params.clientId;
235+
var clientKey = this.params.clientKey;
236+
237+
if (typeof nonce === 'function') {
238+
callback = nonce;
239+
nonce = null;
240+
}
241+
242+
// 1) Get nonce
243+
if (!nonce) {
244+
return this.server.request('auth', [], function(nonce) {
245+
self.auth(nonce, callback);
246+
});
247+
}
248+
249+
// 2) Create key hash
250+
var keyHash = crypto.createHash('md5')
251+
.update(clientId + "::" + clientKey)
252+
.digest('hex');
253+
254+
// 3) Create auth key
255+
var authKey = crypto.createHash('md5')
256+
.update(nonce + clientId + keyHash)
257+
.digest('hex');
258+
259+
// 4) Authenticate with client creds and options
260+
var creds = {
261+
client: clientId,
262+
key: authKey
263+
};
264+
if (this.params.api) {
265+
creds.$api = this.params.api;
266+
}
267+
if (this.params.version) {
268+
creds.$v = this.params.version;
269+
}
270+
if (this.params.session) {
271+
creds.$session = this.params.session;
272+
}
273+
274+
// TODO: send local $ip address
275+
276+
return this.server.request('auth', [creds], function(result) {
277+
if (result.$error) {
278+
self.emit('error', 'Authentication failed (client: '+clientId+')');
279+
} else {
280+
callback && callback.call(this, result);
281+
}
282+
});
283+
};
284+
285+
/**
286+
* Flush local request buffer (when connected)
287+
*
288+
* @return void
289+
*/
290+
Client.prototype.flushBuffer = function() {
291+
292+
if (this.buffer.length && this.server.connected) {
293+
for (var i = 0; i < this.buffer.length; i++) {
294+
this.request.apply(this, this.buffer[i]);
295+
}
296+
}
297+
};
298+
299+
/**
300+
* Client create/init helper
301+
*
302+
* @param string clientId
303+
* @param string clientKey
304+
* @param object options
305+
* @param function callback
306+
* @return Client
307+
*/
308+
Client.create = function(clientId, clientKey, options, callback) {
309+
return new Client(clientId, clientKey, options, callback);
310+
};
311+
312+
/**
313+
* Create a resource from result data
314+
*
315+
* @param string url
316+
* @param mixed result
317+
* @param Client client
318+
* @return Resource
319+
*/
320+
Client.createResource = function(url, result, client) {
321+
if (result && result.$data && result.$data.count && result.$data.results) {
322+
return new Collection(url, result, client);
323+
}
324+
return new Record(url, result, client);
325+
};
326+
327+
// Exports
328+
exports.Client = Client;
329+
exports.defaults = defaults;
330+
exports.createClient = Client.create;

0 commit comments

Comments
 (0)