ShareJsDoc.coffee 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. define [
  2. "utils/EventEmitter"
  3. "libs/sharejs"
  4. ], (EventEmitter, ShareJs) ->
  5. SINGLE_USER_FLUSH_DELAY = 1000 #ms
  6. class ShareJsDoc extends EventEmitter
  7. constructor: (@doc_id, docLines, version, @socket) ->
  8. # Dencode any binary bits of data
  9. # See http://ecmanaut.blogspot.co.uk/2006/07/encoding-decoding-utf8-in-javascript.html
  10. @type = "text"
  11. docLines = for line in docLines
  12. if line.text?
  13. @type = "json"
  14. line.text = decodeURIComponent(escape(line.text))
  15. else
  16. @type = "text"
  17. line = decodeURIComponent(escape(line))
  18. line
  19. if @type == "text"
  20. snapshot = docLines.join("\n")
  21. else if @type == "json"
  22. snapshot = { lines: docLines }
  23. else
  24. throw new Error("Unknown type: #{@type}")
  25. @connection = {
  26. send: (update) =>
  27. @_startInflightOpTimeout(update)
  28. @socket.emit "applyOtUpdate", @doc_id, update
  29. state: "ok"
  30. id: @socket.socket.sessionid
  31. }
  32. @_doc = new ShareJs.Doc @connection, @doc_id,
  33. type: @type
  34. @_doc.setFlushDelay(SINGLE_USER_FLUSH_DELAY)
  35. @_doc.on "change", () =>
  36. @trigger "change"
  37. @_doc.on "acknowledge", () =>
  38. @trigger "acknowledge"
  39. @_doc.on "remoteop", () =>
  40. # As soon as we're working with a collaborator, start sending
  41. # ops as quickly as possible for low latency.
  42. @_doc.setFlushDelay(0)
  43. @trigger "remoteop"
  44. @_bindToDocChanges(@_doc)
  45. @processUpdateFromServer
  46. open: true
  47. v: version
  48. snapshot: snapshot
  49. submitOp: (args...) -> @_doc.submitOp(args...)
  50. processUpdateFromServer: (message) ->
  51. try
  52. @_doc._onMessage message
  53. catch error
  54. # Version mismatches are thrown as errors
  55. @_handleError(error)
  56. if message?.meta?.type == "external"
  57. @trigger "externalUpdate", message
  58. catchUp: (updates) ->
  59. for update, i in updates
  60. update.v = @_doc.version
  61. update.doc = @doc_id
  62. @processUpdateFromServer(update)
  63. getSnapshot: () -> @_doc.snapshot
  64. getVersion: () -> @_doc.version
  65. getType: () -> @type
  66. clearInflightAndPendingOps: () ->
  67. @_doc.inflightOp = null
  68. @_doc.inflightCallbacks = []
  69. @_doc.pendingOp = null
  70. @_doc.pendingCallbacks = []
  71. flushPendingOps: () ->
  72. # This will flush any ops that are pending.
  73. # If there is an inflight op it will do nothing.
  74. @_doc.flush()
  75. updateConnectionState: (state) ->
  76. @connection.state = state
  77. @connection.id = @socket.socket.sessionid
  78. @_doc.autoOpen = false
  79. @_doc._connectionStateChanged(state)
  80. hasBufferedOps: () ->
  81. @_doc.inflightOp? or @_doc.pendingOp?
  82. getInflightOp: () -> @_doc.inflightOp
  83. getPendingOp: () -> @_doc.pendingOp
  84. attachToAce: (ace) -> @_doc.attach_ace(ace)
  85. detachFromAce: () -> @_doc.detach_ace?()
  86. INFLIGHT_OP_TIMEOUT: 10000
  87. _startInflightOpTimeout: (update) ->
  88. meta =
  89. v: update.v
  90. op_sent_at: new Date()
  91. timer = setTimeout () =>
  92. @trigger "op:timeout", update
  93. , @INFLIGHT_OP_TIMEOUT
  94. @_doc.inflightCallbacks.push () =>
  95. clearTimeout timer
  96. _handleError: (error, meta = {}) ->
  97. @trigger "error", error, meta
  98. _bindToDocChanges: (doc) ->
  99. submitOp = doc.submitOp
  100. doc.submitOp = (args...) =>
  101. @trigger "op:sent", args...
  102. doc.pendingCallbacks.push () =>
  103. @trigger "op:acknowledged", args...
  104. submitOp.apply(doc, args)
  105. flush = doc.flush
  106. doc.flush = (args...) =>
  107. @trigger "flush", doc.inflightOp, doc.pendingOp, doc.version
  108. flush.apply(doc, args)