高齢者向けの行商アプリ、素晴らしいアイデアですね。ログイン機能なしでの実現可能性について検討しましょう。
技術的には可能ですが、いくつかの工夫が必要です:
// sw.js または初回アクセス時
// デバイス固有のIDを生成して保存
const deviceId = localStorage.getItem('deviceId') ||
crypto.randomUUID();
localStorage.setItem('deviceId', deviceId);
// Push通知の購読
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey
});
// サーバーに購読情報を送信(deviceIdと紐付け)
await fetch('/api/subscribe', {
method: 'POST',
body: JSON.stringify({
deviceId,
userType: 'customer', // or 'vendor'
vendorCode: 'ABC123', // 行商のコード
subscription
})
});
[行商アプリ] ─→ サーバー ─→ [客アプリ1, 客アプリ2, ...]
↑ ↓
└──────────────────────────────┘
✅ パスワード不要で高齢者に優しい
✅ アプリを開くだけで使える
✅ デバイスを変えない限り再設定不要
⚠️ デバイスを変えたら再セットアップが必要
⚠️ アプリをアンインストールすると再設定が必要
⚠️ 複数デバイスで使えない(スマホとタブレット両方など)
高齢者でも使える簡単な認証方法:
1. 電話番号入力(数字のみ、大きいボタン)
2. SMSで4桁の番号が届く
3. 4桁入力で完了
初回のみ:
- 行商が客に4桁の番号を渡す(紙に書いて)
- その番号を入力するだけ
- 以降は自動ログイン状態を維持
「ログインなし + 初回QRコードペアリング」方式をお勧めします:
行商側の操作:
客側の操作:
セキュリティ:
この方式で実装を進めますか?それとも、電話番号認証などの別の方式をご希望ですか?
PIN方式での実装プランを提案します!
// models/Vendor.js - 行商
{
vendorId: String, // 自動生成ID
name: String, // 行商の名前(例:「山田鮮魚店」)
phoneNumber: String, // 連絡先
route: String, // 巡回ルート名
createdAt: Date
}
// models/Customer.js - 顧客
{
deviceId: String, // デバイス識別用
pin: String, // 4桁PIN(暗号化)
vendorId: String, // 紐付けられた行商ID
name: String, // 顧客名(任意)
address: String, // 住所(任意)
pushSubscription: Object, // Push通知情報
lastStatus: {
date: Date,
isHome: Boolean // true=在宅, false=留守
},
createdAt: Date
}
// models/Notification.js - 通知履歴
{
vendorId: String,
date: Date,
type: String, // 'vendor_coming' or 'customer_away'
message: String,
sentTo: [String], // 送信先のdeviceId配列
createdAt: Date
}
初回起動時のみ
[行商モードで開始]
↓
店名入力: 「山田鮮魚店」
↓
自動生成: vendorId と 顧客用PIN一覧
↓
画面に表示: 「顧客に配布するPIN一覧」
毎日の操作
アプリを開く
↓
[今日は行きます] ボタン(大きく表示)
↓
全顧客にプッシュ通知送信
↓
「通知を送りました!」
初回起動時のみ
[お客様モードで開始]
↓
「行商さんからもらった4桁の番号を入力」
[□□□□] (大きい数字ボタン)
↓
プッシュ通知の許可
↓
完了!
留守の時だけ
アプリを開く
↓
[今日は留守です] ボタン(大きく表示)
↓
行商にプッシュ通知送信
↓
「行商さんに伝えました!」
// PIN生成(行商登録時)
function generateCustomerPIN() {
// 4桁のランダムな数字
return Math.floor(1000 + Math.random() * 9000).toString();
}
// PIN検証(顧客登録時)
async function verifyPIN(pin, deviceId) {
// PINが使用済みでないことを確認
const existing = await Customer.findOne({ pin });
if (existing && existing.deviceId !== deviceId) {
throw new Error('このPINは既に使用されています');
}
// PINが有効なものか確認(未使用のPINプールから検索)
const validPin = await PINPool.findOne({
pin,
isUsed: false
});
return validPin;
}
// Fastify Routes
// 行商登録
POST /api/vendor/register
{
name: "山田鮮魚店",
phoneNumber: "090-1234-5678"
}
→ { vendorId, pins: ["1234", "5678", ...] } // 20個程度生成
// 顧客登録
POST /api/customer/register
{
pin: "1234",
deviceId: "uuid-xxx",
pushSubscription: {...}
}
→ { success: true, vendorName: "山田鮮魚店" }
// 行商が通知送信
POST /api/vendor/notify
{
vendorId: "xxx",
message: "今日は予定通り伺います"
}
→ { sent: 15 } // 送信件数
// 顧客が不在通知
POST /api/customer/notify-away
{
deviceId: "uuid-xxx"
}
→ { success: true }
// manifest.json
{
"name": "行商アプリ",
"short_name": "行商",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2196f3",
"icons": [...]
}
// sw.js (Service Worker)
self.addEventListener('push', (event) => {
const data = event.data.json();
// 高齢者向けに大きく分かりやすい通知
const options = {
body: data.message,
icon: '/icon-192.png',
badge: '/badge.png',
vibrate: [200, 100, 200],
requireInteraction: true, // 手動で閉じるまで表示
actions: [
{
action: 'open',
title: 'アプリを開く'
}
]
};
event.waitUntil(
self.registration.showNotification('🐟 行商からのお知らせ', options)
);
});
この方式で実装を進めて、コードを作成しましょうか?
それとも、何か修正したい点はありますか?