Skip to content

Instantly share code, notes, and snippets.

@vjpr vjpr/
Last active Jan 4, 2016

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


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



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


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

rpc = ChromeRPC.PackagedAppRPC '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 = {}) ->
# 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
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?
@logger.debug "Received request:", message.method,
@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
callback = (err, res) =>
response =
result: res
error: err
@socket.emit 'rpc', response
fn message.params, callback
# We must have a response
# TODO: Report method name of response. Would make it way
# easier for debugging.
@logger.debug "Received response:",, message
# Get callbacks for given request id
callbacks = @outgoing[]
delete @outgoing[]
# 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
# 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
methodName = method
message =
method: methodName
if cb? then = ++@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
id =
@outgoing[id] =
success: cb
error: cb
@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) =>
# NOTE: ackCallback is only used to check successful send for supported
# protocols like
emit: (msg, message, ackCallback) =>
# NOTE: Message must be cloneable or you will get a `DataCloneError`.
@receiver.postMessage message, '*'
removeAllListeners: =>
@ChromeRPC.PackagedAppRPC = (receiver) =>
rpc = new @ChromeRPC
rpc.start new ChromeRPC.WindowSocket window, receiver
return rpc

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.