@@ -48,6 +48,8 @@ const (
48
48
serverUnavailable = "server-unavailable"
49
49
tooManyTopics = "too-many-topics"
50
50
unknownError = "unknown-error"
51
+
52
+ rfc3339Zulu = "2006-01-02T15:04:05.000000000Z"
51
53
)
52
54
53
55
var (
@@ -176,13 +178,7 @@ type AndroidConfig struct {
176
178
func (a * AndroidConfig ) MarshalJSON () ([]byte , error ) {
177
179
var ttl string
178
180
if a .TTL != nil {
179
- seconds := int64 (* a .TTL / time .Second )
180
- nanos := int64 ((* a .TTL - time .Duration (seconds )* time .Second ) / time .Nanosecond )
181
- if nanos > 0 {
182
- ttl = fmt .Sprintf ("%d.%09ds" , seconds , nanos )
183
- } else {
184
- ttl = fmt .Sprintf ("%ds" , seconds )
185
- }
181
+ ttl = durationToString (* a .TTL )
186
182
}
187
183
188
184
type androidInternal AndroidConfig
@@ -209,42 +205,340 @@ func (a *AndroidConfig) UnmarshalJSON(b []byte) error {
209
205
return err
210
206
}
211
207
if temp .TTL != "" {
212
- segments := strings .Split (strings .TrimSuffix (temp .TTL , "s" ), "." )
213
- if len (segments ) != 1 && len (segments ) != 2 {
214
- return fmt .Errorf ("incorrect number of segments in ttl: %q" , temp .TTL )
215
- }
216
- seconds , err := strconv .ParseInt (segments [0 ], 10 , 64 )
208
+ ttl , err := stringToDuration (temp .TTL )
217
209
if err != nil {
218
210
return err
219
211
}
220
- ttl := time .Duration (seconds ) * time .Second
221
- if len (segments ) == 2 {
222
- nanos , err := strconv .ParseInt (strings .TrimLeft (segments [1 ], "0" ), 10 , 64 )
223
- if err != nil {
224
- return err
225
- }
226
- ttl += time .Duration (nanos ) * time .Nanosecond
227
- }
228
212
a .TTL = & ttl
229
213
}
230
214
return nil
231
215
}
232
216
233
217
// AndroidNotification is a notification to send to Android devices.
234
218
type AndroidNotification struct {
235
- Title string `json:"title,omitempty"` // if specified, overrides the Title field of the Notification type
236
- Body string `json:"body,omitempty"` // if specified, overrides the Body field of the Notification type
237
- Icon string `json:"icon,omitempty"`
238
- Color string `json:"color,omitempty"` // notification color in #RRGGBB format
239
- Sound string `json:"sound,omitempty"`
240
- Tag string `json:"tag,omitempty"`
241
- ClickAction string `json:"click_action,omitempty"`
242
- BodyLocKey string `json:"body_loc_key,omitempty"`
243
- BodyLocArgs []string `json:"body_loc_args,omitempty"`
244
- TitleLocKey string `json:"title_loc_key,omitempty"`
245
- TitleLocArgs []string `json:"title_loc_args,omitempty"`
246
- ChannelID string `json:"channel_id,omitempty"`
247
- ImageURL string `json:"image,omitempty"`
219
+ Title string `json:"title,omitempty"` // if specified, overrides the Title field of the Notification type
220
+ Body string `json:"body,omitempty"` // if specified, overrides the Body field of the Notification type
221
+ Icon string `json:"icon,omitempty"`
222
+ Color string `json:"color,omitempty"` // notification color in #RRGGBB format
223
+ Sound string `json:"sound,omitempty"`
224
+ Tag string `json:"tag,omitempty"`
225
+ ClickAction string `json:"click_action,omitempty"`
226
+ BodyLocKey string `json:"body_loc_key,omitempty"`
227
+ BodyLocArgs []string `json:"body_loc_args,omitempty"`
228
+ TitleLocKey string `json:"title_loc_key,omitempty"`
229
+ TitleLocArgs []string `json:"title_loc_args,omitempty"`
230
+ ChannelID string `json:"channel_id,omitempty"`
231
+ ImageURL string `json:"image,omitempty"`
232
+ Ticker string `json:"ticker,omitempty"`
233
+ Sticky bool `json:"sticky,omitempty"`
234
+ EventTimestamp * time.Time `json:"-"`
235
+ LocalOnly bool `json:"local_only,omitempty"`
236
+ Priority AndroidNotificationPriority `json:"-"`
237
+ VibrateTimingMillis []int64 `json:"-"`
238
+ DefaultVibrateTimings bool `json:"default_vibrate_timings,omitempty"`
239
+ DefaultSound bool `json:"default_sound,omitempty"`
240
+ LightSettings * LightSettings `json:"light_settings,omitempty"`
241
+ DefaultLightSettings bool `json:"default_light_settings,omitempty"`
242
+ Visibility AndroidNotificationVisibility `json:"-"`
243
+ NotificationCount * int `json:"notification_count,omitempty"`
244
+ }
245
+
246
+ // MarshalJSON marshals an AndroidNotification into JSON (for internal use only).
247
+ func (a * AndroidNotification ) MarshalJSON () ([]byte , error ) {
248
+ var priority string
249
+ if a .Priority != priorityUnspecified {
250
+ priorities := map [AndroidNotificationPriority ]string {
251
+ PriorityMin : "PRIORITY_MIN" ,
252
+ PriorityLow : "PRIORITY_LOW" ,
253
+ PriorityDefault : "PRIORITY_DEFAULT" ,
254
+ PriorityHigh : "PRIORITY_HIGH" ,
255
+ PriorityMax : "PRIORITY_MAX" ,
256
+ }
257
+ priority , _ = priorities [a .Priority ]
258
+ }
259
+
260
+ var visibility string
261
+ if a .Visibility != visibilityUnspecified {
262
+ visibilities := map [AndroidNotificationVisibility ]string {
263
+ VisibilityPrivate : "PRIVATE" ,
264
+ VisibilityPublic : "PUBLIC" ,
265
+ VisibilitySecret : "SECRET" ,
266
+ }
267
+ visibility , _ = visibilities [a .Visibility ]
268
+ }
269
+
270
+ var timestamp string
271
+ if a .EventTimestamp != nil {
272
+ timestamp = a .EventTimestamp .UTC ().Format (rfc3339Zulu )
273
+ }
274
+
275
+ var vibTimings []string
276
+ for _ , t := range a .VibrateTimingMillis {
277
+ vibTimings = append (vibTimings , durationToString (time .Duration (t )* time .Millisecond ))
278
+ }
279
+
280
+ type androidInternal AndroidNotification
281
+ temp := & struct {
282
+ EventTimestamp string `json:"event_time,omitempty"`
283
+ Priority string `json:"notification_priority,omitempty"`
284
+ Visibility string `json:"visibility,omitempty"`
285
+ VibrateTimings []string `json:"vibrate_timings,omitempty"`
286
+ * androidInternal
287
+ }{
288
+ EventTimestamp : timestamp ,
289
+ Priority : priority ,
290
+ Visibility : visibility ,
291
+ VibrateTimings : vibTimings ,
292
+ androidInternal : (* androidInternal )(a ),
293
+ }
294
+ return json .Marshal (temp )
295
+ }
296
+
297
+ // UnmarshalJSON unmarshals a JSON string into an AndroidNotification (for internal use only).
298
+ func (a * AndroidNotification ) UnmarshalJSON (b []byte ) error {
299
+ type androidInternal AndroidNotification
300
+ temp := struct {
301
+ EventTimestamp string `json:"event_time,omitempty"`
302
+ Priority string `json:"notification_priority,omitempty"`
303
+ Visibility string `json:"visibility,omitempty"`
304
+ VibrateTimings []string `json:"vibrate_timings,omitempty"`
305
+ * androidInternal
306
+ }{
307
+ androidInternal : (* androidInternal )(a ),
308
+ }
309
+ if err := json .Unmarshal (b , & temp ); err != nil {
310
+ return err
311
+ }
312
+
313
+ if temp .Priority != "" {
314
+ priorities := map [string ]AndroidNotificationPriority {
315
+ "PRIORITY_MIN" : PriorityMin ,
316
+ "PRIORITY_LOW" : PriorityLow ,
317
+ "PRIORITY_DEFUALT" : PriorityDefault ,
318
+ "PRIORITY_HIGH" : PriorityHigh ,
319
+ "PRIORITY_MAX" : PriorityMax ,
320
+ }
321
+ if prio , ok := priorities [temp .Priority ]; ok {
322
+ a .Priority = prio
323
+ } else {
324
+ return fmt .Errorf ("unknown priority value: %q" , temp .Priority )
325
+ }
326
+ }
327
+
328
+ if temp .Visibility != "" {
329
+ visibilities := map [string ]AndroidNotificationVisibility {
330
+ "PRIVATE" : VisibilityPrivate ,
331
+ "PUBLIC" : VisibilityPublic ,
332
+ "SECRET" : VisibilitySecret ,
333
+ }
334
+ if vis , ok := visibilities [temp .Visibility ]; ok {
335
+ a .Visibility = vis
336
+ } else {
337
+ return fmt .Errorf ("unknown visibility value: %q" , temp .Visibility )
338
+ }
339
+ }
340
+
341
+ if temp .EventTimestamp != "" {
342
+ ts , err := time .Parse (rfc3339Zulu , temp .EventTimestamp )
343
+ if err != nil {
344
+ return err
345
+ }
346
+
347
+ a .EventTimestamp = & ts
348
+ }
349
+
350
+ var vibTimings []int64
351
+ for _ , t := range temp .VibrateTimings {
352
+ vibTime , err := stringToDuration (t )
353
+ if err != nil {
354
+ return err
355
+ }
356
+
357
+ millis := int64 (vibTime / time .Millisecond )
358
+ vibTimings = append (vibTimings , millis )
359
+ }
360
+ a .VibrateTimingMillis = vibTimings
361
+ return nil
362
+ }
363
+
364
+ // AndroidNotificationPriority represents the priority levels of a notification.
365
+ type AndroidNotificationPriority int
366
+
367
+ const (
368
+ priorityUnspecified AndroidNotificationPriority = iota
369
+
370
+ // PriorityMin is the lowest notification priority. Notifications with this priority might not
371
+ // be shown to the user except under special circumstances, such as detailed notification logs.
372
+ PriorityMin
373
+
374
+ // PriorityLow is a lower notification priority. The UI may choose to show the notifications
375
+ // smaller, or at a different position in the list, compared with notifications with PriorityDefault.
376
+ PriorityLow
377
+
378
+ // PriorityDefault is the default notification priority. If the application does not prioritize
379
+ // its own notifications, use this value for all notifications.
380
+ PriorityDefault
381
+
382
+ // PriorityHigh is a higher notification priority. Use this for more important
383
+ // notifications or alerts. The UI may choose to show these notifications larger, or at a
384
+ // different position in the notification lists, compared with notifications with PriorityDefault.
385
+ PriorityHigh
386
+
387
+ // PriorityMax is the highest notification priority. Use this for the application's most
388
+ // important items that require the user's prompt attention or input.
389
+ PriorityMax
390
+ )
391
+
392
+ // AndroidNotificationVisibility represents the different visibility levels of a notification.
393
+ type AndroidNotificationVisibility int
394
+
395
+ const (
396
+ visibilityUnspecified AndroidNotificationVisibility = iota
397
+
398
+ // VisibilityPrivate shows this notification on all lockscreens, but conceal sensitive or
399
+ // private information on secure lockscreens.
400
+ VisibilityPrivate
401
+
402
+ // VisibilityPublic shows this notification in its entirety on all lockscreens.
403
+ VisibilityPublic
404
+
405
+ // VisibilitySecret does not reveal any part of this notification on a secure lockscreen.
406
+ VisibilitySecret
407
+ )
408
+
409
+ // LightSettings to control notification LED.
410
+ type LightSettings struct {
411
+ Color string
412
+ LightOnDurationMillis int64
413
+ LightOffDurationMillis int64
414
+ }
415
+
416
+ // MarshalJSON marshals an LightSettings into JSON (for internal use only).
417
+ func (l * LightSettings ) MarshalJSON () ([]byte , error ) {
418
+ clr , err := newColor (l .Color )
419
+ if err != nil {
420
+ return nil , err
421
+ }
422
+
423
+ temp := struct {
424
+ Color * color `json:"color"`
425
+ LightOnDuration string `json:"light_on_duration"`
426
+ LightOffDuration string `json:"light_off_duration"`
427
+ }{
428
+ Color : clr ,
429
+ LightOnDuration : durationToString (time .Duration (l .LightOnDurationMillis ) * time .Millisecond ),
430
+ LightOffDuration : durationToString (time .Duration (l .LightOffDurationMillis ) * time .Millisecond ),
431
+ }
432
+ return json .Marshal (temp )
433
+ }
434
+
435
+ // UnmarshalJSON unmarshals a JSON string into an LightSettings (for internal use only).
436
+ func (l * LightSettings ) UnmarshalJSON (b []byte ) error {
437
+ temp := struct {
438
+ Color * color `json:"color"`
439
+ LightOnDuration string `json:"light_on_duration"`
440
+ LightOffDuration string `json:"light_off_duration"`
441
+ }{}
442
+ if err := json .Unmarshal (b , & temp ); err != nil {
443
+ return err
444
+ }
445
+
446
+ on , err := stringToDuration (temp .LightOnDuration )
447
+ if err != nil {
448
+ return err
449
+ }
450
+
451
+ off , err := stringToDuration (temp .LightOffDuration )
452
+ if err != nil {
453
+ return err
454
+ }
455
+
456
+ l .Color = temp .Color .toString ()
457
+ l .LightOnDurationMillis = int64 (on / time .Millisecond )
458
+ l .LightOffDurationMillis = int64 (off / time .Millisecond )
459
+ return nil
460
+ }
461
+
462
+ func durationToString (ms time.Duration ) string {
463
+ seconds := int64 (ms / time .Second )
464
+ nanos := int64 ((ms - time .Duration (seconds )* time .Second ) / time .Nanosecond )
465
+ if nanos > 0 {
466
+ return fmt .Sprintf ("%d.%09ds" , seconds , nanos )
467
+ }
468
+ return fmt .Sprintf ("%ds" , seconds )
469
+ }
470
+
471
+ func stringToDuration (s string ) (time.Duration , error ) {
472
+ segments := strings .Split (strings .TrimSuffix (s , "s" ), "." )
473
+ if len (segments ) != 1 && len (segments ) != 2 {
474
+ return 0 , fmt .Errorf ("incorrect number of segments in ttl: %q" , s )
475
+ }
476
+
477
+ seconds , err := strconv .ParseInt (segments [0 ], 10 , 64 )
478
+ if err != nil {
479
+ return 0 , fmt .Errorf ("failed to parse %s: %v" , s , err )
480
+ }
481
+
482
+ ttl := time .Duration (seconds ) * time .Second
483
+ if len (segments ) == 2 {
484
+ nanos , err := strconv .ParseInt (strings .TrimLeft (segments [1 ], "0" ), 10 , 64 )
485
+ if err != nil {
486
+ return 0 , fmt .Errorf ("failed to parse %s: %v" , s , err )
487
+ }
488
+ ttl += time .Duration (nanos ) * time .Nanosecond
489
+ }
490
+
491
+ return ttl , nil
492
+ }
493
+
494
+ type color struct {
495
+ Red float64 `json:"red"`
496
+ Green float64 `json:"green"`
497
+ Blue float64 `json:"blue"`
498
+ Alpha float64 `json:"alpha"`
499
+ }
500
+
501
+ func newColor (clr string ) (* color , error ) {
502
+ red , err := strconv .ParseInt (clr [1 :3 ], 16 , 32 )
503
+ if err != nil {
504
+ return nil , fmt .Errorf ("failed to parse %s: %v" , clr , err )
505
+ }
506
+
507
+ green , err := strconv .ParseInt (clr [3 :5 ], 16 , 32 )
508
+ if err != nil {
509
+ return nil , fmt .Errorf ("failed to parse %s: %v" , clr , err )
510
+ }
511
+
512
+ blue , err := strconv .ParseInt (clr [5 :7 ], 16 , 32 )
513
+ if err != nil {
514
+ return nil , fmt .Errorf ("failed to parse %s: %v" , clr , err )
515
+ }
516
+
517
+ alpha := int64 (255 )
518
+ if len (clr ) == 9 {
519
+ alpha , err = strconv .ParseInt (clr [7 :9 ], 16 , 32 )
520
+ if err != nil {
521
+ return nil , fmt .Errorf ("failed to parse %s: %v" , clr , err )
522
+ }
523
+ }
524
+
525
+ return & color {
526
+ Red : float64 (red ) / 255.0 ,
527
+ Green : float64 (green ) / 255.0 ,
528
+ Blue : float64 (blue ) / 255.0 ,
529
+ Alpha : float64 (alpha ) / 255.0 ,
530
+ }, nil
531
+ }
532
+
533
+ func (c * color ) toString () string {
534
+ red := int (c .Red * 255.0 )
535
+ green := int (c .Green * 255.0 )
536
+ blue := int (c .Blue * 255.0 )
537
+ alpha := int (c .Alpha * 255.0 )
538
+ if alpha == 255 {
539
+ return fmt .Sprintf ("#%X%X%X" , red , green , blue )
540
+ }
541
+ return fmt .Sprintf ("#%X%X%X%X" , red , green , blue , alpha )
248
542
}
249
543
250
544
// AndroidFCMOptions contains additional options for features provided by the FCM Android SDK.
0 commit comments