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.

Kom igång i din site (t.ex. byggd med Lovable)

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

MetodBeskrivning
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 / restoreIn-App Purchase via StoreKit (App Store-produkter)
share(opts)iOS delningsark (text/länk)
clipboard.write / readUrklipp
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 / getBrightnessHåll skärmen vaken, styr ljusstyrka
flashlight(on)Ficklampa
notifications.requestPermission / schedule / setBadgeLokala 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 / requestPaymentApple Pay (kräver merchant-ID)
nfc.isAvailable / scanLäs NFC-taggar (kräver riktig iPhone)
health.available / stepsHä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.