webPush通知機能 完成!👏.これでブラウザ閉じても通知可能✌.
2024.11.12
おはようございます.金曜日の休みを使ってwebPush通知機能を追加しました、これでブラウザ閉じても通知されるようになります.Laravel側はお見せできませんがどういう技術を使用したか書いていきます.まずLaravelの拡張ライブラリを使用しました.
下記のリンクを参照しライブラリをインストールしてみてください.
https://laravel-notification-channels.com/webpush/#installation
インストール手順はリンク先に書いていますので、それを参照しその後フロント側を設定します.下記は作りかけのコードですが通知登録が可能でバックエンド側からスケジュールで通知を飛ばすことが出来ます.
フロント側はサービスワーカーのJSファイルとユーザー通知の許可行い、その情報をバックエンドに渡す処理ファイルが存在します.サービスワーカーJSコードはググれば書き方が出ているので検索してみてください.
公開するのは通知許可を行う方のファイルになります💁あとヒントとしてバックエンドも少し公開します.
const vapidPublicKey = import.meta.env.VITE_VAPID_PUBLIC_KEY;
async function setupPushNotifications() {
if (Notification.permission === 'granted') {
try {
// サービスワーカーを登録
await navigator.serviceWorker.register('/assets/js/npush-service-worker.js').then(async(registration) => {
if ('serviceWorker' in navigator && 'PushManager' in window) {
try {
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
});
const response = await fetch('/save-subscription', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
},
body: JSON.stringify(subscription),
});
if (!response.ok) throw new Error('サブスクリプションの保存に失敗しました');
new Notification('ご登録', {
body: 'ご登録有難う御座います',
});
} catch (error) {
console.error('Push subscription error:', error);
}
} else if (result !== 'granted') {
console.log('通知の権限が拒否されました。');
}
});
console.log('サービスワーカーが正常に登録されました');
} catch (error) {
console.error('サービスワーカーの登録に失敗しました:', error);
}
}
}
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
document.getElementById('enable-notifications').addEventListener('click', async () => {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
setupPushNotifications();
} else {
console.log('通知の権限が拒否されました。');
}
});
これで通知の識別データが取得し登録が可能になります.登録されたデータを元に個別通知、全員に通知などが可能になります.なお、会員登録されたユーザーのみ通知が飛ぶシステムです.
$endpoint = $request->endpoint;
$token = $request->keys['auth'];
$key = $request->keys['p256dh'];
$user = $request->user();
$user->updatePushSubscription($endpoint, $key, $token);
上記のデータがバックエンド側に保存されます.それを使用しユーザーにどのように送信すれば良いか?
use App\Notifications\Reserved;
$user = User::find(1);
$user->notify(new Reserved($reservation));
こんな感じだと思ってください.抜粋しているのであくまでもヒントです.あとはご自身で考えて対応してください.
明日へ続く
著者名 @taoka_toshiaki
※この記事は著者が40代前半に書いたものです.
Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki
タグ
addEventListener, assets, async, catch, document.getElementById, document.querySelector, headers, installation, keys, METHOD, padding, permission, rawData.charCodeAt, rawData.length, registration, repeat, replace, save-subscription, subscription, then,
お天気APIを試してみました。#超簡単コード
2022.11.21
おはようございます、今日は天気APIの話なので天気のお話はしませんが…。
さて、超簡単コードを記載しています。下記のソースコードとオープンウェザーマップのAPIKEYがあれば、誰でもプログラマーです。という事を言えば後ろから蹴られそうですが…。先ずは簡単なコードを見て勉強するのが大事かなって思います。
いきなり難しいことを身に着けようと思っても、三日坊主になって先に進まないのが世の常ですw。なので、簡単なコードで楽しんで勉強するほうが大事かなって。
<?php
require "./assets/config.php";
$lat = "33.55972";
$lon = "133.53111";
$lang = "ja";
$url = "https://api.openweathermap.org/data/2.5/weather?units=metric&lang={$lang}&lat={$lat}&lon={$lon}&appid=".APIKEY;
$hasWeatherdata = json_decode(@file_get_contents($url));
print($hasWeatherdata->weather[0]->description);
print("<img src='./assets/images/". $hasWeatherdata->weather[0]->icon .".png.webp'>");
print((int)($hasWeatherdata->main->temp) . "℃");
著者名 @taoka_toshiaki
※この記事は著者が40代前半に書いたものです.
Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki
タグ
0, 133.53111, 33.55972, API, APIKEY, assets, com, config, ea, https, ja, lang, lat, lon, lt, php, quot, require, url, w, watch, www, Xn-VCyGQ, youtube, ウェザー, お話, コード, こと, ソース, プログラマー, マップ, 三日坊主, 下記, 世の常, 事, 今日, 勉強, 大事, 天気, 後ろ, 簡単, 記載, 話, 誰, 超簡単,
androidアプリをはじめて申請しました。リリースされるかは?
2021.07.21
高知県も梅雨明け宣言したころだと思います、そんな事を考えながら雷の音を聞きながらこの記事を書いています。
この記事が公開された時にはandroidアプリがリリースされている頃だと思います。
この記事はアンドロイドアプリ(Flutterアプリ)を作ったときに戸惑ったところなどを本人しかわからない形で箇条書きにしたものです。他人が見てもわかるようには書いていません。
尚、参考にしたサイトはこちらになります。
https://passe-de-mode.uedasoft.com/ja/tips/software/device/flutter/flutter04.htm
flutter_icons:
android: true
ios: true
image_path: "lib/assets/icon.png.webp"
flutter pub get
flutter pub run flutter_launcher_icons:main
keytool -genkey -v -keystore C:\android\jks\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias key
signingConfigs {
release {
storeFile file("C:\\android\\jks\\key.jks")
storePassword "Password"
keyAlias "key"
keyPassword "Password"
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.release
}
}
flutter build appbundle
著者名 @taoka_toshiaki
※この記事は著者が40代前半に書いたものです.
Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki
タグ
04, Android, assets, com, device, fl, flutter, GET, htm, https, icon, icons, image, iOS, ja, lib, passe-de-mode, path, png, pub, quot, run, software, tips, true, uedasoft, アプリ, アンドロイド, こちら, ころ, サイト, とき, ところ, もの, リリース, 事, 他人, 公開, 参考, 宣言, 形, 時, 本人, 梅雨明け, 申請, 箇条書き, 記事, 雷, 音, 頃, 高知県,
asyncとawaitとthenの関係と使い道。
2021.01.27
asyncとawaitとthenの関係と使い道は?まず、関係性はこの3つでまぁ一つの役割を果たすと考えても良いかもしれません。どんな時に使うかは、例えば、innerHTMLに計算した値を渡したいとか…。いや違うな。同期処理として使うのが正解ですね。尚、asyncとawaitで同期処理とする。
ちなみにIE11では動かない処理なので氣をつけてご使用ください。今どき、IE11なんてと思うかもしれないけど、使っている人がいるから・・・。
document.getElementById("btn").addEventListener("click", async (e) => {
test1();
test2();
test3();
await test1();
await test2();
await test3();
});
function test1() {
return new Promise(resolve => {
setTimeout(() => {
console.log("btn = 1");
resolve('1');
}, 3000);
});
}
function test2() {
return new Promise(resolve => {
setTimeout(() => {
console.log("btn = 2");
resolve('2');
}, 2000);
});
}
async function test3() {
return new Promise(resolve => {
setTimeout(() => {
console.log("btn = 3");
resolve('3');
}, 100);
});
}
IE11を使っているパターン、例えば社長や上司がシニア世代だったりするとIE11をいまだに使っていたりする。そもそもWindows10にIE11がインストールされている時点で悪だ・・・と思う開発者もいるはずです。そろそろマイクロソフト、IE11なんてものを強制的に削除するようなバージョンアップをして戴きたいと思ってます。それぐらい問題だと。あと、IE11で引っかかったことがあります。新しいブラウザでは下記のコードをHTMLに記入すると察してくれてjsファイルを読み込んでくれるが、IE11は違うのだ。
<script src="./assets/js/common.js"></script>
これじゃ読み込んでくれない。厳密に書かないとファイルを読み込んでくださらない。やっぱ早くIE11の撲滅を願う。
<script type="text/javascript" src="./assets/js/common.js"></script>
著者名 @taoka_toshiaki
※この記事は著者が40代前半に書いたものです.
Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki
タグ
'src', addEventListener, assets, async, asyncとawait, await, await test, common.js", document.getElementById, gt, innerHTML, lt, quot, quot;btn, quot;text, resolve, setTimeout, then, 撲滅, 氣,
Yahoo japanサイトのコロナ情報リンク集です。好評に付きVer2。
2020.12.21
Yahoo japanサイトのコロナ情報リンク集です。好評に付きバージョン2を
作成しました。見た目はこんな感じです。県名をクリックするとYahoo!japanのコロナサイトの
画面が開きます(遷移します)。ほんとは、Yahoo!データをウェブスクレイピングしようと思ったのですが、別にそこまでする必要はないかなと思いとどまり、いまに至っています。
データは国もJSONで公開していますのでわざわざ作ることもないのですけど、自分が見やすいように、時間があれば改善していきます。
追記:時間があったので改善しました。
https://zip358.com/tool/COVID-19-japan/
var ken_json = {};
fetch("./assets/js/ken47.json").then(response => response.json()).then((data)=>{
let ken = [];
for (const key in data) {
ken.push("<a href='#' onClick='c19(\"" + "https://hazard.yahoo.co.jp/article/covid19" + data[key].roman +"\")' onmouseover='c19_f(\"" + data[key].roman.toUpperCase() + "\",\"red\")'' onmouseout='c19_f(\"" + data[key].roman.toUpperCase() + "\",\"#fff\")'>" + data[key].name + "</a>");
}
document.getElementById("covid19-link-list").innerHTML = ken.join(",");
for (const key in data) {
document.getElementById(data[key].roman.toUpperCase()).setAttribute("fill","#fff");
}
});
function c19(url){
window.open(url,"_blank");
}
function c19_f(kenid,color){
document.getElementById(kenid).setAttribute("fill",color);
}
{
"1": {
"name": "北海道",
"roman": "hokkaido"
},
"2": {
"name": "青森",
"roman": "aomori"
},
"3": {
"name": "岩手",
"roman": "iwate"
},
"4": {
"name": "宮城",
"roman": "miyagi"
},
"5": {
"name": "秋田",
"roman": "akita"
},
"6": {
"name": "山形",
"roman": "yamagata"
},
"7": {
"name": "福島",
"roman": "fukushima"
},
"8": {
"name": "茨城",
"roman": "ibaraki"
},
"9": {
"name": "栃木",
"roman": "tochigi"
},
"10": {
"name": "群馬",
"roman": "gunma"
},
"11": {
"name": "埼玉",
"roman": "saitama"
},
"12": {
"name": "千葉",
"roman": "chiba"
},
"13": {
"name": "東京",
"roman": "tokyo"
},
"14": {
"name": "神奈川",
"roman": "kanagawa"
},
"15": {
"name": "新潟",
"roman": "niigata"
},
"16": {
"name": "富山",
"roman": "toyama"
},
"17": {
"name": "石川",
"roman": "ishikawa"
},
"18": {
"name": "福井",
"roman": "fukui"
},
"19": {
"name": "山梨",
"roman": "yamanashi"
},
"20": {
"name": "長野",
"roman": "nagano"
},
"21": {
"name": "岐阜",
"roman": "gifu"
},
"22": {
"name": "静岡",
"roman": "shizuoka"
},
"23": {
"name": "愛知",
"roman": "aichi"
},
"24": {
"name": "三重",
"roman": "mie"
},
"25": {
"name": "滋賀",
"roman": "shiga"
},
"26": {
"name": "京都",
"roman": "kyoto"
},
"27": {
"name": "大阪",
"roman": "osaka"
},
"28": {
"name": "兵庫",
"roman": "hyogo"
},
"29": {
"name": "奈良",
"roman": "nara"
},
"30": {
"name": "和歌山",
"roman": "wakayama"
},
"31": {
"name": "鳥取",
"roman": "tottori"
},
"32": {
"name": "島根",
"roman": "shimane"
},
"33": {
"name": "岡山",
"roman": "okayama"
},
"34": {
"name": "広島",
"roman": "hiroshima"
},
"35": {
"name": "山口",
"roman": "yamaguchi"
},
"36": {
"name": "徳島",
"roman": "tokushima"
},
"37": {
"name": "香川",
"roman": "kagawa"
},
"38": {
"name": "愛媛",
"roman": "ehime"
},
"39": {
"name": "高知",
"roman": "kochi"
},
"40": {
"name": "福岡",
"roman": "fukuoka"
},
"41": {
"name": "佐賀",
"roman": "saga"
},
"42": {
"name": "長崎",
"roman": "nagasaki"
},
"43": {
"name": "熊本",
"roman": "kumamoto"
},
"44": {
"name": "大分",
"roman": "oita"
},
"45": {
"name": "宮崎",
"roman": "miyazaki"
},
"46": {
"name": "鹿児島",
"roman": "kagoshima"
},
"47": {
"name": "沖縄",
"roman": "okinawa"
}
}
著者名 @taoka_toshiaki
※この記事は著者が40代前半に書いたものです.
Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki
タグ
19, 2, 358, 47, assets, com, covid, fetch, gt, https, JAPAN, JS, json, ken, quot, response, then, tool, var, ver, www, Yahoo, zip, ウェブ, クリック, こと, コロナ, サイト, スク, そこ, データ, バージョン, ピング, ほんと, リンク集, レイ, わざわざ, 作成, 公開, 国, 好評, 必要, 情報, 感じ, 改善, 時間, 画面, 県名, 自分, 見た目, 追記, 遷移,
Yahooが567(コロナ)の情報を取り扱っているそれも県単位でURLまとめたよ。
2020.12.20
Yahooが567の情報を取り扱っている。567(コロナ)を県単位で情報を配信しているまとめリンクサイトを作りましたので、ご自由にお使いください。
リンクはこちらです。https://zip358.com/tool/demo28/
ソースコードとJSONを貼っときます。IT土方さんみたいな事をした?。
<script>
fetch("./assets/js/ken47.json").then(response => response.json()).then((data)=>{
let ken = [];
ken.push('<div class="list-group">');
for (const key in data) {
ken.push("<a class='list-group-item list-group-item-action' href='https://hazard.yahoo.co.jp/article/covid19" + data[key].roman + "' target='_"+ data[key].roman +"'>" + data[key].name +":::https://hazard.yahoo.co.jp/article/covid19" + data[key].roman + "</a>");
}
ken.push("</div>");
document.getElementById("covid19-link-list").innerHTML = ken.join("")
});
</script>
{
"1": {
"name": "北海道",
"roman": "hokkaido"
},
"2": {
"name": "青森",
"roman": "aomori"
},
"3": {
"name": "岩手",
"roman": "iwate"
},
"4": {
"name": "宮城",
"roman": "miyagi"
},
"5": {
"name": "秋田",
"roman": "akita"
},
"6": {
"name": "山形",
"roman": "yamagata"
},
"7": {
"name": "福島",
"roman": "fukushima"
},
"8": {
"name": "茨城",
"roman": "ibaraki"
},
"9": {
"name": "栃木",
"roman": "tochigi"
},
"10": {
"name": "群馬",
"roman": "gunma"
},
"11": {
"name": "埼玉",
"roman": "saitama"
},
"12": {
"name": "千葉",
"roman": "chiba"
},
"13": {
"name": "東京",
"roman": "tokyo"
},
"14": {
"name": "神奈川",
"roman": "kanagawa"
},
"15": {
"name": "新潟",
"roman": "niigata"
},
"16": {
"name": "富山",
"roman": "toyama"
},
"17": {
"name": "石川",
"roman": "ishikawa"
},
"18": {
"name": "福井",
"roman": "fukui"
},
"19": {
"name": "山梨",
"roman": "yamanashi"
},
"20": {
"name": "長野",
"roman": "nagano"
},
"21": {
"name": "岐阜",
"roman": "gifu"
},
"22": {
"name": "静岡",
"roman": "shizuoka"
},
"23": {
"name": "愛知",
"roman": "aichi"
},
"24": {
"name": "三重",
"roman": "mie"
},
"25": {
"name": "滋賀",
"roman": "shiga"
},
"26": {
"name": "京都",
"roman": "kyoto"
},
"27": {
"name": "大阪",
"roman": "osaka"
},
"28": {
"name": "兵庫",
"roman": "hyogo"
},
"29": {
"name": "奈良",
"roman": "nara"
},
"30": {
"name": "和歌山",
"roman": "wakayama"
},
"31": {
"name": "鳥取",
"roman": "tottori"
},
"32": {
"name": "島根",
"roman": "shimane"
},
"33": {
"name": "岡山",
"roman": "okayama"
},
"34": {
"name": "広島",
"roman": "hiroshima"
},
"35": {
"name": "山口",
"roman": "yamaguchi"
},
"36": {
"name": "徳島",
"roman": "tokushima"
},
"37": {
"name": "香川",
"roman": "kagawa"
},
"38": {
"name": "愛媛",
"roman": "ehime"
},
"39": {
"name": "高知",
"roman": "kochi"
},
"40": {
"name": "福岡",
"roman": "fukuoka"
},
"41": {
"name": "佐賀",
"roman": "saga"
},
"42": {
"name": "長崎",
"roman": "nagasaki"
},
"43": {
"name": "熊本",
"roman": "kumamoto"
},
"44": {
"name": "大分",
"roman": "oita"
},
"45": {
"name": "宮崎",
"roman": "miyazaki"
},
"46": {
"name": "鹿児島",
"roman": "kagoshima"
},
"47": {
"name": "沖縄",
"roman": "okinawa"
}
}
著者名 @taoka_toshiaki
※この記事は著者が40代前半に書いたものです.
Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki
タグ
39, 47, 567, assets, class, const, data, div, fetch, For, gt, in, IT, JS, json, ken, key, let, list-grou, list-group, list-group-item, lt, push, quot, response, script, then, url, Yahoo, コード, こちら, コロナ, サイト, ソース, それ, まとめ, リンク, 事, 単位, 土方, 情報, 県, 配信,