-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
messaging.ts
161 lines (139 loc) · 5.05 KB
/
messaging.ts
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
158
159
160
161
import { fromEvent, Subscribable, Subscription } from 'rxjs'
import { filter, map } from 'rxjs/operators'
import { Connection, createConnection } from 'sourcegraph/module/protocol/jsonrpc2/connection'
import { Message } from 'sourcegraph/module/protocol/jsonrpc2/messages'
import {
AbstractMessageReader,
AbstractMessageWriter,
DataCallback,
MessageReader,
MessageWriter,
} from 'sourcegraph/module/protocol/jsonrpc2/transport'
import { UpdateExtensionSettingsArgs } from './context'
class SubscribableMessageReader extends AbstractMessageReader implements MessageReader {
private pending: Message[] = []
private callback: DataCallback | null = null
private subscription = new Subscription()
constructor(subscribable: Subscribable<Message>) {
super()
this.subscription.add(
subscribable.subscribe(message => {
try {
if (this.callback) {
this.callback(message)
} else {
this.pending.push(message)
}
} catch (err) {
this.fireError(err)
}
})
)
}
public listen(callback: DataCallback): void {
if (this.callback) {
throw new Error('callback is already set')
}
this.callback = callback
while (this.pending.length !== 0) {
callback(this.pending.pop()!)
}
}
public unsubscribe(): void {
super.unsubscribe()
this.subscription.unsubscribe()
}
}
class CallbackMessageWriter extends AbstractMessageWriter implements MessageWriter {
constructor(private callback: (message: Message) => void) {
super()
}
public write(message: Message): void {
this.callback(message)
}
public unsubscribe(): void {
super.unsubscribe()
}
}
/** One side of a page/client connection. */
export type Source = 'Page' | 'Client'
/**
* Connects the Sourcegraph extension registry page to a client (such as a browser extension) or vice versa.
*/
function connectAs(source: Source): Promise<Connection> {
const connection = createConnection({
reader: new SubscribableMessageReader(
fromEvent<MessageEvent>(window, 'message').pipe(
// Filter to relevant messages, ignoring our own
filter(m => m.data && m.data.source && m.data.source !== source && m.data.message),
map(m => m.data.message)
)
),
writer: new CallbackMessageWriter(message => {
window.postMessage({ source, message }, '*')
}),
})
connection.listen()
return new Promise(resolve => {
connection.onNotification('Ping', () => {
connection.sendNotification('Pong')
resolve(connection)
})
connection.onNotification('Pong', () => {
resolve(connection)
})
connection.sendNotification('Ping')
})
}
/** A connection to the client. */
export interface ClientConnection {
/** Listens for the latest client settings. */
onSettings: (callback: (settings: string) => void) => void
/** Requests the client to update a setting. */
editSetting: (edit: UpdateExtensionSettingsArgs) => Promise<void>
/** Asks the client for its settings. */
getSettings: () => Promise<string>
/** The underlying JSON RPC connection. */
rawConnection: Connection
}
/** A connection to the page. */
export interface PageConnection {
/** Listens for requests to edit settings. */
onEditSetting: (callback: (edit: UpdateExtensionSettingsArgs) => Promise<void>) => void
/** Listens for requests for the latest settings. */
onGetSettings: (callback: () => Promise<string>) => void
/** Notifies the page that the settings have been updated. */
sendSettings: (settings: string) => void
/** The underlying JSON RPC connection. */
rawConnection: Connection
}
/**
* Connects the client (such as a browser extension) to a Sourcegraph extension registry page.
*/
export function connectAsClient(): Promise<PageConnection> {
return connectAs('Client').then<PageConnection>(connection => ({
onEditSetting: callback => {
connection.onRequest('EditSetting', callback)
},
onGetSettings: callback => {
connection.onRequest('GetSettings', callback)
},
sendSettings: settings => {
connection.sendNotification('Settings', settings)
},
rawConnection: connection,
}))
}
/**
* Connects the Sourcegraph extension registry page to a client (such as a browser extension).
*/
export function connectAsPage(): Promise<ClientConnection> {
return connectAs('Page').then<ClientConnection>(connection => ({
onSettings: callback => {
connection.onNotification('Settings', callback)
},
editSetting: callback => connection.sendRequest('EditSetting', callback),
getSettings: () => connection.sendRequest('GetSettings'),
rawConnection: connection,
}))
}