ミニアプリでホームに追加したショートカットキーから起動したかどうかを知りたい

ミニアプリでホームに追加したショートカットキーから起動したかどうかを知りたい

以前投稿した、LINE ミニアプリにてショートカットを追加する機能が使われているかを調べる方法を調査しましたので投稿します。
Clock Icon2024.05.27 13:09

こんにちは、高崎@アノテーションです。

はじめに

以前、スマホのホーム画面に LINE ミニアプリのショートカットを追加する旨の記事にしましたが、合わせて、ショートカットから起動したのかを知る方法を調査・検討・実施しましたので記事にしました。

ミニアプリの起動について

通常起動

LINE Developers のコンテンツにてミニアプリを作成すると、ミニアプリの URL が用意されます(下記)。

リンクをブラウザで開くと、下記のように QR コードが表示されますので、最近のスマートフォンではカメラを向けると相応するコンテンツとしてミニアプリが起動されます(以下、通常起動と記載します)。

ショートカットからの起動

これは書くまでもないですね。

ショートカットを登録(上述のブログ参照)した後、ホーム画面に追加されたアイコンをタップする起動のことです(以下、ショートカット起動と記載します)。

方針

起動を区別するには

LINE ミニアプリの起動経路を区別する判定方法についてはクエリーパラメータを利用します。

ショートカット起動では URL に

https://miniapp.line.me/アプリケーションの LIFF ID?startFromShortcut=1

とクエリーパラメータを追加し、通常起動はパラメータ無しで起動するようにします。

このような仕掛けを行い、トップページ起動のイベントでクエリーパラメータの有無により判別します。

クエリーパラメータの有無の判断

一例を書いておきます。

import { useLocation } from "react-router-dom";
    :
  const location = useLocation();
  const query = new URLSearchParams(location.search);
  const parameter = query.get("queryParameter") || "";
    :

このような実装を行いますと、

https://何らかのURL?queryParameter=123

とした場合にparameter変数に123が入りますので、変数に値が入っているかを判定します。

更に一工夫

実は今回の調査には「とある要件」も追加されていました。

それは、こちら。

ショートカットをタップして起動された回数を記録する。

これはショートカットから起動したとしても、以下の場合は計測しないことになります。

  • ページを移動してトップページに戻った時
  • トップページにてリロードした時

そんなときに思い浮かぶのは下記のような実装です。

    :
  const typeNavigation = window?.performance?.navigation.type;
  if (typeNavigation === 0) {
    /* そのまま起動された処理 */
  } else {
    /* リロード、移動された場合 */
  }
    :

しかし、この実装はご存じかもしれませんが…

はい、現在は非推奨な実装なんですね。

そこで、下記のように実装します。

    :
  const navigationEntries = window?.performance?.getEntriesByType("navigation");
  if (navigationEntries.length > 0 && navigationEntries instanceof PerformanceNavigationTiming) {
    if (navigationEntries[0].type === "navigate") {
      /* そのまま起動された処理 */
    } else {
    /* リロード、移動された場合 */
  }
    :

実装

今回は下記のブログで作成した環境をベースにします。

ファイル構成を見直し

ついでに下記のブログを参考にして package.json や tsconfig.json を見直し(詳細な修正は割愛します)し、下記のファイル構成にしました(主要なファイルのみ記載)。

\
┣ iac
┃ ┣ bin
┃ ┃ ┗ liff-cdk-test.ts:cdk 起動時に呼ばれるスクリプト
┃ ┗ lib
┃   ┗ liff-cdk-test-stack.ts:CloudFormation 構築スクリプト
┣ LiffTest:npx @line/create-liff-app が自動生成
┃ ┣ src
┃ ┃ ┣ App.css:画面のスタイルシート定義
┃ ┃ ┣ App.tsx:メイン画面詳細構築スクリプト
┃ ┃ ┣ main.tsx:メイン画面生成スクリプト
┃ ┃ ┗ vite-env.d.ts:env 定義用スクリプト
┃ ┣ .env:環境変数設定ファイル ← clone した場合、このファイルを用意すること
┃ ┣ index.html:メイン画面
┃ ┗ package.json:LiffTest 内パッケージ定義ファイル
┗ package.json:全体のパッケージ定義ファイル

ソース修正

上述のファイル構成にてハイライトしたファイルが修正ファイルです。

パッケージ追加(LiffTest/src/package.json)

useLocationを使用するため、下記のコマンドを用いてreact-router-domをインストールします。

npm install -w LiffTest react-router-dom

main コンポーネント(LiffTest/src/main.tsx)

main.tsx の修正箇所をハイライトしました。

App コンポーネントでuseLocationを使いたいため、BrowserRouterコンポーネントを挟みました。

import React from "react";
import { BrowserRouter } from "react-router-dom";
import { createRoot } from 'react-dom/client';
import liff from "@line/liff";
import App from "./App";

const liffCreate = async () => {
  if (import.meta.env.VITE_ENV !== "prod") {
    const { default: VConsole } = await import("vconsole");
    new VConsole();
  }

  const container = document.getElementById("root");
  await liff.init({ liffId: import.meta.env.VITE_LIFF_ID });
  await liff.ready;

  if (!liff.isLoggedIn()) {
    liff.login();
    createRoot(container!).render(
      <React.StrictMode>
        <></>
      </React.StrictMode>
    );
  } else {
    createRoot(container!).render(
      <React.StrictMode>
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </React.StrictMode>,
    );
  }
}

liffCreate();

App コンポーネント(LiffTest/src/App.tsx)

App コンポーネントは以下の方針で修正しています。

  • ナビゲーションの調査を別関数にした
  • メインメッセージの出力をクエリーパラメータとナビゲーションを判定して変えるようにした
  • ショートカットにクエリーパラメータを追加

ソースです(修正箇所をハイライトしています)。

import React from "react";
import { useState } from "react";
import { useLocation } from "react-router-dom";
import liff from "@line/liff";
import "./App.css";

const isNavigateAccess = (): boolean => {
  // ナビゲーションエントリーを取得
  const navigationEntries = window?.performance?.getEntriesByType("navigation");
  if (navigationEntries.length > 0 && navigationEntries[0] instanceof PerformanceNavigationTiming) {
    return (navigationEntries[0].type === "navigate");
  }
  return false;
}

const App = () => {
  // クエリーパラメータ取得
  const location = useLocation();
  const query = new URLSearchParams(location.search);
  const paramHomeScreen = query.get("createHomeScreen") || "";

  // 開始メッセージ構築
  // クエリーパラメータ有り、かつ URL 指定起動(更新や戻るで移動していない)のものを選別
  const startMessage = (paramHomeScreen !== "" && isNavigateAccess()) ?
    "Started from home screen." :
    "Started from direct access.";
  
  // 初期メッセージ組み込み
  const [message, setMessage] = useState(startMessage);
  const [error, setError] = useState("");

  const handleClick = async () => {
    // ショートカット URL 作成(クエリーパラメータ付与)
    liff.createShortcutOnHomeScreen({ url: `https://miniapp.line.me/${import.meta.env.VITE_LIFF_ID}?createHomeScreen=1`})
      .then(() => {
        setMessage("createShortcutOnHomeScreen succeeded.");
      })
      .catch((e: Error) => {
        console.log(e);
        setMessage("createShortcutOnHomeScreen failed.");
        setError(`${e}`);
      });
    }

  return (
    <div className="App">
      <h1>create-liff-app</h1>
      {message && <p>{message}</p>}
      <p>
        <button onClick={handleClick}>リンクを作成</button>
      </p>
      {error && 
        <p>
          <code>{error}</code>
        </p>
      }
      <a
        href="https://developers.line.biz/ja/docs/liff/"
        target="_blank"
        rel="noreferrer"
      >
        LIFF Documentation
      </a>
    </div>
  );
}

export default App;

動作確認

ショートカット起動した時の画面です。

通常起動した時や、ショートカット起動してからリロードをした時の画面です。

このように表示が変わっていることがわかります。

おわりに

今回は LINE ミニアプリにおける起動方法により表示を変える仕組みを検討した実装を行いました。

ほぼ TypeScript の仕組みで恐縮ですが、皆様のご参考になれば幸いです。

ご参考までに今回のソースを下記の GitHub に保存しました。

参考文献

アノテーション株式会社について

アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。

サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。

当社は様々な職種でメンバーを募集しています。

「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。

この記事をシェアする

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.