Etherpad-lite integration in Crabgrass

Etherpad-lite integration in Crabgrass

Adding a new Page type called Pad that has as data an etherpad iframe

See how to install etherpad-lite install etherpad-lite

Etherpad-lite ruby library

Using library

Install it “gem install etherpad-lite”


Data Types

    groupID a string, the unique id of a group. Format is g.16RANDOMCHARS, for example g.s8oes9dhwrvt0zif
    sessionID a string, the unique id of a session. Format is s.16RANDOMCHARS, for example s.s8oes9dhwrvt0zif
    authorID a string, the unique id of an author. Format is a.16RANDOMCHARS, for example a.s8oes9dhwrvt0zif
    readOnlyID a string, the unique id of an readonly relation to a pad. Format is r.16RANDOMCHARS, for example r.s8oes9dhwrvt0zif
    padID a string, format is GROUPID$PADNAME, for example the pad test of group g.s8oes9dhwrvt0zif has padID g.s8oes9dhwrvt0zif$test

Only users with a valid session for this group, can access group pads

You should save the sessionID of this session and delete it after the user logged out

Hands Ruby-etherpad-lite

ruby script/console 

require 'etherpad-lite'

ether = EtherpadLite.connect('http://localhost:9001', '7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO')

ether = EtherpadLite.connect(:local,'/var/www/etherpad-lite/APIKEY.txt'))

pad = ether.pad('my first etherpad lite pad')=> #<EtherpadLite::Pad:0xb5f009dc @id="my first etherpad lite pad"

# Map your app's group to an EtherpadLite Group
# equivalent to /api/1/createGroupIfNotExistsFor?groupMapper=2000&apikey=...
group ="my_app_group_#{}")

#author =, name)

# Get the EtherpadLite Group and Pad by id
@group = ether.get_group(params[:group_id])

# Get or create an hour-long session for this Author in this Group
sess = session[:ep_sessions][] ? ether.get_session(session[:ep_sessions][]) : @group.create_session(author, 60)

if sess.expired?
  sess = @group.create_session(author, 60)

session[:ep_sessions][] =

# Set the EtherpadLite session cookie. This will automatically be picked up by the jQuery plugin's iframe.
cookies[:sessionID] = {:value =>, :domain => ""}

Example requests and responses

Extracted from from /var/log/etherpad-lite.log

API - REQUEST, createAuthorIfNotExistsFor, {"apikey":"7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO","authorMapper":"4","name":"blue"}
API - RESPONSE, createAuthorIfNotExistsFor, {"code":0,"message":"ok","data":{"authorID":"a.0uTNv4QGzEDdniaI"}}
http - 200, GET /api/1/createAuthorIfNotExistsFor?apikey=7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO&authorMapper=4&name=blue

#epg =
API - REQUEST, createGroupIfNotExistsFor, {"groupMapper":"2000","apikey":"7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO"}
API - RESPONSE, createGroupIfNotExistsFor, {"code":0,"message":"ok","data":{"groupID":"g.x4rEQoMdP2K85LWc"}}
http - 200, GET /api/1/createGroupIfNotExistsFor?groupMapper=2000&apikey=7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO

API - REQUEST, createGroupPad, {"apikey":"7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO","groupID":"g.x4rEQoMdP2K85LWc","padName":"mypad"}
API - RESPONSE, createGroupPad, {"code":0,"message":"ok","data":{"padID":"g.x4rEQoMdP2K85LWc$mypad"}}
http - 200, GET /api/1/createGroupPad?apikey=7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO&groupID=g.x4rEQoMdP2K85LWc&padName=mypad

API - REQUEST, createSession, {"validUntil":"1337259943","apikey":"7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO","groupID":"g.x4rEQoMdP2K85LWc","authorID":"a.0uTNv4QGzEDdniaI"}
API - RESPONSE, createSession, {"code":0,"message":"ok","data":{"sessionID":"s.PaZ4JYSUcldgk5JB"}}
http - 200, GET /api/1/createSession?validUntil=1337259943&apikey=7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO&groupID=g.x4rEQoMdP2K85LWc&authorID=a.0uTNv4QGzEDdniaI

# - handshake authorized '1958083917176744501'
# message - from 1958083917176744501: {"component":"pad","type":"CLIENT_READY","padId":"g.x4rEQoMdP2K85LWc$mypad","sessionID":"s.NA5y8KIxGYOXG5jc","password":null,"token":"t.nGKY8MXC0Y6gHQmtlx5Y","protocolVersion":2}

API - REQUEST, getSessionInfo, {"apikey":"7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO","sessionID":"s.PaZ4JYSUcldgk5JB"}
API - RESPONSE, getSessionInfo, {"code":0,"message":"ok","data":{"groupID":"g.x4rEQoMdP2K85LWc","authorID":"a.0uTNv4QGzEDdniaI","validUntil":1337259943}}
http - 200, GET /api/1/getSessionInfo?apikey=7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO&sessionID=s.PaZ4JYSUcldgk5JB

API - REQUEST, deleteSession, {"apikey":"7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO","sessionID":"s.PaZ4JYSUcldgk5JB"}
API - RESPONSE, deleteSession, {"code":0,"message":"ok","data":null}
http - 200, GET /api/1/deleteSession?apikey=7DU3LJWh7pGojsniRAtnqFi7ayzPy4fO&sessionID=s.PaZ4JYSUcldgk5JB

Crabgrass code changes

The branch where the code is:

Most of the code created lives on extensions/pages/pad_page

Files changed/added

 config/crabgrass/crabgrass.development.yml # Add PadPage here, still needed?


DB tables added


# create_table do |t|
#   t.integer :page_id
#   t.string  :name
#   t.blob    :text
#   t.string  :url
# end
  create_table "pads", :force => true do |t|
    t.string   "name"
    t.string   "url"
    t.text     "text"
    t.integer  "revision",   :default => 0
    t.integer  "page_id"
    t.datetime "created_at"
    t.datetime "updated_at"

Comments to the code


PadPage here, still needed?


  pad_page_description: "Create a collaborative editor for a group."
  pad_page_display: "Pad"


etherpad needs the cookie, otherwise u get permission denied

ep session is in session cookie, cg session is in session

just ensure that if ep_sessions was not in the global rails session variable, add it

  def refresh_epl_session
    session[:ep_sessions] ||= {}


it creates a frame with the etherpad url


for tests, the symbol needs to be wroten as string


Example trace

  extensions/pages/pad_page/app/models/pad.rb:22:in `create_pad_on_etherpad'
  extensions/pages/pad_page/app/models/pad.rb:40:in `sync'
  extensions/pages/pad_page/app/models/pad.rb:40:in `new'
  extensions/pages/pad_page/lib/EPL.rb:27:in `initialize'
  extensions/pages/pad_page/lib/EPL.rb:94:in `create_pad!'

Example of js if we’d like to use the jquery lib

<script src="js/etherpad.js"></script>
<div id="examplePadBasic"></div>

<script type="text/javascript">
        // A slightly more intense example
        $('#examplePadIntense').pad({'padId':'test2','showChat':'true'}); // sets the pad id and puts the pad in the div


  • Creating a pad as user is created with group anyway
  • Sharing a pad with other user works
  • Granting access to one pad of a group does not grant access to other pads.

To fix

  • Sharing a pad with another user in readonly mode still allow the other user to write
  • Add a pad_page icon different from the page_icon
  • Pad renaming (not only the Page but change also the name of the pad)

To check

  • When is etherpad showing the message reconnecting?
  • If a group shares a pad with another group, will the next pad be shared too by default?
  • changing ownership of a pad (e.g., from a user to the group) should recreate the pad under the group. (move)
  • of course, the original pad should be deleted then, but does that affect history?
  • → changing ownership does not (yet) move the pad, so it’s a failing case
  • How to move the history?

To do

  • Rename sess to ep_session, user, group, etc to don’t confuse ep stuff with cg one
  • DONE: group_mapping is configurable per container, and should be chosen to be a unique string.
    (was: Add owner type as in @page.owner_id, the user id and group id might be the same)
  • DONE page title should not be editable, or the pad should be recreated with the right contents
  • DONE tests are missing!
  • CLOSED some edge cases with etherpad-lite not loading the pad (etherpad-lite issue)—didn’t happen in a while
  • some page functionalities should be disabled?
  • DONE without the sessionID cookie, one cannot access the pad: if the session entry exists,
    but not the cookie, it should be created.
  • destroying the page should remove the pad
  • for a static view, contents should be fetched from page.pad.text -> maybe would cache the latest revision when a static view is requested
  • WONTFIX Editing the page title should rename the pad (but that would break the URL) → the title is set once for all by hashing the original title.
  • edit : current iframe with editor
  • show: text without the editor/colors…
  • print: like show but without all cg menus and so on