-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.coffee
More file actions
157 lines (129 loc) · 3.75 KB
/
main.coffee
File metadata and controls
157 lines (129 loc) · 3.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
###
Postmaster wraps the `postMessage` API with promises.
###
defaultReceiver = self
ackTimeout = 1000
pmId = 0
module.exports = Postmaster = (self={}) ->
name = "#{defaultReceiver.name}-#{++pmId}"
info = ->
self.logger.info(name, arguments...)
debug = ->
self.logger.debug(name, arguments...)
dominant = Postmaster.dominant()
self.remoteTarget ?= -> dominant
self.receiver ?= -> defaultReceiver
self.ackTimeout ?= -> ackTimeout
self.delegate ?= self
self.logger ?=
info: ->
debug: ->
self.token ?= Math.random()
send = (data) ->
target = self.remoteTarget()
if self.token
data.token = self.token
data.from = name
if !target
throw new Error "No remote target"
info("->", data)
if !Worker? or target instanceof Worker
target.postMessage data
else
target.postMessage data, "*"
return
listener = (event) ->
{data, source} = event
target = self.remoteTarget()
# Only listening to messages from `opener`
# event.source becomes undefined during the `onunload` event
# We can track a token and match to allow the final message in this case
if source is target or (source is undefined and data.token is self.token)
event.stopImmediatePropagation() #
info "<-", data
{type, method, params, id} = data
switch type
when "ack"
pendingResponses[id]?.ack = true
when "response"
pendingResponses[id].resolve data.result
when "error"
pendingResponses[id].reject data.error
when "message"
Promise.resolve()
.then ->
if source
send
type: "ack"
id: id
if typeof self.delegate[method] is "function"
self.delegate[method](params...)
else
throw new Error "`#{method}` is not a function"
.then (result) ->
if source
send
type: "response"
id: id
result: result
.catch (error) ->
if typeof error is "string"
message = error
else
message = error.message
if source
send
type: "error"
id: id
error:
message: message
stack: error.stack
else
debug "DROP message", event, "source #{JSON.stringify(data.from)} does not match target"
receiver = self.receiver()
receiver.addEventListener "message", listener
self.dispose = ->
receiver.removeEventListener "message", listener
info "DISPOSE"
pendingResponses = {}
msgId = 0
clear = (id) ->
debug "CLEAR PENDING", id
clearTimeout pendingResponses[id].timeout
delete pendingResponses[id]
self.invokeRemote = (method, params...) ->
new Promise (resolve, reject) ->
id = ++msgId
ackWait = self.ackTimeout()
timeout = setTimeout ->
unless resp.ack
info "TIMEOUT", resp
resp.reject new Error "No ack received within #{ackWait}"
, ackWait
debug "STORE PENDING", id
pendingResponses[id] = resp =
timeout: timeout
resolve: (result) ->
debug "RESOLVE", id, result
resolve(result)
clear(id)
reject: (error) ->
debug "REJECT", id, error
reject(error)
clear(id)
try
send
type: "message"
method: method
params: params
id: id
catch e
reject(e)
return
info "INITIALIZE"
return self
Postmaster.dominant = ->
if window? # iframe or child window context
opener or ((parent != window) and parent) or undefined
else # Web Worker Context
self