feat: complete IM P0 and P1 reliability work
This commit is contained in:
@@ -17,6 +17,13 @@ This adapter requires you to obtain your own credentials from Goole Firebase. If
|
||||
Remove the other entry. I.e. if you have updated `"credentials_file"`, remove `"credentials"` and vice versa.
|
||||
4. Update [TinodeWeb](/tinode/webapp/) config [`firebase-init.js`](https://github.com/tinode/webapp/blob/master/firebase-init.js): update `apiKey`, `messagingSenderId`, `projectId`, `appId`, `messagingVapidKey`. See more info at https://github.com/tinode/webapp/#push_notifications
|
||||
|
||||
The server may also enable FCM without storing credentials in `tinode.conf`:
|
||||
|
||||
* `IM_FCM_CREDENTIALS_FILE` points to the service-account JSON file.
|
||||
* `IM_FCM_CREDENTIALS_JSON` contains the service-account JSON directly.
|
||||
|
||||
Either variable enables the adapter even when the checked-in configuration has `enabled: false`.
|
||||
|
||||
### iOS and Android
|
||||
|
||||
1. If you are using an Android client, add `google-services.json` to [Tindroid](/tinode/tindroid/) by following instructions at https://developers.google.com/android/guides/google-services-plugin and recompile the client. You may also optionally submit it to Google Play Store.
|
||||
|
||||
@@ -123,7 +123,7 @@ func PrepareV1Notifications(rcpt *push.Receipt, config *configType) ([]*fcmv1.Me
|
||||
skipDevices[deviceID] = struct{}{}
|
||||
}
|
||||
}
|
||||
devices, count, err = store.Devices.GetAll(rcpt.TenantID, uids...)
|
||||
devices, count, err = pushDevicesForUsers(rcpt.TenantID, uids...)
|
||||
if err != nil {
|
||||
logs.Warn.Println("fcm push: db error", err)
|
||||
return nil, nil
|
||||
@@ -218,7 +218,7 @@ func TenantChannelName(tenantID t.TenantID, channel string) string {
|
||||
|
||||
// DevicesForUser loads device IDs of the given user.
|
||||
func DevicesForUser(tenantID t.TenantID, uid t.Uid) []string {
|
||||
ddef, count, err := store.Devices.GetAll(tenantID, uid)
|
||||
ddef, count, err := pushDevicesForUsers(tenantID, uid)
|
||||
if err != nil {
|
||||
logs.Warn.Println("fcm devices for user: db error", err)
|
||||
return nil
|
||||
@@ -235,6 +235,19 @@ func DevicesForUser(tenantID t.TenantID, uid t.Uid) []string {
|
||||
return devices
|
||||
}
|
||||
|
||||
func pushDevicesForUsers(tenantID t.TenantID, uids ...t.Uid) (map[t.Uid][]t.DeviceDef, int, error) {
|
||||
if tokens, ok := store.DevicePushTokens.(store.DevicePushTokenQueryInterface); ok {
|
||||
devices, count, err := tokens.GetAll(tenantID, uids...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if count > 0 {
|
||||
return devices, count, nil
|
||||
}
|
||||
}
|
||||
return store.Devices.GetAll(tenantID, uids...)
|
||||
}
|
||||
|
||||
// ChannelsForUser loads user's channel subscriptions with P permission.
|
||||
func ChannelsForUser(tenantID t.TenantID, uid t.Uid) []string {
|
||||
channels, err := store.Users.GetChannels(tenantID, uid)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package fcm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tinode/chat/server/store"
|
||||
"github.com/tinode/chat/server/store/types"
|
||||
)
|
||||
|
||||
type testProductPushTokens struct {
|
||||
devices map[types.Uid][]types.DeviceDef
|
||||
}
|
||||
|
||||
func (s *testProductPushTokens) Upsert(types.TenantID, *types.DevicePushToken) (*types.DevicePushToken, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *testProductPushTokens) GetAll(_ types.TenantID, _ ...types.Uid) (map[types.Uid][]types.DeviceDef, int, error) {
|
||||
count := 0
|
||||
for _, devices := range s.devices {
|
||||
count += len(devices)
|
||||
}
|
||||
return s.devices, count, nil
|
||||
}
|
||||
|
||||
type testLegacyPushDevices struct {
|
||||
called bool
|
||||
}
|
||||
|
||||
func (*testLegacyPushDevices) Update(types.TenantID, types.Uid, string, *types.DeviceDef) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testLegacyPushDevices) GetAll(types.TenantID, ...types.Uid) (map[types.Uid][]types.DeviceDef, int, error) {
|
||||
s.called = true
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (*testLegacyPushDevices) Delete(types.TenantID, types.Uid, string) error { return nil }
|
||||
|
||||
func TestPushDevicesForUsersPrefersProductTokens(t *testing.T) {
|
||||
oldTokens := store.DevicePushTokens
|
||||
oldDevices := store.Devices
|
||||
defer func() {
|
||||
store.DevicePushTokens = oldTokens
|
||||
store.Devices = oldDevices
|
||||
}()
|
||||
uid := types.Uid(42)
|
||||
legacy := &testLegacyPushDevices{}
|
||||
store.DevicePushTokens = &testProductPushTokens{devices: map[types.Uid][]types.DeviceDef{
|
||||
uid: {{TenantID: 7, DeviceId: "product-token", Platform: "android"}},
|
||||
}}
|
||||
store.Devices = legacy
|
||||
|
||||
devices, count, err := pushDevicesForUsers(7, uid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 1 || devices[uid][0].DeviceId != "product-token" {
|
||||
t.Fatalf("unexpected devices: %#v count=%d", devices, count)
|
||||
}
|
||||
if legacy.called {
|
||||
t.Fatal("legacy device store must not be read when product tokens exist")
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,14 @@ func (Handler) Init(jsonconf json.RawMessage) (bool, error) {
|
||||
return false, errors.New("failed to parse config: " + err.Error())
|
||||
}
|
||||
|
||||
if raw := strings.TrimSpace(os.Getenv("IM_FCM_CREDENTIALS_JSON")); raw != "" {
|
||||
config.Credentials = json.RawMessage(raw)
|
||||
config.Enabled = true
|
||||
}
|
||||
if path := strings.TrimSpace(os.Getenv("IM_FCM_CREDENTIALS_FILE")); path != "" {
|
||||
config.CredentialsFile = path
|
||||
config.Enabled = true
|
||||
}
|
||||
if !config.Enabled {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user