github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/plugins/webpush/plugin.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2016-2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package webpush 20 21 import ( 22 "context" 23 "embed" 24 "encoding/json" 25 "strconv" 26 "strings" 27 28 "github.com/e154/smart-home/common/encryptor" 29 "github.com/e154/smart-home/common/logger" 30 m "github.com/e154/smart-home/models" 31 "github.com/e154/smart-home/plugins/notify" 32 "github.com/e154/smart-home/plugins/notify/common" 33 "github.com/e154/smart-home/system/supervisor" 34 ) 35 36 var ( 37 log = logger.MustGetLogger("plugins.webpush") 38 ) 39 40 var _ supervisor.Pluggable = (*plugin)(nil) 41 42 //go:embed Readme.md 43 //go:embed Readme.ru.md 44 var F embed.FS 45 46 func init() { 47 supervisor.RegisterPlugin(Name, New) 48 } 49 50 type plugin struct { 51 *supervisor.Plugin 52 VAPIDPublicKey, VAPIDPrivateKey string 53 notify *notify.Notify 54 } 55 56 // New ... 57 func New() supervisor.Pluggable { 58 p := &plugin{ 59 Plugin: supervisor.NewPlugin(), 60 } 61 p.F = F 62 return p 63 } 64 65 // Load ... 66 func (p *plugin) Load(ctx context.Context, service supervisor.Service) (err error) { 67 if err = p.Plugin.Load(ctx, service, nil); err != nil { 68 return 69 } 70 71 p.notify = notify.NewNotify(service.Adaptors()) 72 p.notify.Start() 73 74 // load settings 75 var settings m.Attributes 76 settings, err = p.LoadSettings(p) 77 if err != nil { 78 log.Warn(err.Error()) 79 settings = NewSettings() 80 } 81 82 if settings == nil { 83 settings = NewSettings() 84 } 85 if settings[AttrPrivateKey].Decrypt() == "" || settings[AttrPublicKey].Decrypt() == "" { 86 log.Info(`Keys not found, will be generate`) 87 88 if p.VAPIDPrivateKey, p.VAPIDPublicKey, err = GenerateVAPIDKeys(); err != nil { 89 return 90 } 91 92 settings[AttrPrivateKey].Value, _ = encryptor.Encrypt(p.VAPIDPrivateKey) 93 settings[AttrPublicKey].Value, _ = encryptor.Encrypt(p.VAPIDPublicKey) 94 95 var model *m.Plugin 96 model, _ = p.Service.Adaptors().Plugin.GetByName(context.Background(), Name) 97 model.Settings = settings.Serialize() 98 _ = p.Service.Adaptors().Plugin.Update(context.Background(), model) 99 } else { 100 p.VAPIDPrivateKey = settings[AttrPrivateKey].Decrypt() 101 p.VAPIDPublicKey = settings[AttrPublicKey].Decrypt() 102 } 103 104 log.Infof(`Used public key: "%s"`, p.VAPIDPublicKey) 105 106 _ = p.Service.EventBus().Subscribe(TopicPluginWebpush, p.eventHandler) 107 _ = p.Service.EventBus().Subscribe(notify.TopicNotify, p.eventHandler, false) 108 109 return 110 } 111 112 // Unload ... 113 func (p *plugin) Unload(ctx context.Context) (err error) { 114 if err = p.Plugin.Unload(ctx); err != nil { 115 return 116 } 117 118 p.notify.Shutdown() 119 120 _ = p.Service.EventBus().Unsubscribe(notify.TopicNotify, p.eventHandler) 121 _ = p.Service.EventBus().Unsubscribe(TopicPluginWebpush, p.eventHandler) 122 123 return nil 124 } 125 126 // Name ... 127 func (p *plugin) Name() string { 128 return Name 129 } 130 131 // Type ... 132 func (p *plugin) Type() supervisor.PluginType { 133 return supervisor.PluginInstallable 134 } 135 136 // Depends ... 137 func (p *plugin) Depends() []string { 138 return []string{notify.Name} 139 } 140 141 // Version ... 142 func (p *plugin) Version() string { 143 return Version 144 } 145 146 // Options ... 147 func (p *plugin) Options() m.PluginOptions { 148 return m.PluginOptions{ 149 Setts: NewSettings(), 150 } 151 } 152 153 // Save ... 154 func (p *plugin) Save(msg common.Message) (addresses []string, message *m.Message) { 155 message = &m.Message{ 156 Type: Name, 157 Attributes: msg.Attributes, 158 } 159 var err error 160 if message.Id, err = p.Service.Adaptors().Message.Add(context.Background(), message); err != nil { 161 log.Error(err.Error()) 162 } 163 164 attr := NewMessageParams() 165 _, _ = attr.Deserialize(message.Attributes) 166 167 addresses = strings.Split(attr[AttrUserIDS].String(), ",") 168 169 return 170 } 171 172 // Send ... 173 func (p *plugin) Send(address string, message *m.Message) (err error) { 174 175 attr := NewMessageParams() 176 if _, err = attr.Deserialize(message.Attributes); err != nil { 177 log.Error(err.Error()) 178 return 179 } 180 181 userId, _ := strconv.ParseInt(address, 0, 64) 182 var userDevices []*m.UserDevice 183 if userId != 0 { 184 if userDevices, err = p.Service.Adaptors().UserDevice.GetByUserId(context.Background(), userId); err != nil { 185 return 186 } 187 } else { 188 const perPage int64 = 500 189 var page int64 = 0 190 LOOP: 191 var list []*m.UserDevice 192 if list, _, err = p.Service.Adaptors().UserDevice.List(context.Background(), perPage, page*perPage, "", ""); err != nil { 193 return 194 } 195 if len(list) > 0 { 196 userDevices = append(userDevices, list...) 197 page++ 198 goto LOOP 199 } 200 } 201 go func() { 202 for _, device := range userDevices { 203 if err = p.sendPush(device, attr[AttrTitle].String(), attr[AttrBody].String()); err != nil { 204 log.Error(err.Error()) 205 } 206 } 207 }() 208 209 return 210 } 211 212 // MessageParams ... 213 func (p *plugin) MessageParams() m.Attributes { 214 return NewMessageParams() 215 } 216 217 func (p *plugin) sendPush(userDevice *m.UserDevice, msgTitle, msgBody string) (err error) { 218 219 msg := map[string]string{ 220 "title": msgTitle, 221 "body": msgBody, 222 } 223 224 message, _ := json.Marshal(msg) 225 226 var statusCode int 227 var responseBody []byte 228 statusCode, responseBody, err = SendNotification(message, userDevice.Subscription, &Options{ 229 Crawler: p.Service.Crawler(), 230 VAPIDPublicKey: p.VAPIDPublicKey, 231 VAPIDPrivateKey: p.VAPIDPrivateKey, 232 TTL: 30, 233 }) 234 if err != nil { 235 return 236 } 237 238 if statusCode != 201 { 239 log.Warn(string(responseBody)) 240 if statusCode == 410 { 241 go func() { 242 _ = p.Service.Adaptors().UserDevice.Delete(context.Background(), userDevice.Id) 243 log.Infof("statusCode %d, remove user device %d", statusCode, userDevice.Id) 244 }() 245 } 246 return 247 } 248 249 log.Infof(`Send push, user: "%d", device: "%d", title: "%s"`, userDevice.UserId, userDevice.Id, msgTitle) 250 251 return 252 } 253 254 func (p *plugin) eventHandler(_ string, event interface{}) { 255 256 switch v := event.(type) { 257 case EventAddWebPushSubscription: 258 p.updateSubscribe(v) 259 case EventGetWebPushPublicKey: 260 p.sendPublicKey(v) 261 case EventGetUserDevices: 262 p.eventGetUserDevices(v) 263 case common.Message: 264 if v.Type == Name { 265 p.notify.SaveAndSend(v, p) 266 } 267 } 268 } 269 270 func (p *plugin) sendPublicKey(event EventGetWebPushPublicKey) { 271 p.Service.EventBus().Publish("system/dashboard", EventNewWebPushPublicKey{ 272 UserID: event.UserID, 273 SessionID: event.SessionID, 274 PublicKey: p.VAPIDPublicKey, 275 }) 276 } 277 278 func (p *plugin) updateSubscribe(event EventAddWebPushSubscription) { 279 280 if _, err := p.Service.Adaptors().UserDevice.Add(context.Background(), &m.UserDevice{ 281 UserId: event.UserID, 282 Subscription: event.Subscription, 283 }); err != nil { 284 return 285 } 286 287 log.Infof("user subscription updated, %d", event.UserID) 288 } 289 290 func (p *plugin) eventGetUserDevices(event EventGetUserDevices) { 291 292 devices, err := p.Service.Adaptors().UserDevice.GetByUserId(context.Background(), event.UserID) 293 if err != nil { 294 return 295 } 296 297 var subscriptions = make([]*m.Subscription, 0, len(devices)) 298 for _, device := range devices { 299 subscriptions = append(subscriptions, device.Subscription) 300 } 301 302 p.Service.EventBus().Publish(TopicPluginWebpush, EventUserDevices{ 303 UserID: event.UserID, 304 SessionID: event.SessionID, 305 Subscriptions: subscriptions, 306 }) 307 }