Viewer模子加载当地离线缓存实战

演示视频:http://www.bilibili.com/video…

由于Autodesk Forge是完整基于RESTful API框架的云平台,且临时没有当地布置计划,特别是Viewer.js暂不支撑当地搭建,必需外部援用位于效劳器端的剧本,怎样满足离线运用的需求一直是宽大开辟者关注的题目。本文将引见来自Forge参谋团队的国际同事Petr原创的Viewer缓存类型,应用HTML5普遍用于PWA(Progressive Web App)开辟的典范接口完成。

时至今日,要把来自收集运用或效劳的数据缓存至当地装备,我们有几种差别的手艺与体式格局可选,本文将树模运用Service Worker,Cache和Channel Messaging等API完成,它们都是开辟Progrssive Web App的常客。虽然这些API相对较为新锐,但已取得新版浏览器的广发支撑,细致支撑状况能够参考(详见“浏览器兼容性”部份):

当我们在JavaScript中注册了Service Worker以后,该Worker会阻拦浏览器一切页面关于指定收集源或域的要求,返回缓存中的内容,Service Worker亦能够挪用IndexDBChannel MessagingPush等API。Service Worker在特别的Worker上下文(ServiceWorkerGlobalScope)中实行,没法直接对DOM举行操纵,能够自力于页面的情势掌握页面的加载。一个Service Worker能够掌握多个页面,每当指定范围内的页面加载时,Service Worker便于会对其举行装置并操纵,所以请警惕运用全局变量,每一个页面并没有本身自力的Worker。

作为一种特别的Web Worker,Service Worker的生命周期以下:

  • 在JavaScript中注册Service Worker
  • 浏览器下载并实行Worker剧本
  • Worker收到“install”(装置)事宜,一次性设置所需资本
  • 守候其他正在实行的Service Worker完毕(页面封闭)
  • Worker收到“activate”(激活)事宜,消灭Worker的旧cache,并按需接收Worker
  • Worker最先接收“fetch”(阻拦收集要求并返回缓存中的资本)和“message”(与前端代码通信)事宜:

《Viewer模子加载当地离线缓存实战》

Cache是一个存储API,与LocalStorageIndexDB相似,每一个收集源或域都有本身对应的存储空间,个中包含不重名的cache对象,用于存储HTTP要求与应对内容。
《Viewer模子加载当地离线缓存实战》

Channel Messaging是剧本之间通信API,支撑包含主页面、iframe、Web Worker、Service Worker之间的双向通信。

缓存战略

缓存诸如静态资本和API端口返回的数据并不庞杂,可在Service Worker装置时缓存即可。然后,当页面向API端口发送要求时,Service Worker会立即返回缓存的内容,且可按需在背景拉取资本并更新缓存内容。

缓存模子就稍许烦琐,一个模子通常会转换天生数个资本,天生的资本也经常援用其他素材,所以须要找出一切这些依靠并按需将其缓存。在本文的代码示例中,我们在背景写有接口,可依据模子的URN查询并返回所需资本的URL列表。因而在缓存模子时,Service Worker能够挪用该接口缓存一切相干的URL,无需用到Viewer。

代码示例

我们制作了让用户挑选模子作离线缓存的例子,检察代码请接见:https://github.com/petrbroz/f…,在线演示请接见:https://forge-offline.herokua…。接下来我们解说一些详细的代码片断。

例子的背景基于Express,public目次的内容作静态托管,别的效劳端口位于以下三个途径:

  • GET /api/token – 返回考证Token
  • GET /api/models – 返回可浏览的模子列表
  • GET /api/models/:urn/files – 依据模子的URN查询并返回所需资本的URL列表

客户端包含两个中心剧本:public/javascript/main.jspublic/service-worker.js,个中public/javascript/main.js主要用于设置Viewer和UI逻辑,有两个主要的函数在剧本底部:initServiceWorkersubmitWorkerTask,前者触发Service Worker的注册,后者向其发送音讯:

async function initServiceWorker() {
    try {
        const registration = await navigator.serviceWorker.register('/service-worker.js');
        console.log('Service worker registered', registration.scope);
    } catch (err) {
        console.error('Could not register service worker', err);
    }
}

在“activate”事宜中,本例并没有清算旧Worker的须要,因而直接接收并掌握一切Service Worker:

async function activateAsync() {
    const clients = await self.clients.matchAll({ includeUncontrolled: true });
    console.log('Claiming clients', clients.map(client => client.url).join(','));
    await self.clients.claim();
}

阻拦要求时,我们优选比对并返回缓存中的内容,除了GET /api/token将优先尝试猎取新的Token,由于Token是有时效性的,只要猎取新Token失利时我们才运用缓存:

async function fetchAsync(event) {
    // 优先猎取新Token而非运用缓存
    if (event.request.url.endsWith('/api/token')) {
        try {
            const response = await fetch(event.request);
            return response;
        } catch(err) {
            console.log('Could not fetch new token, falling back to cache.', err);
        }
    }

    // 如缓存婚配胜利,直接返回其内容
    const match = await caches.match(event.request.url, { ignoreSearch: true });
    if (match) {
        // 如要求指向静态资本或我们制订范围内的API,同时背景更新缓存
        if (STATIC_URLS.includes(event.request.url) || API_URLS.includes(event.request.url)) {
            caches.open(CACHE_NAME)
                .then((cache) => cache.add(event.request))
                .catch((err) => console.log('Cache not updated, but that\'s ok...', err));
        }
        return match;
    }

    return fetch(event.request);
}

末了,运用Channel Messaging API实行从页面剧本提议的使命:

async function messageAsync(event) {
    switch (event.data.operation) {
        case 'CACHE_URN':
            try {
                const urls = await cacheUrn(event.data.urn, event.data.access_token);
                event.ports[0].postMessage({ status: 'ok', urls });
            } catch(err) {
                event.ports[0].postMessage({ error: err.toString() });
            }
            break;
        case 'CLEAR_URN':
            try {
                const urls = await clearUrn(event.data.urn);
                event.ports[0].postMessage({ status: 'ok', urls });
            } catch(err) {
                event.ports[0].postMessage({ error: err.toString() });
            }
            break;
        case 'LIST_CACHES':
            try {
                const urls = await listCached();
                event.ports[0].postMessage({ status: 'ok', urls });
            } catch(err) {
                event.ports[0].postMessage({ error: err.toString() });
            }
            break;
    }
}

async function cacheUrn(urn, access_token) {
    console.log('Caching', urn);
    // 首先从背景猎取URN所需资本的URL列表
    const baseUrl = 'https://developer.api.autodesk.com/derivativeservice/v2';
    const res = await fetch(`/api/models/${urn}/files`);
    const derivatives = await res.json();
    // 初始化拉取要求以便缓存资本
    const cache = await caches.open(CACHE_NAME);
    const options = { headers: { 'Authorization': 'Bearer ' + access_token } };
    const fetches = [];
    const manifestUrl = `${baseUrl}/manifest/${urn}`;
    fetches.push(fetch(manifestUrl, options).then(resp => cache.put(manifestUrl, resp)).then(() => manifestUrl));
    for (const derivative of derivatives) {
        const derivUrl = baseUrl + '/derivatives/' + encodeURIComponent(derivative.urn);
        fetches.push(fetch(derivUrl, options).then(resp => cache.put(derivUrl, resp)).then(() => derivUrl));
        for (const file of derivative.files) {
            const fileUrl = baseUrl + '/derivatives/' + encodeURIComponent(derivative.basePath + file);
            fetches.push(fetch(fileUrl, options).then(resp => cache.put(fileUrl, resp)).then(() => fileUrl));
        }
    }
    // 并发实行拉取要求并缓存资本
    const urls = await Promise.all(fetches);
    return urls;
}

async function clearUrn(urn) {
    console.log('Clearing cache', urn);
    const cache = await caches.open(CACHE_NAME);
    const requests = (await cache.keys()).filter(req => req.url.includes(urn));
    await Promise.all(requests.map(req => cache.delete(req)));
    return requests.map(req => req.url);
}

async function listCached() {
    console.log('Listing caches');
    const cache = await caches.open(CACHE_NAME);
    const requests = await cache.keys();
    return requests.map(req => req.url);
}

以上。在线演示请接见: https://forge-offline.herokua…,点击左上角模子旁边的“☆”触发缓存, 请参考本文开首所示运用支撑所需API的浏览器接见。
《Viewer模子加载当地离线缓存实战》

延长浏览:

    原文作者:BryanHuang
    原文地址: https://segmentfault.com/a/1190000017940842
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞