APP CODE 1
A1) PWA lazima iwe kwenye HTTPS
https://faulink.com ✅
localhost pia inakubali wakati wa dev.
A2) Hakikisha una hizi files 3
manifest.json (au manifest.webmanifest)
sw.js (service worker)
offline.html (ukurasa wa offline)
Sehemu B: Code za msingi za PWA (weka/rekebisha)
B1) manifest.json (toleo la PRO kwa Faulink)
Weka kwenye root: /manifest.json
{
"name": "Faulink Systems",
"short_name": "Faulink",
"id": "/",
"start_url": "/index.php?source=pwa",
"scope": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#0b1220",
"theme_color": "#0066cc",
"lang": "sw",
"description": "Faulink ni mfumo wa kisasa wa kidigitali kwa shule na biashara: usimamizi wa taarifa, malipo, ripoti na ufuatiliaji kwa urahisi na usalama.",
"icons": [
{ "src": "/assets/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/assets/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/assets/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
✅ Muhimu:
id iwe simple path (mf. "/"). Usiiweke kama string yenye JSON ndani.
start_url weka na ?source=pwa (optional) ili ujue watumiaji wa PWA.
icons: angalau 192 na 512.
B2) sw.js (service worker ya production)
Weka kwenye root: /sw.js
'use strict';
const SW_VERSION = 'v1.0.0';
const CACHE_STATIC = `static-${SW_VERSION}`;
const CACHE_PAGES = `pages-${SW_VERSION}`;
const CACHE_ASSETS = `assets-${SW_VERSION}`;
const OFFLINE_URL = '/offline.html';
const STATIC_ASSETS = [
OFFLINE_URL,
'/manifest.json'
];
self.addEventListener('install', (event) => {
event.waitUntil((async () => {
const cache = await caches.open(CACHE_STATIC);
await cache.addAll(STATIC_ASSETS);
self.skipWaiting();
})());
});
self.addEventListener('activate', (event) => {
event.waitUntil((async () => {
const keys = await caches.keys();
await Promise.all(
keys
.filter((k) => ![CACHE_STATIC, CACHE_PAGES, CACHE_ASSETS].includes(k))
.map((k) => caches.delete(k))
);
await self.clients.claim();
})());
});
const isNavigationRequest = (req) =>
req.mode === 'navigate' ||
(req.method === 'GET' && (req.headers.get('accept') || '').includes('text/html'));
async function cacheFirst(req) {
const cache = await caches.open(CACHE_ASSETS);
const cached = await cache.match(req);
if (cached) return cached;
const res = await fetch(req);
if (res && res.ok) cache.put(req, res.clone());
return res;
}
async function networkFirst(req) {
const cache = await caches.open(CACHE_PAGES);
try {
const res = await fetch(req);
if (res && res.ok) cache.put(req, res.clone());
return res;
} catch {
const cached = await cache.match(req);
return cached || caches.match(OFFLINE_URL);
}
}
self.addEventListener('fetch', (event) => {
const req = event.request;
if (req.method !== 'GET') return;
const url = new URL(req.url);
if (url.origin !== self.location.origin) return;
if (isNavigationRequest(req)) {
event.respondWith(networkFirst(req));
return;
}
const isAsset =
url.pathname.startsWith('/assets/') ||
['.css', '.js', '.png', '.jpg', '.jpeg', '.webp', '.svg', '.ico', '.woff', '.woff2', '.ttf']
.some(ext => url.pathname.endsWith(ext));
if (isAsset) {
event.respondWith(cacheFirst(req));
return;
}
event.respondWith((async () => {
const cached = await caches.match(req);
try {
return await fetch(req);
} catch {
return cached || caches.match(OFFLINE_URL);
}
})());
});
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
});
✅ Ukitoa update baadaye, badilisha SW_VERSION (mf. v1.0.1) ili cache ibadilike.
B3) offline.html
Weka: /offline.html
<!doctype html>
<html lang="sw">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="theme-color" content="#0b1220" />
<title>Upo Offline</title>
<style>
body{font-family:system-ui,Segoe UI,Roboto,Arial;margin:0;background:#0b1220;color:#fff}
.wrap{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.card{max-width:560px;width:100%;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);
border-radius:16px;padding:22px;box-shadow:0 10px 30px rgba(0,0,0,.35)}
h1{margin:0 0 10px;font-size:22px}
p{margin:0 0 16px;opacity:.9;line-height:1.5}
button{border:0;background:#fff;color:#0b1220;padding:10px 14px;border-radius:12px;cursor:pointer;font-weight:700}
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<h1>Upo Offline</h1>
<p>Hakuna intaneti kwa sasa. Rudia tena baada ya mtandao kurudi.</p>
<button onclick="location.reload()">Jaribu Tena</button>
</div>
</div>
</body>
</html>
B4) Code ya kuweka kwenye index.php (head + register SW)
Ndani ya <head> weka:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#0066cc">
<link rel="apple-touch-icon" href="/assets/icons/icon-192.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="Faulink">
Kabla ya </body> weka SW registration:
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const reg = await navigator.serviceWorker.register('/sw.js', { scope: '/' });
// auto refresh on update (optional)
reg.addEventListener('updatefound', () => {
const w = reg.installing;
if (!w) return;
w.addEventListener('statechange', () => {
if (w.state === 'installed' && navigator.serviceWorker.controller) {
w.postMessage({ type: 'SKIP_WAITING' });
}
});
});
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
});
} catch (e) {
console.warn('SW failed:', e);
}
});
}
</script>
Sehemu C: Ku-test kabla ya PWABuilder
Fungua hizi kwenye browser:
https://faulink.com/manifest.json
lazima ionyeshe JSON, isiwe 404
https://faulink.com/sw.js
https://faulink.com/offline.html
https://faulink.com/assets/icons/icon-512.png
Kama moja inakataa, PWABuilder atakwama.
Sehemu D: PWABuilder → kupata Android App Bundle (AAB)
D1) Ingia PWABuilder
Nenda PWABuilder
Weka URL ya site yako (au direct manifest URL)
Inafanya “analysis”
D2) Rekebisha Action Items
Kama kuna warnings, hakikisha:
description ipo
id ipo na ni sahihi (mf. "/")
orientation ipo
D3) Store ready
Ukiona “Awesome! Your PWA is store ready!”
Bonyeza Android → Generate Package
D4) Jaza details za Android package
Itakuuliza:
Application Id / Package name (mf. com.faulink.systems)
App name
Version
Kisha itatengeneza package na utapata Download .aab.
✅ File muhimu kwa Play Store ni .aab.
Sehemu E: Google Play Console (ku-upload AAB mpaka live)
E1) Fungua Play Console
Unda account (kuna fee ya mara moja)
Create app
Chagua:
App type: App
Free or Paid: chagua unavyotaka
Default language: Swahili/English
E2) Upload AAB
Nenda:
Release → Production → Create new release
Upload .aab
Save
E3) Store Listing (lazima)
Andaa:
App name
Short description
Full description
App icon 512×512
Feature graphic 1024×500
Screenshots (2–8 recommended; 1080×1920 ni standard)
E4) Privacy Policy (lazima)
Unda page kwenye site yako mfano:
https://faulink.com/privacy-policy
Kisha uweke link hiyo Play Console.
E5) App Content / Data Safety (lazima)
Utaulizwa:
App inakusanya data gani (mf. email, name, etc.)
Inatumika vipi
Kama data inasharekwa
Jibu kweli kulingana na mfumo wako.
E6) Submit for review
Ukishajaza kila kitu, “Submit” — kisha subiri review.
Sehemu F: Model yako ya biashara (2 days trial + manual activation) iwe SAFE kwa Play Store
Wewe unafanya:
Trial → lock → user anakulipa nje → wewe unamactivate kupitia website ✅
Ili isiwe issue kwenye review:
Usionyeshe “Lipia hapa M-Pesa” ndani ya app kama “digital unlock button”
Badala yake, onyesha message ya support:
Message ya kuonyesha trial ikisha (mfano)
“Trial imeisha. Tafadhali wasiliana na Faulink kwa ajili ya ku-activate huduma.”
Hii ni clean.
Sehemu G: “Kitu kinachowachanganya watu” (common mistakes)
Manifest link kwenye head haifanani na file uliyoupdate
hakikisha: <link rel="manifest" href="/manifest.json">
Cache ya browser/service worker
badilisha SW_VERSION kila update + Ctrl+F5
Icon 512 haipatikani (404)
start_url inaredirect loop
Sehemu H: Vitu utakavyohitaji kwa Play Store (quick checklist)
✅ .aab kutoka PWABuilder
✅ Privacy Policy URL
✅ Screenshots
✅ Feature graphic 1024×500
✅ Description
✅ App category (Education/Business)
✅ Data safety answers
Ukiwa tayari, niambie vitu 2 tu: