@@ -23,6 +23,7 @@ import (
2323 "fmt"
2424 "net/http"
2525 "regexp"
26+ "strconv"
2627 "strings"
2728 "time"
2829
@@ -149,16 +150,33 @@ type Message struct {
149150
150151// MarshalJSON marshals a Message into JSON (for internal use only).
151152func (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.
153155 type messageInternal Message
154- s := & struct {
156+ temp := & struct {
155157 BareTopic string `json:"topic,omitempty"`
156158 * messageInternal
157159 }{
158160 BareTopic : strings .TrimPrefix (m .Topic , "/topics/" ),
159161 messageInternal : (* messageInternal )(m ),
160162 }
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
162180}
163181
164182// Notification is the basic notification template to use across all platforms.
@@ -191,14 +209,48 @@ func (a *AndroidConfig) MarshalJSON() ([]byte, error) {
191209 }
192210
193211 type androidInternal AndroidConfig
194- s := & struct {
212+ temp := & struct {
195213 TTL string `json:"ttl,omitempty"`
196214 * androidInternal
197215 }{
198216 TTL : ttl ,
199217 androidInternal : (* androidInternal )(a ),
200218 }
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
202254}
203255
204256// AndroidNotification is a notification to send to Android devices.
@@ -240,30 +292,30 @@ type WebpushNotificationAction struct {
240292// See https://developer.mozilla.org/en-US/docs/Web/API/notification/Notification for additional
241293// details.
242294type 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"`
258310 CustomData map [string ]interface {}
259311}
260312
261- // WebpushFcmOptions Options for features provided by the FCM SDK for Web.
262- type WebpushFcmOptions struct {
263- Link string `json:"link,omitempty"`
264- }
265-
266313// 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.
267319func (n * WebpushNotification ) standardFields () map [string ]interface {} {
268320 m := make (map [string ]interface {})
269321 addNonEmpty := func (key , value string ) {
@@ -311,6 +363,31 @@ func (n *WebpushNotification) MarshalJSON() ([]byte, error) {
311363 return json .Marshal (m )
312364}
313365
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+
314391// APNSConfig contains messaging options specific to the Apple Push Notification Service (APNS).
315392//
316393// See https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html
@@ -328,34 +405,59 @@ type APNSConfig struct {
328405// See https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html
329406// for a full list of supported payload fields.
330407type 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 }
333415}
334416
335417// MarshalJSON marshals an APNSPayload into JSON (for internal use only).
336418func (p * APNSPayload ) MarshalJSON () ([]byte , error ) {
337- m := map [ string ] interface {}{ "aps" : p . Aps }
419+ m := p . standardFields ()
338420 for k , v := range p .CustomData {
339421 m [k ] = v
340422 }
341423 return json .Marshal (m )
342424}
343425
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+
344446// Aps represents the aps dictionary that may be included in an APNSPayload.
345447//
346448// Alert may be specified as a string (via the AlertString field), or as a struct (via the Alert
347449// field).
348450type 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:"-"`
359461}
360462
361463// standardFields creates a map containing all the fields except the custom data.
@@ -398,26 +500,89 @@ func (a *Aps) MarshalJSON() ([]byte, error) {
398500 return json .Marshal (m )
399501}
400502
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+
401550// CriticalSound is the sound payload that can be included in an Aps.
402551type 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"`
406555}
407556
408557// MarshalJSON marshals a CriticalSound into JSON (for internal use only).
409558func (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+ }
411566 if cs .Critical {
412- m [ "critical" ] = 1
567+ temp . CriticalInt = 1
413568 }
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 ),
416580 }
417- if cs . Volume != 0 {
418- m [ "volume" ] = cs . Volume
581+ if err := json . Unmarshal ( b , & temp ); err != nil {
582+ return err
419583 }
420- return json .Marshal (m )
584+ cs .Critical = (temp .CriticalInt == 1 )
585+ return nil
421586}
422587
423588// ApsAlert is the alert payload that can be included in an Aps.
0 commit comments