Skip to content

Instantly share code, notes, and snippets.

@vjpr vjpr/README.md
Last active Jan 4, 2016

Embed
What would you like to do?
RPC for Chrome Packaged App to allow communication between sandbox and privileged environment

ChromeRPC

I will eventually turn this into a bower module when I have time with tests and the whole shebang.

Usage

background.html

<html>
<head>
<script src='background.js'/>
</head>
<body>
<iframe id='iframe' src='main.js'/>
</body>
</html>

background.js

iframe = document.getElementById 'iframe'
rpc = ChromeRPC.PackagedAppRPC iframe.contentWindow
rpc.on 'getFoo', (data, cb) -> 
  if data.foo
    cb null, {foo: 'bar'}

main.coffee

rpc = ChromeRPC.PackagedAppRPC window.top

rpc.call 'getFoo', {foo: true}, (err, res) ->
  if err then return console.error err
  console.log 'Got a response:', res
@ChromeRPC = {}
class @ChromeRPC
# Establish communication channel with server.
constructor: (@options = {}) ->
#@Message:
# name: 'S.Data.RPC.Message'
# properties:
# method: {required: true}
# params: {required: true}
# id: {required: false}
@options.logger or= console
@logger = @options.logger
# Listeners for incoming RPC requests.
@listeners = {}
# Dictionary of outgoing requests waiting for a response.
@outgoing = {}
# Dictionary of incoming requests to respond to.
@incoming = {}
start: (@socket) =>
# Validate socket conforms to API.
unless @socket.on? and @socket.emit? and @socket.removeAllListeners?
throw new Error "Socket does not conform to require API"
@lastId = 0
@outgoing = {}
@incoming = {}
@socket.on 'rpc', @handle
stop: =>
for key, callbacks of @outgoing
callbacks.error?("socket disconnected")
if @socket
@socket.removeAllListeners()
delete @socket
# Add a listener for an rpc request
# TODO: Only one listener for each request
on: (method, cb) =>
#validateArgs 2, ['string', 'function']
if @listeners[method]?
throw new Error "There is already a listener for '#{method}'. " +
"Only one listener permitted for each request."
@listeners[method] = cb
###
Handle incoming message from server
@param {S.Data.RPC.Message} message - JSON-RPC message
###
handle: (message) =>
#validateArgs 1, [S.Data.RPC.Message]
# Check if we have a request rather than a response
if message.method?
if message.id?
@logger.debug "Received request:", message.method, message.id
else
@logger.debug "Received notification:", message.method
fn = @listeners[message.method]
# Do we have a handler for this request
if not fn?
@logger.error "No listener found for #{message.method}"
fn = (params, callback) -> callback("no listener found")
# Check if we need to reply with a response
callback = null
if message.id?
callback = (err, res) =>
response =
result: res
error: err
id: message.id
@socket.emit 'rpc', response
fn message.params, callback
return
# We must have a response
else
# TODO: Report method name of response. Would make it way
# easier for debugging.
@logger.debug "Received response:", message.id, message
# Get callbacks for given request id
callbacks = @outgoing[message.id]
delete @outgoing[message.id]
# No callbacks found - carry on without error
return if not callbacks?
# If we've encountered an error in the response, trigger the error callback if it exists
if message.error
@logger.error "ERROR:", message.error
callbacks.error?(message.error)
return
# Otherwise, successful request, run the success request if it exists
callbacks.success?(null, message.result)
# Send a message to the server
# method -
# params -
# cb(err, resp) - [optional] If omitted, a notification is sent which
# does not expect a response.
call: (method, params, cb) =>
#validateArgs 1, 2, ['string', 'object']
if typeof params is 'function'
throw new Error 'Message must not be a function'
if @options.namespace?
methodName = @options.namespace + '.' + method
else
methodName = method
message =
method: methodName
if cb? then message.id = ++@lastId
message.params ?= params
# Send message to server
if @socket?
@logger.debug "Sending:", message
@socket.emit 'rpc', message, (err, result) =>
if err? then @logger.error "Failed to send Socket.IO message: #{err}"
# Store rpc callback with message id
if message.id?
id = message.id
@outgoing[id] =
success: cb
error: cb
else
@logger.error "No connection"
cb("no connection", null)
setLogger: (@logger) =>
class ExampleSocket
on: (msg, requestHandler) ->
emit: (msg, obj, responseHandler) ->
removeAllListeners: ->
class @ChromeRPC.WindowSocket
constructor: (@window, @receiver) ->
on: (method, handler) =>
@window.onmessage = (event) =>
handler event.data
# NOTE: ackCallback is only used to check successful send for supported
# protocols like Socket.io.
emit: (msg, message, ackCallback) =>
# NOTE: Message must be cloneable or you will get a `DataCloneError`.
@receiver.postMessage message, '*'
removeAllListeners: =>
# TODO
@ChromeRPC.PackagedAppRPC = (receiver) =>
rpc = new @ChromeRPC
rpc.start new ChromeRPC.WindowSocket window, receiver
return rpc
@burkeholland

This comment has been minimized.

Copy link

commented Jan 30, 2014

That's HAWT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.