blob: 287407bd9b06dd443a90945dd517497fce016097 [file] [log] [blame]
giolekva313ee2b2021-12-15 15:17:29 +04001package main
2
3import (
giolekvaf58a7692021-12-15 18:05:39 +04004 "bytes"
5 "encoding/json"
giolekva313ee2b2021-12-15 15:17:29 +04006 "flag"
7 "fmt"
giolekvaf58a7692021-12-15 18:05:39 +04008 "image"
9 "image/png"
giolekva1026d2d2021-12-19 19:09:15 +040010 "strings"
giolekva52da88a2021-12-17 18:08:25 +040011 "time"
giolekva313ee2b2021-12-15 15:17:29 +040012
13 "gioui.org/app"
giolekva96202c52021-12-18 12:45:34 +040014 "gioui.org/font/gofont"
giolekva313ee2b2021-12-15 15:17:29 +040015 "gioui.org/io/system"
16 "gioui.org/layout"
17 "gioui.org/op"
18 "gioui.org/unit"
giolekva96202c52021-12-18 12:45:34 +040019 "gioui.org/widget/material"
giolekvaf58a7692021-12-15 18:05:39 +040020 "github.com/skip2/go-qrcode"
giolekva313ee2b2021-12-15 15:17:29 +040021)
22
giolekvaf58a7692021-12-15 18:05:39 +040023var vpnApiAddr = flag.String("vpn-api-addr", "", "VPN API server address")
giolekva313ee2b2021-12-15 15:17:29 +040024
giolekvaf58a7692021-12-15 18:05:39 +040025var p *processor
26
27type processor struct {
28 vc VPNClient
29 app App
giolekva8d6a0ca2021-12-19 17:42:25 +040030 st Storage
giolekvaf58a7692021-12-15 18:05:39 +040031 ui *UI
32
33 inviteQrCh chan image.Image
34 inviteQrScannedCh chan []byte
giolekva52da88a2021-12-17 18:08:25 +040035
36 onConnectCh chan interface{}
37 onDisconnectCh chan interface{}
giolekva8d6a0ca2021-12-19 17:42:25 +040038
39 onConfigCh chan struct{}
giolekva313ee2b2021-12-15 15:17:29 +040040}
41
giolekvaf58a7692021-12-15 18:05:39 +040042func newProcessor() *processor {
giolekva96202c52021-12-18 12:45:34 +040043 th := material.NewTheme(gofont.Collection())
giolekva8d6a0ca2021-12-19 17:42:25 +040044 app := createApp()
giolekvaf58a7692021-12-15 18:05:39 +040045 return &processor{
46 vc: NewDirectVPNClient(*vpnApiAddr),
giolekva8d6a0ca2021-12-19 17:42:25 +040047 app: app,
48 st: app.CreateStorage(),
giolekva96202c52021-12-18 12:45:34 +040049 ui: NewUI(th),
giolekvaf58a7692021-12-15 18:05:39 +040050 inviteQrCh: make(chan image.Image, 1),
51 inviteQrScannedCh: make(chan []byte, 1),
giolekva52da88a2021-12-17 18:08:25 +040052 onConnectCh: make(chan interface{}, 1),
53 onDisconnectCh: make(chan interface{}, 1),
giolekva8d6a0ca2021-12-19 17:42:25 +040054 onConfigCh: make(chan struct{}, 1),
giolekvaf58a7692021-12-15 18:05:39 +040055 }
56}
57
58func (p *processor) InviteQRCodeScanned(code []byte) {
59 p.inviteQrScannedCh <- code
60}
61
giolekva52da88a2021-12-17 18:08:25 +040062func (p *processor) ConnectRequested(service interface{}) {
63 go func() {
64 time.Sleep(1 * time.Second)
65 p.onConnectCh <- service
66 }()
67}
68
69func (p *processor) DisconnectRequested(service interface{}) {
70 p.onDisconnectCh <- service
71}
72
giolekvaf58a7692021-12-15 18:05:39 +040073func (p *processor) run() error {
giolekva313ee2b2021-12-15 15:17:29 +040074 w := app.NewWindow(
75 app.Size(unit.Px(1500), unit.Px(1500)),
76 app.Title("PCloud"),
77 )
78 var ops op.Ops
79 for {
80 select {
81 case e := <-w.Events():
82 switch e := e.(type) {
83 case app.ViewEvent:
giolekvaf58a7692021-12-15 18:05:39 +040084 if err := p.app.OnView(e); err != nil {
giolekva313ee2b2021-12-15 15:17:29 +040085 return err
86 } else {
87 w.Invalidate()
88 }
giolekva8d6a0ca2021-12-19 17:42:25 +040089 if config, err := p.st.Get(); err != nil {
90 return err
91 } else {
92 if config.Network != nil {
93 p.onConfigCh <- struct{}{}
94 }
95 }
giolekva313ee2b2021-12-15 15:17:29 +040096 case *system.CommandEvent:
97 if e.Type == system.CommandBack {
giolekvaf58a7692021-12-15 18:05:39 +040098 if p.ui.OnBack() {
giolekva313ee2b2021-12-15 15:17:29 +040099 e.Cancel = true
100 w.Invalidate()
101 }
102 }
103 case system.FrameEvent:
104 gtx := layout.NewContext(&ops, e)
giolekvaf58a7692021-12-15 18:05:39 +0400105 events := p.ui.Layout(gtx)
giolekva313ee2b2021-12-15 15:17:29 +0400106 e.Frame(&ops)
giolekvaf58a7692021-12-15 18:05:39 +0400107 if err := p.processUIEvents(events); err != nil {
giolekva313ee2b2021-12-15 15:17:29 +0400108 return err
109 }
110 }
giolekvaf58a7692021-12-15 18:05:39 +0400111 case img := <-p.inviteQrCh:
112 p.ui.InviteQRGenerated(img)
113 w.Invalidate()
114 case code := <-p.inviteQrScannedCh:
giolekva8d6a0ca2021-12-19 17:42:25 +0400115 if err := p.JoinAndGetNetworkConfig(code); err != nil {
giolekva96202c52021-12-18 12:45:34 +0400116 return err
giolekva52da88a2021-12-17 18:08:25 +0400117 }
giolekva8d6a0ca2021-12-19 17:42:25 +0400118 case <-p.onConfigCh:
119 if err := p.app.TriggerService(); err != nil {
120 return err
121 }
122 case s := <-p.onConnectCh:
123 if err := p.app.UpdateService(s); err != nil {
124 return err
125 }
126 if config, err := p.st.Get(); err != nil {
127 return err
128 } else {
129 if err := p.app.Connect(config); err != nil {
130 return err
131 }
132 }
giolekva313ee2b2021-12-15 15:17:29 +0400133 }
134 }
135 return nil
136}
137
giolekvaf58a7692021-12-15 18:05:39 +0400138func (p *processor) processUIEvents(events []UIEvent) error {
139 for _, e := range events {
140 switch e.(type) {
141 case EventGetInviteQRCode:
142 go func() {
143 if img, err := p.generateInviteQRCode(); err == nil {
144 p.inviteQrCh <- img
145 } else {
146 // TODO(giolekva): do not panic
147 panic(err)
148 }
149 }()
150 case EventScanBarcode:
151 return p.app.LaunchBarcodeScanner()
152 default:
153 return fmt.Errorf("Unhandled event: %#v", e)
154 }
155 }
156 return nil
157}
158
159type qrCodeData struct {
160 VPNApiAddr string `json:"vpn_api_addr"`
161 Message []byte `json:"message"`
162 Signature []byte `json:"signature"`
163}
164
165func (p *processor) generateInviteQRCode() (image.Image, error) {
giolekva8d6a0ca2021-12-19 17:42:25 +0400166 config, err := p.st.Get()
167 if err != nil {
168 return nil, err
169 }
giolekvaf58a7692021-12-15 18:05:39 +0400170 message := []byte("Hello PCloud")
giolekva8d6a0ca2021-12-19 17:42:25 +0400171 signature, err := p.vc.Sign(config.ApiAddr, message)
giolekvaf58a7692021-12-15 18:05:39 +0400172 if err != nil {
173 return nil, err
174 }
175 c := qrCodeData{
giolekva8d6a0ca2021-12-19 17:42:25 +0400176 config.ApiAddr,
giolekvaf58a7692021-12-15 18:05:39 +0400177 message,
178 signature,
179 }
180 var data bytes.Buffer
181 if err := json.NewEncoder(&data).Encode(c); err != nil {
182 return nil, err
183 }
184 qr, err := qrcode.Encode(data.String(), qrcode.Medium, 1024)
185 if err != nil {
186 return nil, err
187 }
188 img, err := png.Decode(bytes.NewReader(qr))
189 if err != nil {
190 return nil, err
191 }
192 return img, nil
193}
194
giolekva8d6a0ca2021-12-19 17:42:25 +0400195func (p *processor) JoinAndGetNetworkConfig(code []byte) error {
giolekvaf58a7692021-12-15 18:05:39 +0400196 var invite qrCodeData
197 if err := json.NewDecoder(bytes.NewReader(code)).Decode(&invite); err != nil {
giolekva1026d2d2021-12-19 19:09:15 +0400198 return err
giolekvaf58a7692021-12-15 18:05:39 +0400199 }
giolekva1026d2d2021-12-19 19:09:15 +0400200 hostname, err := p.app.GetHostname()
201 if err != nil {
202 return err
203 }
204 hostname = strings.ToLower(strings.ReplaceAll(hostname, " ", "-"))
205 fmt.Printf("------ %s\n", hostname)
206 network, err := p.vc.Join(invite.VPNApiAddr, hostname, invite.Message, invite.Signature)
giolekvaf58a7692021-12-15 18:05:39 +0400207 if err != nil {
giolekva8d6a0ca2021-12-19 17:42:25 +0400208 return err
giolekvaf58a7692021-12-15 18:05:39 +0400209 }
giolekva8d6a0ca2021-12-19 17:42:25 +0400210 if err := p.st.Store(Config{invite.VPNApiAddr, network}); err != nil {
211 return err
giolekva52da88a2021-12-17 18:08:25 +0400212 }
giolekva8d6a0ca2021-12-19 17:42:25 +0400213 p.onConfigCh <- struct{}{}
214 return nil
giolekvaf58a7692021-12-15 18:05:39 +0400215}
216
giolekva313ee2b2021-12-15 15:17:29 +0400217func main() {
218 flag.Parse()
giolekvaf58a7692021-12-15 18:05:39 +0400219 p = newProcessor()
giolekva313ee2b2021-12-15 15:17:29 +0400220 go func() {
giolekvaf58a7692021-12-15 18:05:39 +0400221 if err := p.run(); err != nil {
giolekva313ee2b2021-12-15 15:17:29 +0400222 panic(err)
223 }
224 }()
225 app.Main()
226}