Open13

firebaseAuth x Service Worker ユーザー認証

tama8021tama8021

firebae公式

https://firebase.google.com/docs/auth/web/service-worker-sessions?hl=ja#web-version-9_5
https://github.com/FirebaseExtended/firebase-auth-service-worker-sessions

公式のコードを使って全容(service-worker)

sw.js
/**
 * Copyright 2018 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Service worker for Firebase Auth test app application. The
 * service worker caches all content and only serves cached content in offline
 * mode.
 */

import firebase from 'firebase/app';
import 'firebase/auth';
import * as config from './config.js';

// Initialize the Firebase app in the web worker.
firebase.initializeApp(config);

const CACHE_NAME = 'cache-v1';
const urlsToCache = [
  '/',
  '/manifest.json',
  '/config.js',
  '/script.js',
  '/common.js',
  '/style.css'
];

firebase.auth().onAuthStateChanged((user) => {
  if (user) {
    console.log('user signed in', user.uid);
  } else {
    console.log('user signed out');
  }
});

/**
 * Returns a promise that resolves with an ID token if available.
 * @return {!Promise<?string>} The promise that resolves with an ID token if
 *     available. Otherwise, the promise resolves with null.
 */
const getIdToken = () => {
  return new Promise((resolve, reject) => {
    const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
      unsubscribe();
      if (user) {
        user.getIdToken().then((idToken) => {
          resolve(idToken);
        }, (error) => {
          resolve(null);
        });
      } else {
        resolve(null);
      }
    });
  }).catch((error) => {
    console.log(error);
  });
};


/**
 * @param {string} url The URL whose origin is to be returned.
 * @return {string} The origin corresponding to given URL.
 */
const getOriginFromUrl = (url) => {
  // https://stackoverflow.com/questions/1420881/how-to-extract-base-url-from-a-string-in-javascript
  const pathArray = url.split('/');
  const protocol = pathArray[0];
  const host = pathArray[2];
  return protocol + '//' + host;
};


self.addEventListener('install', (event) => {
  // Perform install steps.
  event.waitUntil(caches.open(CACHE_NAME).then((cache) => {
    // Add all URLs of resources we want to cache.
    return cache.addAll(urlsToCache)
        .catch((error) => {
          // Suppress error as some of the files may not be available for the
          // current page.
        });
  }));
});

// As this is a test app, let's only return cached data when offline.
self.addEventListener('fetch', (event) => {
  const fetchEvent = event;
  // Get underlying body if available. Works for text and json bodies.
  const getBodyContent = (req) => {
    return Promise.resolve().then(() => {
      if (req.method !== 'GET') {
        if (req.headers.get('Content-Type').indexOf('json') !== -1) {
          return req.json()
            .then((json) => {
              return JSON.stringify(json);
            });
        } else {
          return req.text();
        }
      }
    }).catch((error) => {
      // Ignore error.
    });
  };
  const requestProcessor = (idToken) => {
    let req = event.request;
    let processRequestPromise = Promise.resolve();
    // For same origin https requests, append idToken to header.
    if (self.location.origin == getOriginFromUrl(event.request.url) &&
        (self.location.protocol == 'https:' ||
         self.location.hostname == 'localhost') &&
        idToken) {
      // Clone headers as request headers are immutable.
      const headers = new Headers();
      for (let entry of req.headers.entries()) {
        headers.append(entry[0], entry[1]);
      }
      // Add ID token to header. We can't add to Authentication header as it
      // will break HTTP basic authentication.
      headers.append('Authorization', 'Bearer ' + idToken);
      processRequestPromise = getBodyContent(req).then((body) => {
        try {
          req = new Request(req.url, {
            method: req.method,
            headers: headers,
            mode: 'same-origin',
            credentials: req.credentials,
            cache: req.cache,
            redirect: req.redirect,
            referrer: req.referrer,
            body,
            bodyUsed: req.bodyUsed,
            context: req.context
          });
        } catch (e) {
          // This will fail for CORS requests. We just continue with the
          // fetch caching logic below and do not pass the ID token.
        }
      });
    }
    return processRequestPromise.then(() => {
      return fetch(req);
    })
    .then((response) => {
      // Check if we received a valid response.
      // If not, just funnel the error response.
      if (!response || response.status !== 200 || response.type !== 'basic') {
        return response;
      }
      // If response is valid, clone it and save it to the cache.
      const responseToCache = response.clone();
      // Save response to cache only for GET requests.
      // Cache Storage API does not support using a Request object whose method is
      // not 'GET'.
      if (req.method === 'GET') {
        caches.open(CACHE_NAME).then((cache) => {
          cache.put(fetchEvent.request, responseToCache);
        });
      }
      // After caching, return response.
      return response;
    })
    .catch((error) => {
      // For fetch errors, attempt to retrieve the resource from cache.
      return caches.match(fetchEvent.request.clone());
    })
    .catch((error) => {
      // If error getting resource from cache, do nothing.
      console.log(error);
    });
  };
  // Try to fetch the resource first after checking for the ID token.
  event.respondWith(getIdToken().then(requestProcessor, requestProcessor));
});

self.addEventListener('activate', (event) => {
  // Update this list with all caches that need to remain cached.
  const cacheWhitelist = ['cache-v1'];
  event.waitUntil(caches.keys().then((cacheNames) => {
    return Promise.all(cacheNames.map((cacheName) => {
      // Check if cache is not whitelisted above.
      if (cacheWhitelist.indexOf(cacheName) === -1) {
        // If not whitelisted, delete it.
        return caches.delete(cacheName);
      }
    // Allow active service worker to set itself as the controller for all clients
    // within its scope. Otherwise, pages won't be able to use it until the next
    // load. This makes it possible for the login page to immediately use this.
    })).then(() => clients.claim());
  }));
});
tama8021tama8021

TypeScriptに置き換えた

service_worker.ts
import '../@types/service_worker';
import 'firebase/auth';

const CACHE_NAME = 'cache-v1';

import { onAuthStateChanged, getIdToken } from 'firebase/auth';
import { firebaseAuth } from 'src/libs/firebase/index';

// idTokenを取得
function getIdTokenPromise(): Promise<unknown> {
  try {
    return new Promise((resolve, reject) => {
      const unsubscribe = onAuthStateChanged(firebaseAuth, (user) => {
        unsubscribe();
        if (user) {
          getIdToken(user).then((idToken) => {
            resolve(idToken);
          }, (error) => {
            resolve(null);
          });
        } else {
          resolve(null);
        }
      });
    });
  } catch (error_1) {
    console.log(error_1);
  }
}

// URLからルートのURLを取得する処理
function getOriginFromUrl(url: String): string {
  const pathArray = url.split('/');
  const protocol = pathArray[0];
  const host = pathArray[2];
  return protocol + '//' + host;
}

// Service Workderのライフサイクルでfetchしたときの処理
self.addEventListener('fetch', (event: any) => {
  const fetchEvent: FetchEvent = event;

  // Get underlying body if available. Works for text and json bodies.
  function getBodyContent(req: Request): Promise<string | void | undefined> {
    return Promise.resolve().then(() => {
      if (req.method !== 'GET') {
        if (req.headers.get('Content-Type').indexOf('json') !== -1) {
          return req.json()
            .then((json: any) => {
              return JSON.stringify(json);
            });
        } else {
          return req.text();
        }
      }
    }).catch((error) => {
      console.log(error);
    });
  }

  // リクエストをラップして、ヘッダにFirebase AuthのIdTokenを追加する処理
  function requestProcessor(idToken: any): Promise<void | Response> {
    let req: Request = event.request;
    let processRequestPromise = Promise.resolve();
    // URLを取得して、httpsもしくはlocalhostかなどをチェック
    if (self.location.origin == getOriginFromUrl(event.request.url) &&
      (self.location.protocol == 'https:' ||
        self.location.hostname == 'localhost') &&
      idToken) {
      // ヘッダ情報をクローンする
      const headers = new Headers();
      req.headers.forEach((val: string, key: string) => {
        headers.append(key, val);
      });
      // クローンしたヘッダにFirebase AuthのIdTokenを追加
      headers.append('Authorization', 'Bearer ' + idToken);

      processRequestPromise = getBodyContent(req).then((body: any) => {
        try {
          req = new Request(req.url, {
            method: req.method,
            headers: headers,
            mode: 'same-origin',
            credentials: req.credentials,
            cache: req.cache,
            redirect: req.redirect,
            referrer: req.referrer,
            body,
          });
        } catch (e) {
          console.log(e);
          // This will fail for CORS requests. We just continue with the
          // fetch caching logic below and do not pass the ID token.
        }
      });
    }
    return processRequestPromise.then(() => {
      return fetch(req);
    }).then((response: Response) => {
      // レスポンスが正しくない場合はそのまま返却
      if (!response || response.status !== 200 || response.type !== 'basic') {
        return response;
      }

      // request を複製する(ストリームは再利用できないので)
      const responseToCache = response.clone();
      // Save response to cache only for GET requests.
      // Cache Storage API does not support using a Request object whose method is
      // not 'GET'.
      if (req.method === 'GET') {
        caches.open(CACHE_NAME).then((cache) => {
          // cache に登録する
          cache.put(fetchEvent.request, responseToCache);
        });
      }

      // After caching, return response.
      return response;
    })
      .catch((error) => {
        // For fetch errors, attempt to retrieve the resource from cache.
        return caches.match(fetchEvent.request.clone());
      })
      .catch((error) => {
        // If error getting resource from cache, do nothing.
        console.log(error);
      });
  }
  
  // 上の関数を使って、全リクエストでIdTokenの取得し、Firebase AuthのIdTokenを追加ようにする
  event.respondWith(getIdTokenPromise().then(requestProcessor, requestProcessor));
});


// Service Workderのライフサイクルでactivateしたときの処理
self.addEventListener('activate', (event: any) => {
  const extendebleEvent: ExtendableEvent = event;
  // Update this list with all caches that need to remain cached.
  const cacheWhitelist = ['cache-v1'];
  extendebleEvent.waitUntil(caches.keys().then((cacheNames) => {
    return Promise.all(cacheNames.map((cacheName) => {
      // キャッシュが登録されてるか確認
      if (cacheWhitelist.indexOf(cacheName) === -1) {
        return caches.delete(cacheName);
      }
    })).then(() => clients.claim());
  }));
});
@types/service_worcker.d.ts
/**
 * Copyright (c) 2016, Tiernan Cridland
 *
 * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
 * granted, provided that the above copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Typings for Service Worker
 * @author Tiernan Cridland
 * @email tiernanc@gmail.com
 * @license: ISC
 */

 interface Navigator {
	serviceWorker: ServiceWorkerContainer;
}

interface ExtendableEvent extends Event {
	waitUntil(fn: Promise<any>): void;
}

interface ServiceWorker extends Worker {
	scriptURL: string;
	state: ServiceWorkerState;
}

interface ServiceWorkerContainer {
	controller?: ServiceWorker;
	oncontrollerchange?: (event?: Event) => any;
	onerror?: (event?: Event) => any;
	onmessage?: (event?: Event) => any;
	ready: Promise<ServiceWorkerRegistration>;
	getRegistration(scope?: string): Promise<ServiceWorkerRegistration>;
	getRegistrations(): Promise<Array<ServiceWorkerRegistration>>;
	register(url: string, options?: ServiceWorkerRegistrationOptions): Promise<ServiceWorkerRegistration>;
}

interface ServiceWorkerNotificationOptions {
	tag?: string;
}

interface ServiceWorkerRegistration {
	active?: ServiceWorker;
	installing?: ServiceWorker;
	onupdatefound?: (event?: Event) => any;
	pushManager: PushManager;
	scope: string;
	waiting?: ServiceWorker;
	getNotifications(options?: ServiceWorkerNotificationOptions): Promise<Array<Notification>>;
	update(): void;
	unregister(): Promise<boolean>;
}

interface ServiceWorkerRegistrationOptions {
	scope?: string;
}

type ServiceWorkerState = "installing" | "installed" | "activating" | "activated" | "redundant";

// CacheStorage API

interface Cache {
	add(request: Request): Promise<void>;
	addAll(requestArray: Array<Request>): Promise<void>;
	'delete'(request: Request, options?: CacheStorageOptions): Promise<boolean>;
	keys(request?: Request, options?: CacheStorageOptions): Promise<Array<string>>;
	match(request: Request, options?: CacheStorageOptions): Promise<Response>;
	matchAll(request: Request, options?: CacheStorageOptions): Promise<Array<Response>>;
	put(request: Request|string, response: Response): Promise<void>;
}

interface CacheStorage {
	'delete'(cacheName: string): Promise<boolean>;
	has(cacheName: string): Promise<boolean>;
	keys(): Promise<Array<string>>;
	match(request: Request, options?: CacheStorageOptions): Promise<Response>;
	open(cacheName: string): Promise<Cache>;
}

interface CacheStorageOptions {
	cacheName?: string;
	ignoreMethod?: boolean;
	ignoreSearch?: boolean;
	ignoreVary?: boolean;
}

// Client API

interface Client {
	frameType: ClientFrameType;
	id: string;
	url: string;
}

interface Clients {
	claim(): Promise<any>;
	get(id: string): Promise<Client>;
	matchAll(options?: ClientMatchOptions): Promise<Array<Client>>;
	openWindow(url: string): Promise<WindowClient>;
}

interface ClientMatchOptions {
	includeUncontrolled?: boolean;
	type?: ClientMatchTypes;
}

interface WindowClient {
	focused: boolean;
	visibilityState: WindowClientState;
	focus(): Promise<WindowClient>;
	navigate(url: string): Promise<WindowClient>;
}

type ClientFrameType = "auxiliary" | "top-level" | "nested" | "none";
type ClientMatchTypes = "window" | "worker" | "sharedworker" | "all";
type WindowClientState = "hidden" | "visible" | "prerender" | "unloaded";

// Fetch API

interface Body {
	bodyUsed: boolean;
	arrayBuffer(): Promise<ArrayBuffer>;
	blob(): Promise<Blob>;
	formData(): Promise<FormData>;
	json(): Promise<any>;
	text(): Promise<string>;
}

interface FetchEvent extends Event {
	request: Request;
	respondWith(response: Promise<Response>|Response): Promise<Response>;
}

interface InstallEvent extends ExtendableEvent {
	activeWorker: ServiceWorker
}

interface ActivateEvent extends ExtendableEvent {
}

interface Headers {
	new(init?: any): Headers;
	append(name: string, value: string): void;
	'delete'(name: string): void;
	entries(): Array<Array<string>>;
	get(name: string): string;
	getAll(name: string): Array<string>;
	has(name: string): boolean;
	keys(): Array<string>;
	set(name: string, value: string): void;
	values(): Array<string>;
}

interface Request extends Body {
	new(url: string, init?: {
    method?: string,
    url?: string,
    referrer?: string,
    mode?: 'cors'|'no-cors'|'same-origin'|'navigate',
    credentials?: 'omit'|'same-origin'|'include',
    redirect?: 'follow'|'error'|'manual',
    integrity?: string,
    cache?: 'default'|'no-store'|'reload'|'no-cache'|'force-cache'
    headers?: Headers
	}): Request;
	cache: RequestCache;
	credentials: RequestCredentials;
	headers: Headers;
	integrity: string;
	method: string;
	mode: RequestMode;
	referrer: string;
	referrerPolicy: ReferrerPolicy;
	redirect: RequestRedirect;
	url: string;
	clone(): Request;
}

interface Response extends Body {
	new(url: string): Response;
	new(body: Blob|BufferSource|FormData|String, init: {
		status?: number,
		statusText?: string,
		headers?: (Headers|{ [k: string]: string })
	}): Response;
	headers: Headers;
	ok: boolean;
	redirected: boolean;
	status: number;
	statusText: string;
	type: ResponseType;
	url: string;
	useFinalURL: boolean;
	clone(): Response;
	error(): Response;
	redirect(): Response;
}

type ReferrerPolicy = "" | "no-referrer" | "no-referrer-when-downgrade" | "origin-only" | "origin-when-cross-origin" |
	"unsafe-url";
type RequestCache = "default" | "no-store" | "reload" | "no-cache" | "force-cache";
type RequestCredentials = "omit" | "same-origin" | "include";
type RequestMode = "cors" | "no-cors" | "same-origin" | "navigate";
type RequestRedirect = "follow" | "error" | "manual";
type ResponseType = "basic" | "cores" | "error" | "opaque";

// Notification API

interface Notification {
	body: string;
	data: any;
	icon: string;
	lang: string;
	requireInteraction: boolean;
	silent: boolean;
	tag: string;
	timestamp: number;
	title: string;
	close(): void;
	requestPermission(): Promise<string>;
}

interface NotificationEvent {
	action: string;
	notification: Notification;
}

// Push API

interface PushEvent extends ExtendableEvent {
	data: PushMessageData;
}

interface PushManager {
	getSubscription(): Promise<PushSubscription>;
	permissionState(): Promise<string>;
	subscribe(): Promise<PushSubscription>;
}

interface PushMessageData {
	arrayBuffer(): ArrayBuffer;
	blob(): Blob;
	json(): any;
	text(): string;
}

interface PushSubscription {
	endpoint: string;
	getKey(method: string): ArrayBuffer;
	toJSON(): string;
	unsubscribe(): Promise<boolean>;
}

// Sync API

interface SyncEvent extends Event {
	lastChance: boolean;
	tag: string;
}

// ServiceWorkerGlobalScope

declare var Headers: Headers;
declare var Response: Response;
declare var Request: Request;
declare var caches: CacheStorage;
declare var clients: Clients;
declare var onactivate: (event?: ExtendableEvent) => any;
declare var onfetch: (event?: FetchEvent) => any;
declare var oninstall: (event?: ExtendableEvent) => any;
declare var onmessage: (event: MessageEvent) => any;
declare var onnotificationclick: (event?: NotificationEvent) => any;
declare var onnotificationclose: (event?: NotificationEvent) => any;
declare var onpush: (event?: PushEvent) => any;
declare var onpushsubscriptionchange: () => any;
declare var onsync: (event?: SyncEvent) => any;
declare var registration: ServiceWorkerRegistration;

declare function fetch(request: Request|string): Promise<Response>;
declare function skipWaiting(): void;

参考

tama8021tama8021

Next.jsでService Workerを使う

  • public /service_worker.jsに作る
tama8021tama8021

tsではできないらしい。。

tokenが取れない

ReferenceError: onAuthStateChanged is not defined

onAuthStateChangedがないよとおこられる

tama8021tama8021
  • worker/index.ts を作って、custom service workerを書く。
  • next.config.jsを下記のように変更
const withPWA = require("next-pwa");
const runtimeCaching= require("next-pwa/cache");

module.exports = withPWA({
  pwa: {
    dest: 'public',
    runtimeCaching
  }
})

一応これでbuildは通ったけど、、

tama8021tama8021

publicファイルにfirebase-service-worker.jsファイルを作る

public/firebase-service-worker.js
importScripts('https://www.gstatic.com/firebasejs/9.1.3/firebase-app-compat.js')
importScripts('https://www.gstatic.com/firebasejs/9.1.3/firebase-auth-compat.js')
importScripts('/swenv.js')

const CACHE_NAME = 'cache-v1'

// Initialize the Firebase app in the service worker script.
firebase.initializeApp(swEnv)
const auth = firebase.auth()

// idTokenを取得
const getIdTokenPromise = () => {
  return new Promise((resolve, reject) => {
    const unsubscribe = auth.onAuthStateChanged((user) => {
      unsubscribe()
      if (user) {
        user.getIdToken().then(
          (idToken) => {
            resolve(idToken)
          },
          (e) => {
            resolve(null)
          }
        )
      } else {
        resolve(null)
      }
    })
  })
}

// URLからルートのURLを取得する処理
const getOriginFromUrl = (url) => {
  const pathArray = url.split('/')
  const protocol = pathArray[0]
  const host = pathArray[2]
  return protocol + '//' + host
}

// Service Workderのライフサイクルでfetchしたときの処理
self.addEventListener('fetch', (event) => {
  const fetchEvent = event

  // Get underlying body if available. Works for text and json bodies.
  function getBodyContent(req) {
    return Promise.resolve()
      .then(() => {
        if (req.method !== 'GET') {
          if (req.headers.get('Content-Type').indexOf('json') !== -1) {
            return req.json().then((json) => {
              return JSON.stringify(json)
            })
          } else {
            return req.text()
          }
        }
      })
      .catch((error) => {
        console.log(error)
      })
  }

  // リクエストをラップして、ヘッダにFirebase AuthのIdTokenを追加する処理
  function requestProcessor(idToken) {
    let req = event.request
    let processRequestPromise = Promise.resolve()
    // URLを取得して、httpsもしくはlocalhostかなどをチェック
    console.log(idToken);
    if (
      self.location.origin == getOriginFromUrl(event.request.url) &&
      (self.location.protocol == 'https:' ||
        self.location.hostname == 'localhost') &&
      idToken
    ) {
      // ヘッダ情報をクローンする
      const headers = new Headers()
      req.headers.forEach((val, key) => {
        headers.append(key, val)
      })
      // クローンしたヘッダにFirebase AuthのIdTokenを追加
      headers.append('Authorization', 'Bearer ' + idToken)

      processRequestPromise = getBodyContent(req).then((body) => {
        try {
          req = new Request(req.url, {
            method: req.method,
            headers: headers,
            mode: 'same-origin',
            credentials: req.credentials,
            cache: req.cache,
            redirect: req.redirect,
            referrer: req.referrer,
            body,
          })
        } catch (e) {
          console.log(e)
          // This will fail for CORS requests. We just continue with the
          // fetch caching logic below and do not pass the ID token.
        }
      })
    }
    return processRequestPromise
      .then(() => {
        return fetch(req)
      })
      .then((response) => {
        // レスポンスが正しくない場合はそのまま返却
        if (!response || response.status !== 200 || response.type !== 'basic') {
          return response
        }

        // request を複製する(ストリームは再利用できないので)
        const responseToCache = response.clone()
        // Save response to cache only for GET requests.
        // Cache Storage API does not support using a Request object whose method is
        // not 'GET'.
        if (req.method === 'GET') {
          caches.open(CACHE_NAME).then((cache) => {
            // cache に登録する
            cache.put(fetchEvent.request, responseToCache)
          })
        }

        // After caching, return response.
        return response
      })
      .catch((error) => {
        // For fetch errors, attempt to retrieve the resource from cache.
        return caches.match(fetchEvent.request.clone())
      })
      .catch((error) => {
        // If error getting resource from cache, do nothing.
        console.log(error)
      })
  }

  // 上の関数を使って、全リクエストでIdTokenの取得し、Firebase AuthのIdTokenを追加ようにする
  event.respondWith(
    getIdTokenPromise().then(requestProcessor, requestProcessor)
  )
})

// Service Workderのライフサイクルでactivateしたときの処理
self.addEventListener('activate', (event) => {
  const extendebleEvent = event
  // Update this list with all caches that need to remain cached.
  const cacheWhitelist = ['cache-v1']
  extendebleEvent.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          // キャッシュが登録されてるか確認
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName)
          }
        })
      ).then(() => clients.claim())
    })
  )
})

環境変数の設定はこの記事を参考にした
https://sunday-morning.app/posts/2019-07-17-nuxt-js-service-worker-environment

next-pwaを使わず、一応これでも呼び出しはできているみたい

tama8021tama8021

getIdTokenPromiseでuserが取れていないため、headerに追加されていないみたい

tama8021tama8021

onAuthStateChangedでuserが何故か取れないため、一旦諦めて、普通にCookieからheaderに渡すようにする

tama8021tama8021

Next.js x FirebaseAuth x Service Workerの実装

誰かできた人いたらコメントとかで教えて下さい。。