AppWrapper API
Allt siten kan göra när den körs i appen — med exempel för varje funktion.
AppWrapper är en native iOS-app (Android kommer) som visar din webbsite i fullskärm
och ger den tillgång till telefonens native-funktioner via ett JavaScript-API.
Vilken site som visas styrs av config-sidan — appen hämtar
GET /api/config vid varje start.
När siten körs inuti appen finns objektet window.AppWrapper.
I en vanlig webbläsare finns det inte — koda alltid med en koll:
if (window.AppWrapper) {
// Körs i appen — native-funktioner tillgängliga
const pos = await AppWrapper.location.get();
} else {
// Vanlig webbläsare — använd webbens API:er eller dölj funktionen
}
Alla metoder returnerar Promises. Fel (nekad behörighet,
funktion som saknas m.m.) kastas som Error med ett läsbart meddelande
— använd try/catch. Första anropet till en funktion visar iOS
behörighetsdialog för användaren.
Översikt
| Metod | Beskrivning |
|---|---|
device.info() | Enhetsmodell, OS-version, språk, appversion |
location.get() | Aktuell GPS-position |
camera.takePhoto(opts) | Öppna kameran, ta foto → base64-JPEG |
camera.recordVideo(opts) | Spela in video → base64 |
audio.startRecording() / stopRecording() | Ljudinspelning via mikrofonen → base64-m4a |
speech.startListening(opts) / stopListening() | Tal-till-text med löpande delresultat |
speech.speak(text, opts) | Text-till-tal |
motion.start(opts) / stop() | Gyro, accelerometer och attityd som eventström |
haptic(style) | Haptisk feedback |
payments.getProducts / purchase / restore | In-App Purchase via StoreKit (App Store-produkter) |
share(opts) | iOS delningsark (text/länk) |
clipboard.write / read | Urklipp |
openUrl(url) / openSettings() | Öppna länk externt (även tel:, mailto:) / appens inställningar |
battery() | Batterinivå och laddstatus |
network.status() | Online/offline + wifi/mobil |
screen.keepAwake / setBrightness / getBrightness | Håll skärmen vaken, styr ljusstyrka |
flashlight(on) | Ficklampa |
notifications.requestPermission / schedule / setBadge | Lokala notiser och app-badge |
biometrics.authenticate(reason) | Face ID / Touch ID |
photos.pick(opts) | Välj foto ur bildbiblioteket → base64 |
scanner.scan() | Skanna QR-/streckkod med kameran |
contacts.pick() | Välj en kontakt (namn, telefon, e-post) |
calendar.addEvent(opts) | Lägg till kalenderhändelse |
notifications.registerPush() | Registrera enheten för push från servern |
applePay.canMakePayments / requestPayment | Apple Pay (kräver merchant-ID) |
nfc.isAvailable / scan | Läs NFC-taggar (kräver riktig iPhone) |
health.available / steps | Hälsodata, t.ex. steg per dag |
on(event, cb) / off(event, cb) | Lyssna på events (motion, speechPartial) |
Referens
AppWrapper.device.info()
Returnerar { model, system, name, language, appVersion }.
const info = await AppWrapper.device.info();
// { model: "iPhone", system: "iOS 26.5", language: "sv-SE", ... }
AppWrapper.location.get()
Hämtar aktuell position. Returnerar
{ latitude, longitude, accuracy, altitude, speed, heading, timestamp }
(accuracy i meter, timestamp i millisekunder).
try {
const pos = await AppWrapper.location.get();
console.log(pos.latitude, pos.longitude);
} catch (e) {
// T.ex. "Behörighet nekad: plats"
}
AppWrapper.camera.takePhoto(opts?)
Öppnar kameran. Alternativ: maxSize (längsta sida i pixlar,
standard 1600) och quality (JPEG-kvalitet 0.1–1.0, standard 0.8).
Returnerar { base64, mimeType: "image/jpeg", width, height }.
const photo = await AppWrapper.camera.takePhoto({ maxSize: 1200 });
img.src = "data:image/jpeg;base64," + photo.base64;
AppWrapper.camera.recordVideo(opts?)
Öppnar videokameran. Alternativ: maxDuration (sekunder, standard 60).
Returnerar { base64, mimeType: "video/quicktime" }.
Håll inspelningarna korta — resultatet överförs som base64.
AppWrapper.audio.startRecording() / stopRecording()
startRecording() börjar spela in från mikrofonen.
stopRecording() returnerar { base64, mimeType: "audio/m4a", duration }.
await AppWrapper.audio.startRecording();
// ... användaren pratar ...
const rec = await AppWrapper.audio.stopRecording();
audio.src = "data:audio/m4a;base64," + rec.base64;
AppWrapper.speech.startListening(opts?) / stopListening()
Tal-till-text. Alternativ: locale (standard "sv-SE").
Delresultat kommer löpande som eventet speechPartial;
stopListening() returnerar { text } med sluttexten.
AppWrapper.on("speechPartial", (p) => (output.textContent = p.text));
await AppWrapper.speech.startListening({ locale: "sv-SE" });
// ... senare ...
const result = await AppWrapper.speech.stopListening();
console.log(result.text);
AppWrapper.speech.speak(text, opts?)
Läser upp texten. Alternativ: locale (standard "sv-SE"),
rate (0–1) och pitch (0.5–2). Promisen löses när uppläsningen är klar.
await AppWrapper.speech.speak("Hej! Din order är klar.");
AppWrapper.motion.start(opts?) / stop()
Strömmar rörelsedata som eventet motion. Alternativ:
interval (sekunder mellan uppdateringar, standard 0.1).
Varje event innehåller acceleration (G, utan gravitation),
rotationRate (rad/s — gyro) och attitude
(roll/pitch/yaw i radianer).
AppWrapper.on("motion", (m) => {
console.log(m.rotationRate.x, m.attitude.roll);
});
await AppWrapper.motion.start({ interval: 0.05 });
// ... senare ...
await AppWrapper.motion.stop();
Simulatorn saknar rörelsesensorer — anropet ger då ett fel.
AppWrapper.haptic(style?)
Haptisk feedback: "light", "medium" (standard),
"heavy", "success", "warning" eller "error".
AppWrapper.haptic("success");
AppWrapper.payments — In-App Purchase
getProducts(ids) hämtar produkter, purchase(id) öppnar
Apples köpflöde, restore() återställer tidigare köp.
Kräver att produkterna är upplagda i App Store Connect för just den
wrapper-appen — utan det returneras tomma listor.
const products = await AppWrapper.payments.getProducts(["premium_month"]);
// [{ id, title, description, price, priceFormatted }]
const result = await AppWrapper.payments.purchase("premium_month");
// { success: true, transactionId, productId } — eller kastar fel vid avbrott
const restored = await AppWrapper.payments.restore(); // { restored: [ids] }
Viktigt om betalningar och Apple:
• Digitalt innehåll (premium, prenumerationer, virtuella varor) måste säljas via In-App Purchase ovan (riktlinje 3.1.1, Apple tar 15–30 %). Webbcheckout för digitalt leder till avslag i granskningen.
• Fysiska varor och tjänster ska däremot använda sitens vanliga webbcheckout (Stripe, Klarna m.m.) direkt i webbvyn — det är tillåtet och kräver inget av API:t.
• Apple Pay på webben fungerar inte i inbäddade webbvyer. Verifiering av köp bör dessutom ske på er server (transaktions-ID:t returneras för det).
AppWrapper.share(opts)
Öppnar iOS delningsark. { text?, url? } — minst ett krävs.
await AppWrapper.share({ text: "Kolla in min annons!", url: "https://sellavo.com/annons/123" });
AppWrapper.clipboard.write(text) / read()
await AppWrapper.clipboard.write("kopierad text");
const { text } = await AppWrapper.clipboard.read();
AppWrapper.openUrl(url) / openSettings()
Öppnar länken utanför appen — https: i Safari, och även
tel:, mailto:, sms:.
openSettings() öppnar appens sida i Inställningar (bra när en behörighet nekats).
await AppWrapper.openUrl("tel:+46701234567");
AppWrapper.battery() / network.status()
const b = await AppWrapper.battery(); // { level: 0.82, state: "unplugged" }
const n = await AppWrapper.network.status(); // { online: true, type: "wifi" }
AppWrapper.screen — vakenhet & ljusstyrka
keepAwake(true/false) hindrar skärmen från att slockna (t.ex. recept,
presentation). setBrightness(0–1) / getBrightness() styr ljusstyrkan.
AppWrapper.flashlight(on)
await AppWrapper.flashlight(true); // tänd
await AppWrapper.flashlight(false); // släck
AppWrapper.notifications — lokala notiser
requestPermission() → { granted }.
schedule({ title, body, seconds }) schemalägger en lokal notis.
setBadge(count) sätter siffran på appikonen (0 tar bort).
Push-notiser från server ingår inte ännu — kräver APNs-infrastruktur.
const { granted } = await AppWrapper.notifications.requestPermission();
if (granted) {
await AppWrapper.notifications.schedule({
title: "Ny bud!", body: "Någon har lagt bud på din annons.", seconds: 5
});
}
AppWrapper.biometrics.authenticate(reason)
Face ID/Touch ID. Returnerar { success, method } eller kastar fel.
Använd för att låsa känsliga vyer — men riktig inloggning måste fortfarande
verifieras på er server.
const auth = await AppWrapper.biometrics.authenticate("Lås upp ditt konto");
AppWrapper.photos.pick(opts?)
Öppnar bildbiblioteket (ingen behörighet krävs — iOS egen väljare).
Samma alternativ och svar som camera.takePhoto.
AppWrapper.scanner.scan()
Öppnar kameran och läser QR-/streckkoder. Returnerar
{ value, type } för första koden som hittas.
const code = await AppWrapper.scanner.scan(); // { value: "https://…", type: "org.iso.QRCode" }
AppWrapper.contacts.pick()
Öppnar iOS kontaktväljare (användaren väljer själv — ingen behörighet krävs).
Returnerar { name, phones, emails }.
AppWrapper.calendar.addEvent(opts)
{ title, startDate, endDate?, notes?, location? } — datum i millisekunder
(Date.getTime()). Utan endDate blir händelsen en timme.
await AppWrapper.calendar.addEvent({
title: "Visning", startDate: Date.now() + 86400000, location: "Storgatan 1"
});
AppWrapper.notifications.registerPush() — push från servern
Registrerar enheten för riktiga push-notiser: appen hämtar en APNs-token och
anmäler den automatiskt till config-tjänsten. Utskick görs sedan per site från
admin ("Skicka push") eller via
POST /api/admin/push/<app-id> med { title, body }.
const { token } = await AppWrapper.notifications.registerPush();
Aktivering (engångssetup per Apple-konto): skapa en push-nyckel
(.p8) under Keys i Apple Developer-portalen och lägg in Pages-secrets
APNS_KEY_ID, APNS_TEAM_ID och APNS_P8
(hela filinnehållet), samt sätt sitens Bundle-ID i admin.
Utan detta svarar utskicks-API:t med en tydlig felbeskrivning.
APNS_ENV=production som secret när apparna distribueras via App Store.
AppWrapper.applePay — Apple Pay
canMakePayments() → { available, withCards }.
requestPayment({ merchantId, amount, label, countryCode?, currencyCode? })
visar Apple Pay-arket och returnerar { authorized, paymentData } —
paymentData (base64) skickas till er betalväxel (t.ex. Stripe) som
debiterar på servern.
const check = await AppWrapper.applePay.canMakePayments();
if (check.available) {
const result = await AppWrapper.applePay.requestPayment({
merchantId: "merchant.com.cjb.sellavo", amount: 149, label: "Frakt"
});
// POST result.paymentData till er server → Stripe/Adyen debiterar
}
Kräver ett merchant-ID + Apple Pay-certifikat i Apple
Developer-portalen och Apple Pay-entitlement i appbygget. Gäller bara fysiska
varor/tjänster — digitalt innehåll ska använda payments (IAP) ovan.
AppWrapper.nfc — läs NFC-taggar
isAvailable() → { available }. scan() öppnar
iOS NFC-läsaren och returnerar { records: [text] } från taggen.
const tag = await AppWrapper.nfc.scan(); // { records: ["https://…"] }
Fungerar bara på riktig iPhone (7 eller senare) och kräver NFC-entitlement i appbygget — i simulatorn och utan entitlement fås ett tydligt fel.
AppWrapper.health — Hälsodata
available() → { available }.
steps({ days }) ber om läsbehörighet och returnerar steg per dag:
const data = await AppWrapper.health.steps({ days: 7 });
// { days: [{ date: "2026-08-25T00:00:00Z", steps: 8214 }, …] }
Kräver HealthKit-entitlement i appbygget. Fler datatyper (puls, sömn, vikt m.m.) läggs till när ett projekt behöver dem.
AppWrapper.app — sitens eget app-API
Om siten har en egen endpoint med app-data hämtar appen den vid varje start:
GET <site-URL>/api/public/app/ios (Android: /android) med headern
X-App-Bundle-Key: <APP_BUNDLE_KEY> — nyckeln sätts per site i
admin och ska verifieras i sitens backend.
const info = await AppWrapper.app.info();
// { appId: "sellavo", platform: "ios", bundleKey: "abk_…" }
// bundleKey kan användas av siten för egna app-verifierade anrop.
const data = await AppWrapper.app.data();
// Sitens svar från /api/public/app/ios, oförändrat (JSON).
// Kastar fel om endpointen saknas eller nyckeln är fel.
Datat cachas i appen, så senast hämtade version finns även offline. Svaret är helt upp till siten — appen tolkar det inte utan levererar det som det är.
Kvarstående begränsningar
Bakgrundskörning (appen kör bara när den är öppen) och att skriva hälsodata ingår inte. Apple Pay, push, NFC och Hälsa kräver dessutom de Apple-konton/entitlements som beskrivs ovan innan de fungerar på riktigt.
Behörigheter & säkerhet
• iOS frågar användaren om lov första gången kamera, mikrofon, position eller taligenkänning används. Nekas behörigheten får anropet ett fel.
• Bryggan svarar bara sidor på den värd (inklusive subdomäner) som är konfigurerad som site-URL i config-sidan. Länkar ut till andra domäner fungerar, men de sidorna får inte tillgång till native-API:t.
• Konfigurera bara siter du litar på — de får tillgång till telefonens sensorer via API:t ovan.
Config-API:t (för appen)
Varje wrapper-app är knuten till ett app-ID och hämtar sin
config vid start med GET /api/config/<app-id>:
{
"siteUrl": "https://sellavo.com",
"appName": "Sellavo",
"themeColor": "#1A1A2E",
"splash": { "backgroundColor": "#F5F5F7", "text": "Sellavo" }
}
Siter skapas och redigeras i admin (kräver admin-token;
API-motsvarigheten är /api/admin/…). Appen rapporterar dessutom
en heartbeat vid varje start — POST /api/status/<app-id>
med appversion och om siten laddades OK — som visas i admin-listan, så du
ser direkt att allt kör korrekt efter en uppdatering i t.ex. Lovable.