如何将我在苹果设备上保存的数据与服务工作者同步?

How to sync my saved data on apple devices with service worker?

我知道 Background Sync API 在 Apple 生态系统中不受支持,那么您将如何绕过它并制定适用于 Apple 生态系统和其他平台的解决方案,现在我有一个使用后台同步 API 的解决方案,出于某种原因,它实际上对 IOS 没有做任何事情,它只是保存失败的请求,然后永远不会 sync-s,我可以只访问同步吗queue 以某种方式,使用 indexedDB 包装器然后在任意时间同步? 我试过一次,它把所有东西都弄坏了,你们知道怎么做吗?

    const bgSyncPlugin = new workbox.backgroundSync.Plugin('uploadQueue', {
    maxRetentionTime: 60 * 24 * 60,
    onSync: async ({ queue }) => {
        return getAccessToken().then((token) => {
            replayQueue(queue, token).then(() => {
                return showNotification();
            });
        });
    },
});

这是我所有的代码。有一个目的,因为我的令牌有超时我必须检查令牌是否过期然后继续并在 headers 中替换令牌如果它已过期,我也必须更改数据我在请求主体中同步,但它在苹果设备以外的任何设备上都能正常工作。 Apple 设备从不触发 onsync,我尝试通过以下方式监听获取事件并触发 onsync:

self.registration.sync.register('uploadQueue');

但没有成功,我尝试在 servvice worker 注册时注册同步,似乎没有任何帮助。 如果同步注册在 ios 上不可行,那么我可以通过某种方式访问​​上传 queue table 吗? P.S.: 我正在使用 dexie.js 作为 indexedDB 包装器,它是一个 vue.js 应用程序,具有 laravel api,并且同步过程非常复杂, 但它正在工作,只需要在 IOS!

上弄清楚如何做

在我脑海中和待办事项清单上思考了大约 2 周后,我找到了答案。 现在拿些爆米花,系好安全带,因为这是一个非常棒的食物。 在我的例子中,同步过程非常复杂,因为我的用户可能会长时间远离任何连接,以至于我的 accessTokens 会过期,所以我还必须检查访问令牌是否过期并重新获取它。 此外,我的用户可以将新人添加到人员数据库中,这些人员都有其唯一的服务器端 id-s,因此我必须以先发送人员注册然后发送完成的任务和活动的方式对我的请求进行排序对于他们,所以我可以从 API.

接收到各自的 ID

现在是有趣的部分: 首先你不能使用bgSyncPlugin,因为你不能访问replayQueue,你必须使用普通队列,像这样:

var bgSyncQueue = new workbox.backgroundSync.Queue('uploadQueue', {
    maxRetentionTime: 60 * 24 * 60,
    onSync: () => syncData(),
   });

并将失败的请求推送到获取侦听器中的队列:

this.onfetch = (event) => {
    let requestClone = event.request.clone();
    if (requestClone.method === 'POST' && 'condition to match the requests you need to replay') {
        event.respondWith(
            (() => {
                const promiseChain = fetch(requestClone).catch(() => {
                    return bgSyncQueue.pushRequest(event);
                });
                event.waitUntil(promiseChain);
                return promiseChain;
            })()
        );
    } else {
        event.respondWith(fetch(event.request));
    }
};

当用户有连接时,我们触发“syncData()”函数,在 ios 这有点复杂(稍后会详细介绍),在 android 它自动发生,因为服务worker 看到它有连接,现在让我们看看 syncData 做了什么:

async function syncData() {
    if (bgSyncQueue) //is there data to sync?
        return getAccessToken() //then get the access token, if expired refresh it
            .then((token) => replayQueue(bgSyncQueue, token).then(() => showNotification({ body: 'Succsesful sync', title: 'Data synced to server' })))
            .catch(() => showNotification({ title: 'Sync unsuccessful', body: 'Please find and area with better coverage' })); //replay the requests and show a notification
    return Promise.resolve('empty');//if no requests to replay return with empty
}

对于 android/desktop 方面的事情,我们已经完成了,您可以对同步修改后的数据感到满意,现在 iOS 我们不能仅在用户重新启动时才上传用户数据PWA,那是糟糕的用户体验,但我们正在玩 javascript 一切皆有可能。

每次客户端代码看到它有互联网时,都会触发一个消息事件,如下所示:

if (this.$online && this.isIOSDevice) {
                    if (window.MessageChannel) {
                        var messageChannel = new MessageChannel();
                        messageChannel.port1.onmessage = (event) => {
                            this.onMessageSuccess(event);
                        };
                    } else {
                        navigator.serviceWorker.onmessage = (event) => {
                            this.onMessageSuccess(event);
                        };
                    }
                    navigator.serviceWorker.ready.then((reg) => {
                        try {
                            reg.active.postMessage(
                                {
                                    text: 'sync',
                                    port: messageChannel && messageChannel.port2,
                                },
                                [messageChannel && messageChannel.port2]
                            );
                        } catch (e) {
                            //firefox support
                            reg.active.postMessage({
                                text: 'sync',
                            });
                        }
                    });
                }

这是在一个 Vue.js 监视函数中,它监视我们是否有连接,如果我们有连接它还会检查这是否是来自苹果生态系统的设备,如下所示:

isIosDevice() {
            return !!navigator.platform && /iPad|iPhone|MacIntel|iPod/.test(navigator.platform) && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
        }

所以它告诉服务人员它有互联网并且它必须同步,在这种情况下这段代码被激活:

this.onmessage = (event) => {
    if (event.data.text === 'sync') {
        event.waitUntil(
            syncData().then((res) => {
                if (res !== 'empty') {
                    if (event.source) {
                        event.source.postMessage('doNotification');//this is telling the client code to show a notification (i have a built in notification system into the app, that does not use push notification, just shows a little pill on the bottom of the app with the message)
                    } else if (event.data.port) {
                        event.data.port.postMessage('doNotification'); //same thing
                    }
                    return res;
                }
            })
        );
    }
};

现在是我认为最有用的部分,重放队列功能,这家伙从 getAccessToken 获取队列和令牌,然后它像发条一样工作:

    const replayQueue = async (queue, token) => {
        let entry;
        while ((entry = await queue.shiftRequest())) {//while we have requests to replay
            let data = await entry.request.clone().json();
            try {
//replay the person registrations first and store them into indexed db
                if (isPersonRequest) {
                    //if new person
                    await fetchPerson(entry, data, token);
//then replay the campaign and task submissions
                } else if (isTaskOrCampaignRequest) {
                    //if task
                    await fetchCampaigns(entry, data, token);
                } 
            } catch (error) {
                showNotification({ title: 'no success', body: 'go for better internet plox' });
                await queue.unshiftRequest(entry); //put failed request back into queue, and try again later
            }
        }
        return Promise.resolve();
    };

现在这是大局,如何在 iOS 设备上使用这个家伙并让 Apple 疯狂 :) 我对任何相关问题持开放态度,在这段时间我想我已经成为与服务工作者相关的东西非常好,因为这不是这个项目唯一困难的部分,但我离题了,那是另一天的故事。

(您可能会发现错误处理并不完美,也许这不是最安全的,但是这个项目的用户数量非常少,有固定数量的用户知道如何使用它并且它的作用,所以在这种情况下我并不是真的害怕安全性,但如果你在一个更严肃的项目中使用,你可能想要改进一些东西)

希望我能帮上忙,祝大家有个愉快的一天。