-
Notifications
You must be signed in to change notification settings - Fork 19
/
interaction.go
488 lines (421 loc) · 17.9 KB
/
interaction.go
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
// Copyright (C) 2023 Gobalsky Labs Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package interactor
import (
"encoding/json"
"time"
"github.com/mitchellh/mapstructure"
)
const (
CancelRequestName InteractionName = "CANCEL_REQUEST"
DecisionName InteractionName = "DECISION"
EnteredPassphraseName InteractionName = "ENTERED_PASSPHRASE"
ErrorOccurredName InteractionName = "ERROR_OCCURRED"
InteractionSessionBeganName InteractionName = "INTERACTION_SESSION_BEGAN"
InteractionSessionEndedName InteractionName = "INTERACTION_SESSION_ENDED"
LogName InteractionName = "LOG"
RequestPassphraseName InteractionName = "REQUEST_PASSPHRASE"
RequestPermissionsReviewName InteractionName = "REQUEST_PERMISSIONS_REVIEW"
RequestSucceededName InteractionName = "REQUEST_SUCCEEDED"
RequestTransactionReviewForSendingName InteractionName = "REQUEST_TRANSACTION_REVIEW_FOR_SENDING"
RequestTransactionReviewForSigningName InteractionName = "REQUEST_TRANSACTION_REVIEW_FOR_SIGNING"
RequestTransactionReviewForCheckingName InteractionName = "REQUEST_TRANSACTION_REVIEW_FOR_CHECKING"
RequestWalletConnectionReviewName InteractionName = "REQUEST_WALLET_CONNECTION_REVIEW"
RequestWalletSelectionName InteractionName = "REQUEST_WALLET_SELECTION"
SelectedWalletName InteractionName = "SELECTED_WALLET"
TransactionFailedName InteractionName = "TRANSACTION_FAILED"
TransactionSucceededName InteractionName = "TRANSACTION_SUCCEEDED"
WalletConnectionDecisionName InteractionName = "WALLET_CONNECTION_DECISION"
)
type InteractionName string
// Interaction wraps the messages the JSON-RPC API sends to the wallet front-end
// along with information about the context.
type Interaction struct {
// TraceID is an identifier specifically made for client front-end to keep
// track of a transaction during all of its lifetime, from transaction
// review to sending confirmation and in-memory history.
// It shouldn't be confused with the transaction hash that is the
// transaction identifier.
TraceID string `json:"traceID"`
// Name is the name of the interaction. This helps to figure out how the
// data payload should be parsed.
Name InteractionName `json:"name"`
// Data is the generic field that hold the data of the specific interaction.
Data interface{} `json:"data"`
}
func (f *Interaction) UnmarshalJSON(data []byte) error {
input := &struct {
TraceID string `json:"traceID"`
Name InteractionName `json:"name"`
Data map[string]interface{} `json:"data"`
}{}
if err := json.Unmarshal(data, &input); err != nil {
return err
}
f.TraceID = input.TraceID
f.Name = input.Name
switch input.Name {
case CancelRequestName:
data := CancelRequest{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case RequestWalletConnectionReviewName:
data := RequestWalletConnectionReview{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case RequestWalletSelectionName:
data := RequestWalletSelection{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case RequestPassphraseName:
data := RequestPassphrase{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case RequestPermissionsReviewName:
data := RequestPermissionsReview{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case RequestTransactionReviewForSendingName:
data := RequestTransactionReviewForSending{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case RequestTransactionReviewForSigningName:
data := RequestTransactionReviewForSigning{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case RequestTransactionReviewForCheckingName:
data := RequestTransactionReviewForChecking{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case WalletConnectionDecisionName:
data := WalletConnectionDecision{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case DecisionName:
data := Decision{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case EnteredPassphraseName:
data := EnteredPassphrase{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case SelectedWalletName:
data := SelectedWallet{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case InteractionSessionBeganName:
data := InteractionSessionBegan{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case InteractionSessionEndedName:
data := InteractionSessionEnded{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case RequestSucceededName:
data := RequestSucceeded{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case ErrorOccurredName:
data := ErrorOccurred{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case TransactionSucceededName:
data := TransactionSucceeded{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case TransactionFailedName:
data := TransactionFailed{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
case LogName:
data := Log{}
if err := mapstructure.Decode(input.Data, &data); err != nil {
return err
}
f.Data = data
}
return nil
}
// RequestWalletConnectionReview is a request emitted when a third-party
// application wants to connect to a wallet.
type RequestWalletConnectionReview struct {
Hostname string `json:"hostname"`
StepNumber uint8 `json:"stepNumber"`
// ResponseCh is the channel in which the response to that request is expected.
ResponseCh chan<- Interaction `json:"-"`
// ControlCh is the channel we use to communicate state of the request to
// the UI. If the request gets canceled on the third-party application side,
// we close this channel and the UI, that is listening to it, has to react
// accordingly.
ControlCh <-chan error `json:"-"`
}
// RequestWalletSelection is a request emitted when the service requires the user
// to select a wallet. It is emitted after the user approved the wallet connection
// from a third-party application.
// It should be answered by an interactor.SelectedWallet response.
type RequestWalletSelection struct {
Hostname string `json:"hostname"`
AvailableWallets []string `json:"availableWallets"`
StepNumber uint8 `json:"stepNumber"`
// ResponseCh is the channel in which the response to that request is expected.
ResponseCh chan<- Interaction `json:"-"`
// ControlCh is the channel we use to communicate state of the request to
// the UI. If the request gets canceled on the third-party application side,
// we close this channel and the UI, that is listening to it, has to react
// accordingly.
ControlCh chan error `json:"-"`
}
// RequestPassphrase is a request emitted when the service wants to confirm
// the user has access to the wallet.
// It should be answered by an interactor.EnteredPassphrase response.
type RequestPassphrase struct {
Wallet string `json:"wallet"`
Reason string `json:"reason"`
StepNumber uint8 `json:"stepNumber"`
// ResponseCh is the channel in which the response to that request is expected.
ResponseCh chan<- Interaction `json:"-"`
// ControlCh is the channel we use to communicate state of the request to
// the UI. If the request gets canceled on the third-party application side,
// we close this channel and the UI, that is listening to it, has to react
// accordingly.
ControlCh chan error `json:"-"`
}
// RequestPermissionsReview is a review request emitted when a third-party
// application performs an operation that requires an update to the permissions.
type RequestPermissionsReview struct {
Hostname string `json:"hostname"`
Wallet string `json:"wallet"`
Permissions map[string]string `json:"permissions"`
StepNumber uint8 `json:"stepNumber"`
// ResponseCh is the channel in which the response to that request is expected.
ResponseCh chan<- Interaction `json:"-"`
// ControlCh is the channel we use to communicate state of the request to
// the UI. If the request gets canceled on the third-party application side,
// we close this channel and the UI, that is listening to it, has to react
// accordingly.
ControlCh chan error `json:"-"`
}
// RequestTransactionReviewForSending is a review request emitted when a third-party
// application wants to send a transaction.
type RequestTransactionReviewForSending struct {
Hostname string `json:"hostname"`
Wallet string `json:"wallet"`
PublicKey string `json:"publicKey"`
Transaction string `json:"transaction"`
ReceivedAt time.Time `json:"receivedAt"`
StepNumber uint8 `json:"stepNumber"`
// ResponseCh is the channel in which the response to that request is expected.
ResponseCh chan<- Interaction `json:"-"`
// ControlCh is the channel we use to communicate state of the request to
// the UI. If the request gets canceled on the third-party application side,
// we close this channel and the UI, that is listening to it, has to react
// accordingly.
ControlCh chan error `json:"-"`
}
// RequestTransactionReviewForChecking is a review request when a third-party
// application wants the user to check a transaction.
type RequestTransactionReviewForChecking struct {
Hostname string `json:"hostname"`
Wallet string `json:"wallet"`
PublicKey string `json:"publicKey"`
Transaction string `json:"transaction"`
ReceivedAt time.Time `json:"receivedAt"`
StepNumber uint8 `json:"stepNumber"`
// ResponseCh is the channel in which the response to that request is expected.
ResponseCh chan<- Interaction `json:"-"`
// ControlCh is the channel we use to communicate state of the request to
// the UI. If the request gets canceled on the third-party application side,
// we close this channel and the UI, that is listening to it, has to react
// accordingly.
ControlCh chan error `json:"-"`
}
// RequestTransactionReviewForSigning is a review request when a third-party
// application wants to sign a transaction.
type RequestTransactionReviewForSigning struct {
Hostname string `json:"hostname"`
Wallet string `json:"wallet"`
PublicKey string `json:"publicKey"`
Transaction string `json:"transaction"`
ReceivedAt time.Time `json:"receivedAt"`
StepNumber uint8 `json:"stepNumber"`
// ResponseCh is the channel in which the response to that request is expected.
ResponseCh chan<- Interaction `json:"-"`
// ControlCh is the channel we use to communicate state of the request to
// the UI. If the request gets canceled on the third-party application side,
// we close this channel and the UI, that is listening to it, has to react
// accordingly.
ControlCh chan error `json:"-"`
}
// WalletConnectionDecision is a specific response for interactor.RequestWalletConnectionReview.
type WalletConnectionDecision struct {
// ConnectionApproval tells if the third-party application is authorized
// to connect to a wallet.
// The value is the string representation of a preferences.ConnectionApproval.
ConnectionApproval string `json:"connectionApproval"`
}
// Decision is a generic response for the following review requests:
// - RequestPermissionsReview
// - RequestTransactionReviewForSigning
// - RequestTransactionReviewForSending
type Decision struct {
// Approved is set to true if the request is accepted by the user, false
// otherwise.
Approved bool `json:"approved"`
}
// EnteredPassphrase contains the passphrase of a given wallet the user
// entered. It's a response to the interactor.RequestPassphrase.
type EnteredPassphrase struct {
Passphrase string `json:"passphrase"`
}
// SelectedWallet contains the wallet chosen by the user for a given action.
type SelectedWallet struct {
Wallet string `json:"wallet"`
}
// CancelRequest cancels a request that is waiting for user inputs.
// It can't cancel a request when there is no response awaited.
type CancelRequest struct{}
// InteractionSessionBegan is a notification that is emitted when the interaction
// session begin. It only carries informational value on a request lifecycle. This
// is the first notification to be emitted and is always emitted when a request
// comes in.
type InteractionSessionBegan struct {
Workflow string `json:"workflow"`
MaximumNumberOfSteps uint8 `json:"maximumNumberOfSteps"`
}
// InteractionSessionEnded is a notification that is emitted when the interaction
// session ended. This is the last notification to be emitted and is always emitted,
// regardless of the request status. It only carries informational value on a
// request lifecycle. The success or failure status of a request is carried by
// the interactor.RequestSucceeded and interactor.ErrorOccurred notifications,
// respectively.
// Nothing should be expected after receiving this notification.
type InteractionSessionEnded struct{}
// ErrorOccurred is a generic notification emitted when the something failed.
// This notification can wrap an internal failure as much as a user input error.
// Receiving this notification doesn't necessarily mean the overall
// request failed. The request should be considered as failed when this notification
// is followed by the interactor.InteractionSessionEnded notification.
type ErrorOccurred struct {
// Type is an enumeration that gives information about the origin of the error.
// The value is the string representation of an api.ErrorType.
Type string `json:"type"`
// Error is the error message describing the reason of the failure.
Error string `json:"error"`
}
// RequestSucceeded is a generic notification emitted when the request succeeded,
// meaning no error has been encountered.
// This notification is emitted only once.
type RequestSucceeded struct {
// Message can contain a custom success message.
Message string `json:"message"`
StepNumber uint8 `json:"stepNumber"`
}
// TransactionSucceeded is a notification sent when the sending of a
// transaction succeeded. It replaces the RequestSucceeded notification as it
// carries specific information that wallet front-ends may use for history.
// This notification is emitted only once.
type TransactionSucceeded struct {
// TxHash is the hash of the transaction that is used to uniquely identify
// a transaction. It can be used to retrieve a transaction in the explorer.
TxHash string `json:"txHash"`
// DeserializedInputData is the input data bundled in the transaction in a
// human-readable format.
DeserializedInputData string `json:"deserializedInputData"`
// Tx is the true representation of the transaction that is sent to the
// network.
Tx string `json:"tx"`
// SentAt is the time a which the transaction has been sent to the network.
// It's useful to build a list of the sending in a chronological order on
// the front-ends.
SentAt time.Time `json:"sentAt"`
// Node contains all the information related to the node selected for the
// sending of the transaction.
Node SelectedNode `json:"node"`
StepNumber uint8 `json:"stepNumber"`
}
// TransactionFailed is a notification sent when the sending of a
// transaction failed for any reason. It replaces the ErrorOccurred notification
// as it carries specific information that wallet front-ends may use for
// investigation.
// This notification is emitted only once.
type TransactionFailed struct {
// DeserializedInputData is the input data bundled in the transaction in a
// human-readable format.
DeserializedInputData string `json:"deserializedInputData"`
// Tx is the true representation of the transaction that is sent to the
// network.
Tx string `json:"tx"`
// Error is the error message describing the reason of the failure.
Error error `json:"error"`
// SentAt is the time a which the transaction has been sent to the network.
// It's useful to build a list of the sending in a chronological order on
// the front-ends.
SentAt time.Time `json:"sentAt"`
// Node contains all the information related to the node selected for the
// sending of the transaction.
Node SelectedNode `json:"node"`
StepNumber uint8 `json:"stepNumber"`
}
type SelectedNode struct {
Host string `json:"host"`
}
// Log is a generic event that shouldn't be confused with a notification. A log
// is conceptually different. Whatever the type (error, warning...), a log is just
// an information about the internal processing that we think is worth to
// broadcast to wallet front-ends. That said, it can be safely ignored if not
// needed. That is where is differs from the notifications.
type Log struct {
// Type is an enumeration that gives information about the level of log.
// The value is the string representation of an api.LogType.
Type string `json:"type"`
// Message is the log message itself.
Message string `json:"message"`
}