Ind.ie is now Small Technology Foundation.
Commit 83601c38 authored by Aral Balkan's avatar Aral Balkan

Initial add. Combined Pulse process, config, and API modules.

parents
node_modules
.stfolder
.DS_Store
npm-debug.log
*.old*
test-repository
\ No newline at end of file
Copyright © 2015 Aral Balkan. © 2015 Ind.ie
All Rights Reserved.
\ No newline at end of file
######################################################################
#
# Ind.ie/pulse REST API client for Node.js.
#
# Uses restler under the hood and exposes
# the API via Bluebird promises.
#
# Example usage:
#
# Pulse = require('indie-pulse')
#
# pulse = new Pulse('<YOUR_API_KEY>')
#
# pulse.version()
# .then (result) ->
# console.log 'Version: ' + result
# .catch (error) ->
# console.log 'Error while attempting to get version: ' + error
#
# Copyright (c) 2014 Aral Balkan. Released under GNU AGPLv3
# Independence ★ Democracy ★ Design
# ❤ ind.ie
#
######################################################################
rest = require 'restler'
Promise = require 'bluebird'
util = require 'util'
printIt = require 'printit'
log = printIt {prefix: 'Pulse API', date: true}
#
# Public API
#
# log = (obj) ->
# # Nicer, more detailed logs
# console.log util.inspect(obj, showHidden=false, depth=5, colorize=true)
class Main
# The URL to pulse REST API.
_pulseBaseURL: 'http://localhost:8080/rest/'
_apiKey: null
_headers: null
constructor: (apiKey) ->
log.info "Creating a new Pulse API consumer with API key: #{apiKey}"
@_apiKey = apiKey
@_headers =
'Accept': '*/*'
'User-Agent': 'Pulse API consumer for node.js'
'X-API-Key': apiKey
######################################################################
#
# GET
#
######################################################################
getVersion: => @pulse('get', 'version')
# Data parameter:
# folder: folder ID
getModel: (data) => @pulse('get', 'model', data)
getConnections: => @pulse('get', 'connections')
# Data parameters:
# folder: folder ID
# device: device ID
getCompletion: (data) => @pulse('get', 'completion', data)
getConfig: => @pulse('get', 'config')
getConfigSync: => @pulse('get', 'config/sync')
getSystem: => @pulse('get', 'system')
getErrors: => @pulse('get', 'errors')
getDiscovery: => @pulse('get', 'discovery')
# Data parameter:
# id: device ID
getDeviceId: (data) => @pulse('get', 'deviceid', data)
######################################################################
#
# POST
#
######################################################################
# Data: same object structure as returned from GET:config
postConfig: (data) => @pulse('postJson', 'config', data)
postRestart: => @pulse('postJson', 'restart')
# Not sure about the format the data should be sent in: ask Jakob.
# postError: (data) -> @pulse('post', 'error', data)
postErrorClear: => @pulse('postJson', 'error/clear')
# Data: device (Device ID), addr (IP address:port)
postDiscoveryHint: (data) => @pulse('postJson', 'discovery/hint', data)
# TODO: Not working. Ask Jakob about this (see tests for more info)
postScan: (data) => @pulse('postJson', 'scan', data)
######################################################################
#
# Private functions.
#
######################################################################
#
# Helper function for creating REST URLS.
#_
_pulseURL: (endpoint) =>
return @_pulseBaseURL + endpoint
#
# Helper function that makes the calls.
#
pulse: (verb, url, data = {}) =>
# Sanity
if verb not in ['get', 'post', 'postJson']
throw new Error 'Unsupported REST verb: ' + verb
return new Promise (fulfill, reject) =>
retriesLeft = 3
# Choose the correct data token ('query' or 'data') based
# on whether the REST call is GET or POST, respectively.
restCall = null
optionsObject = {headers: @_headers}
if verb == 'get'
# GET
optionsObject.query = data
restCall = rest.get(@_pulseURL(url), optionsObject)
else if verb == 'postJson'
# POST JSON
if !data
data = {}
restCall = rest.postJson(@_pulseURL(url), data, optionsObject)
# else if verb == 'post'
# #
# # Regular POST
# # (The post error method, for example, requires the body of
# # the error in the message)
# #
# optionsObject.data = data
# restCall = rest.post(@_pulse(url), optionsObject)
restCall
.on 'success', (result) ->
# Success
# Inject the url and passed data as metadata on the result
# in case the handler needs to introspect it to differentiate this call from others.
console.log 'Pulse API RESULT: '
console.log result
# Some calls seems to return nothing (not an empty object but simply nothing)
# if there is nothing to return.
# TODO: Check with Jakob if this is expected behaviour. Surely this should
# ===== be handled differently by Pulse.
if !result
result = {}
result.__meta__ = {url: url, data: data}
process.nextTick ->
console.log "Pulse API call success:"
console.log result
fulfill(result)
.on 'fail', (data, response) ->
# A failure is a successful response with a failure code.
# Retrying will not alter the outcome so let’s fail.
data.__meta__ = {url: url, data: data}
console.log "Pulse API Fail:"
console.log url
console.log data
# log.info response
# log.info response.rawEncoded
if response.rawEncoded == 'CSRF Error\n'
log.error 'Check the Pulse GUI to make sure you have an API key set and check your code to make sure that you’re using it.'
process.nextTick ->
reject(data)
.on 'error', (error) ->
# An error is possibly recoverable — try to do so.
log.warn "#{error} while trying to reach #{url}"
if retriesLeft
retryOrRetries = 'retries'
if retriesLeft == 1
retryOrRetries = 'retry'
log.warn "#{retriesLeft} #{retryOrRetries} left. Trying again in 3 seconds…"
this.retry 3000
retriesLeft--
else
error.__meta__ = {url: url, data: data}
process.nextTick ->
log.error 'No retries left.'
reject(error)
module.exports = Main
This diff is collapsed.
################################################################################
#
# Ind.ie Pulse Process
#
# Starts and manages a pulse process. Informs the delegate of events.
#
# Usage: See test.coffee
#
# Copyright © 2014-2015, Aral Balkan.
# This is independent technology. See ind.ie/manifesto
# Released under the ind.ie/license
#
################################################################################
os = require 'os'
assert = require 'assert'
path = require 'path-extra'
spawn = require('child_process').spawn
PulseConfig = require 'indie-pulse-config'
exec = require('child_process').exec
printIt = require 'printit'
log = printIt {prefix: 'Pulse Process', date: true}
class Main
@pulseProcess = null
#
# Pulse Process Delegate interface.
# (All delegate methods are optional.)
#
# pulseProcessRestApiIsReady
# pulseProcessFailedToStartProcess (data)
# pulseProcessDidSendData (data)
# pulseProcessDidSendErrorData (data)
# pulseProcessDidExitWithCode (data)
#
# A delegate is not required but you should really have one set
# to have control over the process.
#
delegate: {}
homeDirectory: null # e.g., NSHomeDirectory() as passed from Heartbeat native client
pulseDirectory: null # <homeDirectory>/Pulse
pulseConfigDirectory: null # <homeDirectory>/Pulse/Config
pulseSyncDirectory: null # <homeDirectory>/Pulse/Sync
pulseAPIKey: null
# The version of Pulse that is currently supported.
version: '1.0.0+14-gd2cd1f4'
constructor: (delegate={}, homeDirectory='') ->
# Sanity check: make sure that the version exists
assert(@version, 'Version must exist.')
@delegate = delegate
if delegate == {}
log.warn "There is not Pulse process delegate set."
# If we’re explicitly passed a home directory (as is the case with the
# sandboxed native OS X app, then use that. Otherwise, as is the case
# in Waystone), use the home directory on the system.
@homeDirectory = if (homeDirectory=='') then path.datadir() else homeDirectory
# Convenience paths
@pulseDirectory = path.join(@homeDirectory, 'Pulse')
@pulseConfigDirectory = path.join(@pulseDirectory, 'Config')
@pulseSyncDirectory = path.join(@pulseDirectory, 'Sync')
# Debug
# console.log 'Pulse process: home directory is ' + @homeDirectory
pid: =>
return @pulseProcess.pid
start: (generate=false, name='')=>
#
# Start Pulse.
#
#
# First shut down any existing instances.
#
console.log "Sending a shutdown message to any existing Pulse instances before starting up a new one."
PulseConfig.setHomeDirectory(@homeDirectory)
PulseConfig.getApiKey().then (apiKey) =>
@pulseAPIKey = apiKey
command = "curl -X POST --header \"X-API-Key: #{@pulseAPIKey}\" http://127.0.0.1:8080/rest/shutdown"
console.log "Command: #{command}"
exec command, (error, stdout, stderr) =>
console.log 'stdout: ' + stdout
console.log 'stderr: ' + stderr
if error != null
console.log 'exec error: ' + error
# OK, Now let’s start.
@_start generate, name
_start: (generate, name) =>
console.log "Pulse process._start(): generate: #{generate}, name: #{name}"
# Load the correct binary
# Currently supported: x64 Mac and Linux (more to be added)
supportedPlatforms = [
{platform: 'linux', architecture: 'x64', binary: 'linux-amd64'},
{platform: 'darwin', architecture: 'x64', binary: 'macosx-amd64'}
]
platform = os.platform()
architecture = os.arch()
binary = null
for supportedPlatform in supportedPlatforms
if (platform == supportedPlatform.platform) and (architecture == supportedPlatform.architecture)
binary = supportedPlatform.binary
# We can’t recover if the platform is not supported. Fail catastophically. (Shakespeare would be proud.)
assert.notEqual null, binary, 'No binary found for platform: ' + platform + ', architecture: ' + architecture + '. Bailing.'
binaryFilePath = __dirname + '/pulse/pulse-' + binary + '-' + @version + '/pulse'
#
# Launch the pulse process
#
pulseArguments = ['-no-browser']
if generate
pulseArguments.push("-generate=#{@pulseConfigDirectory}")
if name != ''
pulseArguments.push("-name=#{name}")
else
pulseArguments.push("-home=#{@pulseConfigDirectory}")
#
# Set the STNORESTART environment variable so that Pulse
# does not automatically restart.
# (I’m adding this in an effort to gain better control over
# the Pulse process as we seem unable to SIGTERM kill it.)
#
# TODO: Does this break anywhere where we are implicitly relying
# ===== on restarts. If so, we need to handle these manually.
#
# This might be making Pulse brittle after putting computer to sleep.
# Commenting out to test.
#
#env = process.env
#env['STNORESTART'] = 1
#options =
# 'env': env
log.info "About to start Pulse version #{@version} at home directory: #{@homeDirectory} with arguments: #{pulseArguments}"
@pulseProcess = spawn binaryFilePath, pulseArguments #, options
@pulseProcess.stdout.on 'data', (data) =>
#
# Generic data callback.
#
log.info "🌏 #{data}"
if data == '' then log.warn 'EMPTY DATA!'
if @delegate.pulseProcessDidSendData != undefined
@delegate.pulseProcessDidSendData(data)
#
# Specific callback for when the REST API is ready.
#
if /INFO: Starting web GUI/.test(data)
log.info '🌏 Pulse REST API is ready.'
if @delegate.pulseProcessRestApiIsReady != undefined
@delegate.pulseProcessRestApiIsReady()
else if /Is another copy of Pulse already running\?/.test(data)
log.error '🌏 Pulse was already running, this should not happen.'
@stop()
@pulseProcess.stderr.on 'data', (data) =>
#
# Generic error callback.
#
log.error "🌏😩 #{data}"
if @delegate.pulseProcessDidSendErrorData != undefined
@delegate.pulseProcessDidSendErrorData data
#
# Specific error callback when child process fails to start.
#
if /^execvp\(\)/.test(data)
log.error '🌏😱 Failed to start child process.'
if @delegate.pulseProcessFailedToStartProcess != undefined
@delegate.pulseProcessFailedToStartProcess data
@pulseProcess.on 'close', (code) =>
log.info '🌏👋 Exited with code ' + code
if @delegate.pulseProcessDidExitWithCode != undefined
@delegate.pulseProcessDidExitWithCode code
stop: =>
#
# Stop the Pulse process.
#
log.info '🌏 Killing Pulse process…'
@pulseProcess?.kill 'SIGTERM'
@pulseProcess = null
module.exports = Main
\ No newline at end of file
npm publish
\ No newline at end of file
################################################################################
#
# Ind.ie Pulse
#
# * .process: Process manager
# * .config: Configuration
# * .api: API
#
# Usage:
#
# PulseProcess = (require 'pulse-node').process
# PulseConfig = (require 'pulse-node').config
# PulseAPI = (require 'pulse-node').api
#
# TODO: Refactor: review and harmonise the APIs between the three classes.
# ===== Currently, I’m only combining them into the same module to remove the
# cyclical reference at install-time. There is redundancy between the
# three classes that can be refactored as well as room to streamline the
# interface in general.
#
# Copyright © 2014-2015, Aral Balkan.
# This is independent technology. See ind.ie/manifesto
# Released under the ind.ie/license
#
################################################################################
class Main
@process = require './PulseProcess'
@config = require './PulseConfig'
@api = require './PulseAPI'
module.exports = Main
#!/bin/sh
# Make sure failed commands cause the script to fail so
# potential errors early on don’t snowball.
set -e
echo "Installing Ind.ie Pulse Node…\n"
# Install/update the node modules
npm install
# Sanity check by actually running the module and making sure it works.
coffee index.coffee
echo "Ind.ie Pulse Node installed.\n"
{
"name": "pulse-node",
"version": "1.0.0",
"description": "Classes for managing the Pulse process, configuration, and API.",
"main": "index.coffee",
"repository": {
"type": "git",
"url": "https://source.ind.ie/project/pulse-node"
},
"bugs": {
"url": "https://source.ind.ie/project/pulse-node/issues"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"indie",
"pulse"
],
"author": "Aral Balkan",
"license": "ind.ie/license",
"dependencies": {
"bluebird": "2.3.2",
"fs-extra": "0.11.0",
"path-extra": "0.3.0",
"printit": "0.1.5",
"restler": "3.2.2",
"randomstring": "1.0.3",
"xml2js": "0.4.4"
}
}
os = require 'os'
console.log 'platform() -> ' + os.platform()
console.log 'cpus() -> '
console.log os.cpus()
console.log 'type() -> ' + os.type()
console.log 'arch() -> ' + os.arch()
console.log 'release() -> ' + os.release()
\ No newline at end of file
Aaron Bieber <qbit@deftly.net>
Alexander Graf <register-github@alex-graf.de>
Andrew Dunham <andrew@du.nham.ca>
Audrius Butkevicius <audrius.butkevicius@gmail.com>
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
Ben Sidhom <bsidhom@gmail.com>
Brandon Philips <brandon@ifup.org>
Gilli Sigurdsson <gilli@vx.is>
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
Lode Hoste <zillode@zillode.be>
Marcin Dziadus <dziadus.marcin@gmail.com>
Michael Tilli <pyfisch@gmail.com>
Philippe Schommers <philippe@schommers.be>
Ryan Sullivan <kayoticsully@gmail.com>
Tully Robinson <tully@tojr.org>
Veeti Paananen <veeti.paananen@rojekti.fi>
Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
# Pulse
Pulse is a free (as in freedom), secure, and distributed file synchronisation engine. This Go version is forked from the MIT-licensed version of [Syncthing](http://syncthing.net).
There is also an early [Swift version](https://source.ind.ie/project/pulse-swift/tree/master) in the works.
Pulse uses the [Block Exchange Protocol](https://source.ind.ie/project/pulse/blob/master/protocol/PROTOCOL.md).
License
=======
All documentation and protocol specifications are licensed
under the [Creative Commons Attribution 4.0 International
License](http://creativecommons.org/licenses/by/4.0/).
All code is licensed under the [MIT
-License](https://source.ind.ie/project/pulse/blob/master/LICENSE).
Pulse is being developed and maintained (with love) by [Ind.ie](https://ind.ie)
Aaron Bieber <qbit@deftly.net>
Alexander Graf <register-github@alex-graf.de>
Andrew Dunham <andrew@du.nham.ca>
Audrius Butkevicius <audrius.butkevicius@gmail.com>
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
Ben Sidhom <bsidhom@gmail.com>
Brandon Philips <brandon@ifup.org>
Gilli Sigurdsson <gilli@vx.is>
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
Lode Hoste <zillode@zillode.be>
Marcin Dziadus <dziadus.marcin@gmail.com>
Michael Tilli <pyfisch@gmail.com>
Philippe Schommers <philippe@schommers.be>
Ryan Sullivan <kayoticsully@gmail.com>
Tully Robinson <tully@tojr.org>
Veeti Paananen <veeti.paananen@rojekti.fi>
Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
# Pulse
Pulse is a free (as in freedom), secure, and distributed file synchronisation engine. This Go version is forked from the MIT-licensed version of [Syncthing](http://syncthing.net).
There is also an early [Swift version](https://source.ind.ie/project/pulse-swift/tree/master) in the works.
Pulse uses the [Block Exchange Protocol](https://source.ind.ie/project/pulse/blob/master/protocol/PROTOCOL.md).
License
=======
All documentation and protocol specifications are licensed
under the [Creative Commons Attribution 4.0 International
License](http://creativecommons.org/licenses/by/4.0/).
All code is licensed under the [MIT
-License](https://source.ind.ie/project/pulse/blob/master/LICENSE).
Pulse is being developed and maintained (with love) by [Ind.ie](https://ind.ie)
This diff is collapsed.
# indie-pulse
A promises-based API client for the [Ind.ie Pulse REST API][2] in Node.js.
## Installation
npm install indie-pulse
(For Heartbeat development, the module is cloned in from source.ind.ie by ./install)
## Usage
Pulse = require('indie-pulse')
pulse = new Pulse('<YOUR_API_KEY_HERE>')
pulse.version()
.then (result) ->
console.log 'Version: ' + result
.catch (error) ->
console.log 'Error while attempting to get version: ' + error
## Tests
coffee test.coffee
## Reference
* [The REST interface][2]
## Credits
* [Syncthing][1] by Jakob Borg, et. al.
* Uses [Restler][6] by Dan Webb.
* Uses [bluebird][7] by Petka Antonov.
Copyright &copy; 2014 [Aral Balkan][3]. Licensed under [GNU GPLv3][5]. Released with ❤ by [ind.ie][4]
[1]: http://syncthing.net
[2]: https://discourse.syncthing.net/t/the-rest-interface/85
[3]: https://aralbalkan.com
[4]: https://ind.ie
[5]: http://www.gnu.org/licenses/gpl-3.0.html
[6]: https://github.com/danwrong/restler
[7]: https://github.com/petkaantonov/bluebird
\ No newline at end of file
This diff is collapsed.
# Ind.ie Pulse Config
Configures Pulse. Used in Heartbeat and Waystone.
## Installation