This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

1package notella 2 3import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "os" 9 "strings" 10 "time" 11 12 firebase "firebase.google.com/go/v4" 13 "firebase.google.com/go/v4/messaging" 14 ll "github.com/gwennlbh/label-logger-go" 15 "golang.org/x/oauth2" 16 "golang.org/x/oauth2/google" 17 "google.golang.org/api/option" 18) 19 20var firebaseClient *firebase.App 21var firebaseCtx = context.Background() 22 23const MaxTokensPerRequest = 490 24 25func (msg Message) SendToFirebase(groupId string, subs []Subscription) error { 26 if firebaseClient == nil || !config.HasValidFirebaseServiceAccount() { 27 return nil 28 } 29 30 fcm, err := firebaseClient.Messaging(firebaseCtx) 31 if err != nil { 32 return fmt.Errorf("while initializing FCM client: %w", err) 33 } 34 35 message := msg.FirebaseMessage(groupId) 36 tokens := make([]string, len(subs)) 37 for i, sub := range subs { 38 tokens[i] = sub.FirebaseToken() 39 } 40 41 for _, tokensChunk := range chunkBy(tokens, MaxTokensPerRequest) { 42 go func(tokens []string) { 43 if len(tokens) == 0 { 44 return 45 } 46 message.Tokens = tokens 47 if config.DryRunMode { 48 ll.Warn("dry run mode enabled, not sending FCM message to %d tokens", len(tokens)) 49 return 50 } 51 resp, err := fcm.SendEachForMulticast(firebaseCtx, &message) 52 if err != nil { 53 ll.ErrorDisplay("while sending FCM message", err) 54 } else if resp.FailureCount > 0 { 55 fcmErrors := make([]string, 0, resp.FailureCount) 56 for i, result := range resp.Responses { 57 if !result.Success { 58 if result.Error.Error() == "Requested entity was not found." { 59 if sub, found := FindSubscriptionByNativeToken(tokens[i], subs); found { 60 ll.Log("Deleting", "yellow", "invalid native subscription %s", tokens[i]) 61 sub.Destroy() 62 } 63 } else { 64 fcmErrors = append(fcmErrors, fmt.Sprintf("%s: %s", tokens[i], result.Error)) 65 } 66 } 67 } 68 if len(fcmErrors) > 0 { 69 ll.ErrorDisplay( 70 "some FCM messages failed for %d tokens", 71 fmt.Errorf("- %s", strings.Join(fcmErrors, "\n- ")), 72 resp.FailureCount, 73 ) 74 } 75 } 76 }(tokensChunk) 77 } 78 79 return nil 80} 81 82func (msg Message) FirebaseMessage(groupId string) messaging.MulticastMessage { 83 clickAction := "" 84 if len(msg.Actions) > 0 { 85 clickAction = msg.Actions[0].Label 86 } 87 return messaging.MulticastMessage{ 88 Data: map[string]string{ 89 "original": msg.JSONString(), 90 }, 91 Android: &messaging.AndroidConfig{ 92 RestrictedPackageName: config.AppPackageId, 93 Notification: &messaging.AndroidNotification{ 94 VibrateTimingMillis: []int64{}, // TODO 95 EventTimestamp: nil, // TODO 96 ClickAction: clickAction, 97 }, 98 }, 99 Notification: &messaging.Notification{ 100 Title: msg.Title, 101 Body: msg.Body, 102 ImageURL: msg.Image, 103 }, 104 } 105} 106 107type firebaseServiceAccount struct { 108 Type string `json:"type"` 109 ProjectId string `json:"project_id"` 110 PrivateKeyId string `json:"private_key_id"` 111 PrivateKey string `json:"private_key"` 112 ClientEmail string `json:"client_email"` 113 ClientId string `json:"client_id"` 114 AuthUri string `json:"auth_uri"` 115 TokenUri string `json:"token_uri"` 116 AuthProviderX509CertUrl string `json:"auth_provider_x509_cert_url"` 117 ClientX509CertUrl string `json:"client_x509_cert_url"` 118 UniverseDomain string `json:"universe_domain"` 119} 120 121func setupFirebaseClient() (err error) { 122 httpClient := http.DefaultClient 123 if os.Getenv("DEBUG") == "1" { 124 httpClient = &http.Client{ 125 Transport: debugTransport{t: http.DefaultTransport}, 126 } 127 } 128 129 ctxWithClient := context.WithValue(firebaseCtx, oauth2.HTTPClient, httpClient) 130 creds, err := google.CredentialsFromJSON(ctxWithClient, []byte(config.FirebaseServiceAccount), "https://www.googleapis.com/auth/firebase.messaging") 131 if err != nil { 132 return fmt.Errorf("while setting credentials: %w", err) 133 } 134 135 client := &http.Client{ 136 Transport: &oauth2.Transport{ 137 Source: creds.TokenSource, 138 Base: httpClient.Transport, 139 }, 140 Timeout: 10 * time.Second, 141 } 142 143 firebaseClient, err = firebase.NewApp(firebaseCtx, nil, 144 option.WithCredentialsJSON([]byte(config.FirebaseServiceAccount)), 145 option.WithHTTPClient(client), 146 ) 147 return 148} 149 150func (config Configuration) HasValidFirebaseServiceAccount() bool { 151 var serviceAccount firebaseServiceAccount 152 err := json.Unmarshal([]byte(config.FirebaseServiceAccount), &serviceAccount) 153 if err != nil { 154 return false 155 } 156 if serviceAccount.Type != "service_account" { 157 return false 158 } 159 160 if err = setupFirebaseClient(); err != nil { 161 return false 162 } 163 164 return true 165} 166 167func (sub Subscription) FirebaseToken() string { 168 return strings.TrimPrefix(strings.TrimPrefix(sub.Webpush.Endpoint, "apns://"), "firebase://") 169} 170 171func (sub Subscription) IsNative() bool { 172 return !sub.IsWebpush() 173}