iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

Chrome拡張機能をウェブストアへアップロードする際Firebase認証が原因でリジェクトされた話

はじめに

こんにちは! 株式会社iimonでエンジニアをしている「あめく」です。

今回、Firebaseの認証パッケージを使用してるサービスがChromeウェブストアへアップロードできなかったためその内容を記載したいと思います。

なぜリジェクトされたのか

まずリジェクトされた理由として、Chrome拡張機能ではセキュリティの強化のためリモートでホストされる(拡張機能のパッケージに含まれていない)ソースコードを読み込んでの実行が許可されていないのですが、デプロイしたサービスにそのソースコードが含まれてたためリジェクトされました。

今回の場合だと、firebase/auth エントリーポイントで https://apis.google.com/js/api.jshttps://www.google.com/recaptcha/api.jshttps://www.google.com/recaptcha/enterprise.js?render= を呼び出していたことが原因でした。

  • リジェクトされた時のメール通知画面(対象コードが記載されてるのは助かりました!)
    Error message: Item contains code hosted remotely in Manifest V3
    リジェクト通知画面

ちなみに、マニフェストV3からリモートでホストされるコードは許可されなくなったみたいですが、 今回リジェクトされたサービスは最初からマニフェストV3だったため、最近より厳しく制限されたのかなと思ってます。。

developer.chrome.com

対応方法

結論からお伝えすると、Firebaseの認証を行うために firebase/auth を使用していたのですが、 Chrome拡張機能の場合は firebase/auth/web-extension を使用する必要がありそうだったためこちらを使用するように修正しました。 たったこれだけです!!

  • 変更前
import { getAuth, onAuthStateChanged } from 'firebase/auth';
  • 変更後
import { getAuth, onAuthStateChanged } from 'firebase/auth/web-extension';

※ エントリーポイントを変更することで使用できない機能もあるかもしれないので、変更する際はご注意ください。

firebase.google.com

つまずいたポイント

今回の対応方法として使用するエントリーポイントの指定を変更しましたが、最初からエントリーポイントの指定を変えるということはせず別の方法での対応を試みましたので、そちらもお伝えしたいと思います。(この方法は良くないです)

別の方法としてはpatch-packageというツールを用いて、パッケージ内のリモートでホストされる対象のコードに対してパッチを当てる方法(コードに手を加える)を試みました。 ※下記に参考URL載せておきます。

qiita.com

dev.blog.n.inc

github.com

この方法の懸念点ですが、すでにリリースされているパッケージ内のコードに対して直接手を加えてしまうため、 パッケージのバージョンが上がったりすると正しく動作するかの確認が必要だったり、新たに手を加える必要があったりと色々と考え事が増えてしまうことです。

運用中にこの処理がどうなってるかを都度確認する必要があるため、この対応方法はあまりいい策とは言えないと考えてます。
また、自分たちの問題かもしれないですがたまに変な挙動をしてしまい何度かリジェクトされてしまいました。
何度かリジェクトされてしまったため別の方法がないか探していたところ firebase/auth/web-extension を使用すれば解決できるとわかったため、パッチを当てて対応する方法は辞めることにしました。

ちなみに、firebaseのsdkのissueの一番下のコメント(今年の2月)に firebase/auth/web-extension が利用可能になったことが記載されてました。。
issueちゃんと読むべきでした。。

github.com

(余談)パッケージ内のリモートでホストされるコードがなくなっているのか確認

せっかくですので、念の為リモートでホストされるコードが含まれてないかを確認してみます。

まず firebase/auth に関してですが、このエントリーポイントをimportすると下記のファイルの処理が呼び出されます。
対象ファイル:

  • node_modules/@firebase/auth-compat/node_modules/@firebase/auth/dist/esm2017/index-21205181.js
_setExternalJSProvider({
    loadJS(url) {
        // TODO: consider adding timeout support & cancellation
        return new Promise((resolve, reject) => {
            const el = document.createElement('script');
            el.setAttribute('src', url);
            el.onload = resolve;
            el.onerror = e => {
                const error = _createError("internal-error" /* AuthErrorCode.INTERNAL_ERROR */);
                error.customData = e;
                reject(error);
            };
            el.type = 'text/javascript';
            el.charset = 'UTF-8';
            getScriptParentElement().appendChild(el);
        });
    },
    gapiScript: 'https://apis.google.com/js/api.js',
    recaptchaV2Script: 'https://www.google.com/recaptcha/api.js', 
    recaptchaEnterpriseScript: 'https://www.google.com/recaptcha/enterprise.js?render='
});
registerAuth("Browser" /* ClientPlatform.BROWSER */);

https://apis.google.com/js/api.js https://www.google.com/recaptcha/api.js https://www.google.com/recaptcha/enterprise.js?render= が含まれていますね。

次に firebase/auth/web-extension のエントリーポイントを呼び出した場合下記のファイルが呼び出されます。
対象ファイル:

  • node_modules/@firebase/auth-compat/node_modules/@firebase/auth/dist/web-extension-esm2017/index.js
import { r as registerAuth, i as initializeAuth, a as indexedDBLocalPersistence, c as connectAuthEmulator } from './register-5aef0e11.js';
export { Y as ActionCodeURL, m as AuthCredential, A as AuthErrorCodes, E as EmailAuthCredential, q as EmailAuthProvider, F as FacebookAuthProvider, t as GithubAuthProvider, G as GoogleAuthProvider, O as OAuthCredential, w as OAuthProvider, P as PhoneAuthCredential, S as SAMLAuthProvider, T as TotpMultiFactorGenerator, b as TotpSecret, x as TwitterAuthProvider, J as applyActionCode, e as beforeAuthStateChanged, K as checkActionCode, I as confirmPasswordReset, c as connectAuthEmulator, M as createUserWithEmailAndPassword, l as debugErrorMap, k as deleteUser, V as fetchSignInMethodsForEmail, a4 as getAdditionalUserInfo, a1 as getIdToken, a2 as getIdTokenResult, a6 as getMultiFactorResolver, n as inMemoryPersistence, a as indexedDBLocalPersistence, i as initializeAuth, d as initializeRecaptchaConfig, R as isSignInWithEmailLink, B as linkWithCredential, a7 as multiFactor, f as onAuthStateChanged, o as onIdTokenChanged, Z as parseActionCodeURL, p as prodErrorMap, C as reauthenticateWithCredential, a5 as reload, j as revokeAccessToken, W as sendEmailVerification, H as sendPasswordResetEmail, Q as sendSignInLinkToEmail, s as setPersistence, y as signInAnonymously, z as signInWithCredential, D as signInWithCustomToken, N as signInWithEmailAndPassword, U as signInWithEmailLink, h as signOut, a3 as unlink, g as updateCurrentUser, $ as updateEmail, a0 as updatePassword, _ as updateProfile, u as useDeviceLanguage, v as validatePassword, X as verifyBeforeUpdateEmail, L as verifyPasswordResetCode } from './register-5aef0e11.js';
import { _getProvider, getApp } from '@firebase/app';
import { getDefaultEmulatorHost } from '@firebase/util';
import 'tslib';
import '@firebase/component';
import '@firebase/logger';

/**
 * @license
 * Copyright 2023 Google LLC
 *
 * 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.
 */
/**
 * Returns the Auth instance associated with the provided {@link @firebase/app#FirebaseApp}.
 * If no instance exists, initializes an Auth instance with platform-specific default dependencies.
 *
 * @param app - The Firebase App.
 *
 * @public
 */
function getAuth(app = getApp()) {
    const provider = _getProvider(app, 'auth');
    if (provider.isInitialized()) {
        return provider.getImmediate();
    }
    const auth = initializeAuth(app, {
        persistence: [indexedDBLocalPersistence]
    });
    const authEmulatorHost = getDefaultEmulatorHost('auth');
    if (authEmulatorHost) {
        connectAuthEmulator(auth, `http://${authEmulatorHost}`);
    }
    return auth;
}
registerAuth("WebExtension" /* ClientPlatform.WEB_EXTENSION */);

export { getAuth };
//# sourceMappingURL=index.js.map
  • node_modules/@firebase/auth-compat/node_modules/@firebase/auth/dist/web-extension-esm2017/register-5aef0e11.js (上記のindex.jsで呼び出してるファイル) → コード量が多いため割愛します。処理を確認しましたが対象のコードが含まれてませんでした。

firebase/auth/web-extension のエントリーポイントを呼び出した場合は、リモートでホストされるコードが含まれていないため、リジェクトされずにウェブストアへ正しくアップロードされます。

まとめ

Firebaseの認証ですが、スタートガイドでは firebase/auth を使用してます。 そのため、基本的に firebase/auth を利用してしまうかもしれません。 firebase/auth/web-extension は今年の2月くらいから利用できるようになったのかなと思っておりこれからこの影響を受ける人がいるのではと感じてます。

また、Firebaseのパッケージで拡張機能のアップロードがリジェクトされた記事があまりなかったため、他に困っている方がいるorこれから同じようになる人もいるのではないかと思い今回記事にしてみました。

もし同じ境遇にあった際はこの記事を参考にしていただけると幸いです。 (ちゃんとドキュメント読んで対応している方は大丈夫だと思いますが!!!)

別途。。 今回エラー内容から対処法を探っていったため一度本質とは異なる対応方法を行ってしまいましたが、 やはりドキュメントをちゃんと確認して対応しないといけないと改めて考えさせられました!! (拡張機能とパッケージで共に同じGoogleのサービスなので最初からいい感じにして欲しかったなぁと感じましたが、Google様いつも素敵なサービスをありがとうございます!!)

ここまで読んでくださってありがとうございました。

また、弊社ではエンジニアを募集しております。
ぜひカジュアル面談でお話ししましょう!

ご興味ありましたら、ご応募ください!

Wantedly / Green