@@ -23,6 +23,7 @@ import (
23
23
"fmt"
24
24
"net/http"
25
25
"regexp"
26
+ "strconv"
26
27
"strings"
27
28
"time"
28
29
@@ -149,16 +150,33 @@ type Message struct {
149
150
150
151
// MarshalJSON marshals a Message into JSON (for internal use only).
151
152
func (m * Message ) MarshalJSON () ([]byte , error ) {
152
- // Create a new type to prevent infinite recursion.
153
+ // Create a new type to prevent infinite recursion. We use this technique whenever it is needed
154
+ // to customize how a subset of the fields in a struct should be serialized.
153
155
type messageInternal Message
154
- s := & struct {
156
+ temp := & struct {
155
157
BareTopic string `json:"topic,omitempty"`
156
158
* messageInternal
157
159
}{
158
160
BareTopic : strings .TrimPrefix (m .Topic , "/topics/" ),
159
161
messageInternal : (* messageInternal )(m ),
160
162
}
161
- return json .Marshal (s )
163
+ return json .Marshal (temp )
164
+ }
165
+
166
+ // UnmarshalJSON unmarshals a JSON string into a Message (for internal use only).
167
+ func (m * Message ) UnmarshalJSON (b []byte ) error {
168
+ type messageInternal Message
169
+ s := struct {
170
+ BareTopic string `json:"topic,omitempty"`
171
+ * messageInternal
172
+ }{
173
+ messageInternal : (* messageInternal )(m ),
174
+ }
175
+ if err := json .Unmarshal (b , & s ); err != nil {
176
+ return err
177
+ }
178
+ m .Topic = s .BareTopic
179
+ return nil
162
180
}
163
181
164
182
// Notification is the basic notification template to use across all platforms.
@@ -191,14 +209,48 @@ func (a *AndroidConfig) MarshalJSON() ([]byte, error) {
191
209
}
192
210
193
211
type androidInternal AndroidConfig
194
- s := & struct {
212
+ temp := & struct {
195
213
TTL string `json:"ttl,omitempty"`
196
214
* androidInternal
197
215
}{
198
216
TTL : ttl ,
199
217
androidInternal : (* androidInternal )(a ),
200
218
}
201
- return json .Marshal (s )
219
+ return json .Marshal (temp )
220
+ }
221
+
222
+ // UnmarshalJSON unmarshals a JSON string into an AndroidConfig (for internal use only).
223
+ func (a * AndroidConfig ) UnmarshalJSON (b []byte ) error {
224
+ type androidInternal AndroidConfig
225
+ temp := struct {
226
+ TTL string `json:"ttl,omitempty"`
227
+ * androidInternal
228
+ }{
229
+ androidInternal : (* androidInternal )(a ),
230
+ }
231
+ if err := json .Unmarshal (b , & temp ); err != nil {
232
+ return err
233
+ }
234
+ if temp .TTL != "" {
235
+ segments := strings .Split (strings .TrimSuffix (temp .TTL , "s" ), "." )
236
+ if len (segments ) != 1 && len (segments ) != 2 {
237
+ return fmt .Errorf ("incorrect number of segments in ttl: %q" , temp .TTL )
238
+ }
239
+ seconds , err := strconv .ParseInt (segments [0 ], 10 , 64 )
240
+ if err != nil {
241
+ return err
242
+ }
243
+ ttl := time .Duration (seconds ) * time .Second
244
+ if len (segments ) == 2 {
245
+ nanos , err := strconv .ParseInt (strings .TrimLeft (segments [1 ], "0" ), 10 , 64 )
246
+ if err != nil {
247
+ return err
248
+ }
249
+ ttl += time .Duration (nanos ) * time .Nanosecond
250
+ }
251
+ a .TTL = & ttl
252
+ }
253
+ return nil
202
254
}
203
255
204
256
// AndroidNotification is a notification to send to Android devices.
@@ -240,30 +292,30 @@ type WebpushNotificationAction struct {
240
292
// See https://developer.mozilla.org/en-US/docs/Web/API/notification/Notification for additional
241
293
// details.
242
294
type WebpushNotification struct {
243
- Actions []* WebpushNotificationAction
244
- Title string `json:"title,omitempty"` // if specified, overrides the Title field of the Notification type
245
- Body string `json:"body,omitempty"` // if specified, overrides the Body field of the Notification type
246
- Icon string `json:"icon,omitempty"`
247
- Badge string `json:"badge,omitempty"`
248
- Direction string `json:"dir,omitempty"` // one of 'ltr' or 'rtl'
249
- Data interface {} `json:"data,omitempty"`
250
- Image string `json:"image,omitempty"`
251
- Language string `json:"lang,omitempty"`
252
- Renotify bool `json:"renotify,omitempty"`
253
- RequireInteraction bool `json:"requireInteraction,omitempty"`
254
- Silent bool `json:"silent,omitempty"`
255
- Tag string `json:"tag,omitempty"`
256
- TimestampMillis * int64 `json:"timestamp,omitempty"`
257
- Vibrate []int `json:"vibrate,omitempty"`
295
+ Actions []* WebpushNotificationAction `json:"actions,omitempty"`
296
+ Title string `json:"title,omitempty"` // if specified, overrides the Title field of the Notification type
297
+ Body string `json:"body,omitempty"` // if specified, overrides the Body field of the Notification type
298
+ Icon string `json:"icon,omitempty"`
299
+ Badge string `json:"badge,omitempty"`
300
+ Direction string `json:"dir,omitempty"` // one of 'ltr' or 'rtl'
301
+ Data interface {} `json:"data,omitempty"`
302
+ Image string `json:"image,omitempty"`
303
+ Language string `json:"lang,omitempty"`
304
+ Renotify bool `json:"renotify,omitempty"`
305
+ RequireInteraction bool `json:"requireInteraction,omitempty"`
306
+ Silent bool `json:"silent,omitempty"`
307
+ Tag string `json:"tag,omitempty"`
308
+ TimestampMillis * int64 `json:"timestamp,omitempty"`
309
+ Vibrate []int `json:"vibrate,omitempty"`
258
310
CustomData map [string ]interface {}
259
311
}
260
312
261
- // WebpushFcmOptions Options for features provided by the FCM SDK for Web.
262
- type WebpushFcmOptions struct {
263
- Link string `json:"link,omitempty"`
264
- }
265
-
266
313
// standardFields creates a map containing all the fields except the custom data.
314
+ //
315
+ // We implement a standardFields function whenever we want to add custom and arbitrary
316
+ // fields to an object during its serialization. This helper function also comes in
317
+ // handy during validation of the message (to detect duplicate specifications of
318
+ // fields), and also during deserialization.
267
319
func (n * WebpushNotification ) standardFields () map [string ]interface {} {
268
320
m := make (map [string ]interface {})
269
321
addNonEmpty := func (key , value string ) {
@@ -311,6 +363,31 @@ func (n *WebpushNotification) MarshalJSON() ([]byte, error) {
311
363
return json .Marshal (m )
312
364
}
313
365
366
+ // UnmarshalJSON unmarshals a JSON string into a WebpushNotification (for internal use only).
367
+ func (n * WebpushNotification ) UnmarshalJSON (b []byte ) error {
368
+ type webpushNotificationInternal WebpushNotification
369
+ var temp = (* webpushNotificationInternal )(n )
370
+ if err := json .Unmarshal (b , temp ); err != nil {
371
+ return err
372
+ }
373
+ allFields := make (map [string ]interface {})
374
+ if err := json .Unmarshal (b , & allFields ); err != nil {
375
+ return err
376
+ }
377
+ for k := range n .standardFields () {
378
+ delete (allFields , k )
379
+ }
380
+ if len (allFields ) > 0 {
381
+ n .CustomData = allFields
382
+ }
383
+ return nil
384
+ }
385
+
386
+ // WebpushFcmOptions contains additional options for features provided by the FCM web SDK.
387
+ type WebpushFcmOptions struct {
388
+ Link string `json:"link,omitempty"`
389
+ }
390
+
314
391
// APNSConfig contains messaging options specific to the Apple Push Notification Service (APNS).
315
392
//
316
393
// See https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html
@@ -328,34 +405,59 @@ type APNSConfig struct {
328
405
// See https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html
329
406
// for a full list of supported payload fields.
330
407
type APNSPayload struct {
331
- Aps * Aps
332
- CustomData map [string ]interface {}
408
+ Aps * Aps `json:"aps,omitempty"`
409
+ CustomData map [string ]interface {} `json:"-"`
410
+ }
411
+
412
+ // standardFields creates a map containing all the fields except the custom data.
413
+ func (p * APNSPayload ) standardFields () map [string ]interface {} {
414
+ return map [string ]interface {}{"aps" : p .Aps }
333
415
}
334
416
335
417
// MarshalJSON marshals an APNSPayload into JSON (for internal use only).
336
418
func (p * APNSPayload ) MarshalJSON () ([]byte , error ) {
337
- m := map [ string ] interface {}{ "aps" : p . Aps }
419
+ m := p . standardFields ()
338
420
for k , v := range p .CustomData {
339
421
m [k ] = v
340
422
}
341
423
return json .Marshal (m )
342
424
}
343
425
426
+ // UnmarshalJSON unmarshals a JSON string into an APNSPayload (for internal use only).
427
+ func (p * APNSPayload ) UnmarshalJSON (b []byte ) error {
428
+ type apnsPayloadInternal APNSPayload
429
+ var temp = (* apnsPayloadInternal )(p )
430
+ if err := json .Unmarshal (b , temp ); err != nil {
431
+ return err
432
+ }
433
+ allFields := make (map [string ]interface {})
434
+ if err := json .Unmarshal (b , & allFields ); err != nil {
435
+ return err
436
+ }
437
+ for k := range p .standardFields () {
438
+ delete (allFields , k )
439
+ }
440
+ if len (allFields ) > 0 {
441
+ p .CustomData = allFields
442
+ }
443
+ return nil
444
+ }
445
+
344
446
// Aps represents the aps dictionary that may be included in an APNSPayload.
345
447
//
346
448
// Alert may be specified as a string (via the AlertString field), or as a struct (via the Alert
347
449
// field).
348
450
type Aps struct {
349
- AlertString string
350
- Alert * ApsAlert
351
- Badge * int
352
- Sound string
353
- CriticalSound * CriticalSound
354
- ContentAvailable bool
355
- MutableContent bool
356
- Category string
357
- ThreadID string
358
- CustomData map [string ]interface {}
451
+ AlertString string `json:"-"`
452
+ Alert * ApsAlert `json:"-"`
453
+ Badge * int `json:"badge,omitempty"`
454
+ Sound string `json:"-"`
455
+ CriticalSound * CriticalSound `json:"-"`
456
+ ContentAvailable bool `json:"-"`
457
+ MutableContent bool `json:"-"`
458
+ Category string `json:"category,omitempty"`
459
+ ThreadID string `json:"thread-id,omitempty"`
460
+ CustomData map [string ]interface {} `json:"-"`
359
461
}
360
462
361
463
// standardFields creates a map containing all the fields except the custom data.
@@ -398,26 +500,89 @@ func (a *Aps) MarshalJSON() ([]byte, error) {
398
500
return json .Marshal (m )
399
501
}
400
502
503
+ // UnmarshalJSON unmarshals a JSON string into an Aps (for internal use only).
504
+ func (a * Aps ) UnmarshalJSON (b []byte ) error {
505
+ type apsInternal Aps
506
+ temp := struct {
507
+ AlertObject * json.RawMessage `json:"alert,omitempty"`
508
+ SoundObject * json.RawMessage `json:"sound,omitempty"`
509
+ ContentAvailableInt int `json:"content-available,omitempty"`
510
+ MutableContentInt int `json:"mutable-content,omitempty"`
511
+ * apsInternal
512
+ }{
513
+ apsInternal : (* apsInternal )(a ),
514
+ }
515
+ if err := json .Unmarshal (b , & temp ); err != nil {
516
+ return err
517
+ }
518
+ a .ContentAvailable = (temp .ContentAvailableInt == 1 )
519
+ a .MutableContent = (temp .MutableContentInt == 1 )
520
+ if temp .AlertObject != nil {
521
+ if err := json .Unmarshal (* temp .AlertObject , & a .Alert ); err != nil {
522
+ a .Alert = nil
523
+ if err := json .Unmarshal (* temp .AlertObject , & a .AlertString ); err != nil {
524
+ return fmt .Errorf ("failed to unmarshal alert as a struct or a string: %v" , err )
525
+ }
526
+ }
527
+ }
528
+ if temp .SoundObject != nil {
529
+ if err := json .Unmarshal (* temp .SoundObject , & a .CriticalSound ); err != nil {
530
+ a .CriticalSound = nil
531
+ if err := json .Unmarshal (* temp .SoundObject , & a .Sound ); err != nil {
532
+ return fmt .Errorf ("failed to unmarshal sound as a struct or a string" )
533
+ }
534
+ }
535
+ }
536
+
537
+ allFields := make (map [string ]interface {})
538
+ if err := json .Unmarshal (b , & allFields ); err != nil {
539
+ return err
540
+ }
541
+ for k := range a .standardFields () {
542
+ delete (allFields , k )
543
+ }
544
+ if len (allFields ) > 0 {
545
+ a .CustomData = allFields
546
+ }
547
+ return nil
548
+ }
549
+
401
550
// CriticalSound is the sound payload that can be included in an Aps.
402
551
type CriticalSound struct {
403
- Critical bool
404
- Name string
405
- Volume float64
552
+ Critical bool `json:"-"`
553
+ Name string `json:"name,omitempty"`
554
+ Volume float64 `json:"volume,omitempty"`
406
555
}
407
556
408
557
// MarshalJSON marshals a CriticalSound into JSON (for internal use only).
409
558
func (cs * CriticalSound ) MarshalJSON () ([]byte , error ) {
410
- m := make (map [string ]interface {})
559
+ type criticalSoundInternal CriticalSound
560
+ temp := struct {
561
+ CriticalInt int `json:"critical,omitempty"`
562
+ * criticalSoundInternal
563
+ }{
564
+ criticalSoundInternal : (* criticalSoundInternal )(cs ),
565
+ }
411
566
if cs .Critical {
412
- m [ "critical" ] = 1
567
+ temp . CriticalInt = 1
413
568
}
414
- if cs .Name != "" {
415
- m ["name" ] = cs .Name
569
+ return json .Marshal (temp )
570
+ }
571
+
572
+ // UnmarshalJSON unmarshals a JSON string into a CriticalSound (for internal use only).
573
+ func (cs * CriticalSound ) UnmarshalJSON (b []byte ) error {
574
+ type criticalSoundInternal CriticalSound
575
+ temp := struct {
576
+ CriticalInt int `json:"critical,omitempty"`
577
+ * criticalSoundInternal
578
+ }{
579
+ criticalSoundInternal : (* criticalSoundInternal )(cs ),
416
580
}
417
- if cs . Volume != 0 {
418
- m [ "volume" ] = cs . Volume
581
+ if err := json . Unmarshal ( b , & temp ); err != nil {
582
+ return err
419
583
}
420
- return json .Marshal (m )
584
+ cs .Critical = (temp .CriticalInt == 1 )
585
+ return nil
421
586
}
422
587
423
588
// ApsAlert is the alert payload that can be included in an Aps.
0 commit comments