بررسی service worker در جاوااسکریپت

سرویس ورکر یک قابلیت از مرورگرهای مدرن می‌باشد که امکان اجرای برخی از اسکریپتهای جاوااسکریپت را خارج از process اصلی صفحه وب می‌دهد.

کدهای جاوااسکریپت خط به خط و در قالب یک thread اجرا می‌شوند و امکان داشتن دو thread همزمان را نداریم. service worker این امکان را می‌دهد که برخی از اسکریپتهای جاوااسکریپت را در خارج از thread اصلی صفحه وب و در پس زمینه اجرا کنیم. service worker قابلیت دسترسی به DOM را ندارد اما امکان مواردی مانند کش کردن فایل‌ها، اسکریپتها، استایل‌ها و ... را می‌دهد، همیچنین برای پیاده سازی PWA و نوتیفیکیشنها به service worker نیازمند هستیم.

قابلیت service worker در مرورگرهای مدرن پشتیبانی می‌شود ولی خللی در اجرای سایر اسکریپتهای صفحات وب، در مرورگرهای قدیمی‌تر که service worker را پشتیبانی نمیکنند، ایجاد نمی‌کند. برای استفاده از service worker، پس از چک کردن پشتیبانی مرورگر، باید فایل service worker را ریجیستر کرد:


if("serviceWorker" in navigator){
    navigator.serviceWorker.register("/sw.js", {
        scope: "/",
    }).then(register=>{
        console.log(register)
    });
}

در این مثال فایل sw.js را که در مسیر اصلی وب‌سایت است را ریجیستر کردیم. پس از ریجیستر کردن service worker، وضعیت آن به صورت یکی از موارد زیر خواهد بود.

درحال نصب یا installing:زمانی که service worker در حال نصب sw.js است.

درحال انتظار یا waiting: پس از نصب service worker، فعال نمی‌شود و درحال انتظار برای فعال شدن از روشهایی مانند ریلود صفحه یا متد skipWaiting می‌ماند.

فعال یا active زمانی که سروریس ورکر فعال شده باشد.


if("serviceWorker" in navigator){
    navigator.serviceWorker.register("/sw.js", {
        scope: "/",
    }).then(registeration=>{
        if(registeration.installing){
            console.log("sw.js is installing ...");
        }else if(registeration.waiting){
            console.log("sw.js is wating for activation ...");
        }else if(registeration.active){
            console.log("sw.js is active!");
        }
    });
}

با هربار تغییر در فایل sw.js این چرخه برای نسخه جدید این فایل تکرار می‌شود. برای مشاهده نسخه فایل sw.js و وضعیت service worker خود می‌توانید از dev tools مرورگر کمک بگیرید:

service worker در dev-tools
دقت کنید فایل sw.js را اگر در مسیر دیگری غیر از root مانند js قرار دهیم، فقط برای درخواستهایی که در زیر مسیر js باشد، فعال خواهد شد.
برای استفاده از service worker باید https فعال باشد، اما برای localhost استثنائا با http نیز اجرا خواهد شد.
در صورتی که از قبل service worker ریجیستر شده باشد، متد register تا زمانی که فایل اسکریپت sw.js تغییری نداشته باشد، باعث ریجیستر شدن مجدد فایل اسکریپت نمی‌شود.

پراپرتی controller

برای چک کردن فعال بودن serviceWorker می‌توان مانند زیر عمل کرد:


if("serviceWorker" in navigator){
    if(navigator.serviceWorker.controller){
        console.log("service worker is actived!")
    }
}

پراپرتی controller یک پراپرتی read-only است که یک رفرنس از service worker ریجیسترشده فعال را برمی‌گرداند.

ایونت oncontrollchange

یک تابع دریافت می‌کند و موقعی که service worker مجددا ریجیستر و فعال (active) شد، آنرا اجرا می‌کند.


navigator.serviceWorker.addEventListener("controllerchange", () => { 
    console.log("The controller of current browsing context has changed.");
});

پراپرتی ready

به کمک پراپرتی ready که یک پراپرتی read-only است، می‌توان اجرای کدها را تا فعال شدن service worker به تعویق انداخت:


navigator.serviceWorker.ready.then((registration) => {
    console.log(`A service worker is active: ${registration.active}`);
});

متد getRegistration:

این متد یک url دریافت می‌کند و در عوض یک پرامیس که حاوی service worker ریجسترشده فعال مطابق با url را برمی‌گرداند:


navigator.serviceWorker.getRegistration("/").then((registration) => {
    if (registration) {
        console.log(registration)
    }
});

متد getRegistrations:

متد getRegistrations یک پرامیس حاوی آرایه‌ای از تمام service worker های ریجیستر شده بر‌میگرداند.


navigator.serviceWorker.getRegistrations().then((registrations) => {
    for(let registration of registrations){
        console.log(registration)
    }
});

متد unregister

متد unregister برای unregister کردن service worker ریجیستر شده بکار می‌رود. این متد یک پرامیس حاوی مقدار بولین نتیجه unregister کردن service worker را برمی‌گرداند.


navigator.serviceWorker.getRegistration("/").then((registration) => { 
     if (registration) { 
        registration.unregister().then((boolean) => { 
               // if boolean = true, unregister is successful 
         });
     }
});

متد update

با فراخوانی متد update، مرورگر فایل اسکریپت service weorker را فتچ می‌کند و درصورتی که تغییری در این فایل رخ داده باشد، دوباره آنرا install می‌کند.


navigator.serviceWorker.getRegistration("/").then((registration) => {
       if (registration) {
          registration.update();
      }
});
عملیات update و unregister را باید براساس لاجیک نرم افزار خود استفاده کنید. مثلا موقعی که کاربر روی دکمه update کلیک کرد.

همان طور که مشاهده کردید، با کمک متد register توانستیم فایل اسکریپت مربوط به service worker را به مرورگر معرفی کنیم. اکنون به سراغ ایجاد اسکریپت مورد نیاز خود می‌رویم.


navigator.serviceWorker.register("/sw.js", { scope: "/", });

در فایل sw.js دسترسی به آبجکت window و یا document دسترسی وجود ندارد، برای به آبجکت ServiceWorkerGlobalScope، می‌توان از کلمه کلیدی self استفاده کرد. این آبجکت برخی از پراپرتی‌های مورد نیاز مانند caches، locaction و ... را در اختیار برنامه نویس قرار می‌دهد.


//sw.js
console.log({slef});

اکنون به سراغ معرفی برخی event های مهم و ضروری می‌رویم.

ایونت install

یک تابع دریافت می‌کند و آنرا در زمان install شدن فایل service worker اجرا می‌کند. در صورتی که مشکلی در اجرای ایونت install رخ دهد، در دفعات بعد مجددا اجرا می‌شود ولی در صورتی که با موفقیت اجرا شود، دیگر اجرا نخواهد شد. کارهایی مثل کش کردن فایلها و asset ها را می‌توان درون این تابع انجام داد.


//sw.js
self.addEventListener('install',function(event){
    console.log('install');
});

ایونت activate

یک تابع دریافت می‌کند و آنرا در زمان active شدن service worker اجرا می‌کند.


//ws.js
self.addEventListener('activate',function(event){
    console.log('activated');
});

ایونت fetch

پس از install و active شدن اسکریپت service worker، صفحه وب را تحت کنترل خود درخواهد آورد. در هنگام ارسال درخواست بین سرور و کلاینت کاربر، service worker بصورت یک پراکسی عمل خواهد کرد. منظور از ارسال درخواست، تمام ریکوئستهای صفحه تحت کنترل، شامل فایل html، استایلهای css و اسکریپتهای js و ... می‌باشد. به عبارت دیگر درخواست ها از صفحه وب به service worker ارسال می‌شود. service worker تصمیم می‌گیرد که پاسخ درخواست را از سرور یا کش دریافت کند و به کلاینت کاربر برگرداند.

service worker proxy request

یک تابع دریافت می‌کند و آنرا در زمان اجرای هر ریکوئست وب‌سایت، اجرا می‌کند. به کمک این تابع شما می‌توانید مشخص کنید که پاسخ درخواست از طریق کش یا سرور خوانده شود.


//ws.js
self.addEventListener('fetch',function(event){
    console.log('fetch:',event.request);
});

ایونت message

راه ارتباطی service worker با صفحه تحت کنترلش ارسال message می‌باشد.

در نمونه زیر، یک تابع دریافت می‌کند و آنرا موقعی که کلاینت یک message به service worker ارسال کند، اجرا خواهد کرد.


//sw.js
self.addEventListener('message',function(ev){
     console.log(ev.data);
});

در مثال زیر موقعی که serviceWorker آماده استفاده شد، یک message ارسال میکنیم، که نتیجه آن اجرا شدن event بالا می‌شود.


navigator.serviceWorker.ready.then( registration => {
    registration.active.postMessage("hi!");
});

همچنین قابلیت ارسال message از service worker به صفحه تحت کنترلش فراهم است.

متد skipWaiting

موقعی که service worker جدید ریجیستر شود، صفحه وب برای فعال شدن آن تا ریلود صفحه در حالت waiting منتظر می‌ماند. برای اینکه نیاز به ریلود صفحه برای اکتیو شدن sw.js جدید نباشد و مرورگر مجبور شود sw.js جدید را فعال کند، متد skipWaiting را صدا می‌زنیم. این متد در تمام script قابل صدا زدن می‌باشد اما چون فقط روی sw.js جدید تاثیر می‌گذارد، معمولا در ایونت install صدا زده می‌شود:


self.addEventListener('install', function(event) {
    self.skipWaiting();
});

متد clients.claim

موقعی که از service worker جدید بوسیله skipWaiting فعال یا active شود، صفحه وب برای استفاده از نسخه جدید تا ریلود صفحه منتظر می‌ماند. برای اینکه صفحه وب بدون نیاز به ریلود، نسخه جدید service worker را جایگزین نسخه قبلی کند، پس از اکتیو شدن service worker، از متد client.claim استفاده می‌کنیم.


self.addEventListener('install', function(event) {
     self.skipWaiting();
 });

self.addEventListener("activate", (event) => {
     event.waitUntil(clients.claim()); 
});

متد waitUntil

در هنگام install و activate سرویس ورکر، کارهایی مانند کش کردن فایلها، fetch و ... انجام می‌شود. این کارها به صورت async انجام می‌شوند. در حقیقت waitUntil یک promise دریافت می‌کند و تا موقعی که عملیات promise درحال انجام است، service worker را در حالت installing نگه می‌دارد. درصورتی که پرامیس پاس داده شده، ریجکت شود جلوی کامل شدن عملیات install یا activate را می‌گیرد.


self.addEventListener('install', function(event) {
     self.skipWaiting();  
   const preCache = async () => {
    const cache = await caches.open("static-v1");
    return cache.addAll(["/", "/about/", "/static/styles.css"]);
  };
  event.waitUntil(preCache());
});