Referência de capacidades nativas 📇¶
Esta página cataloga todos os grupos de capacidade da ponte tempestweb.native
— um por seção, com uma frase de motivação e um trecho completo e executável. É a
referência de bolso do Trilho T (paridade com a plataforma web); para a
introdução didática ("uma API, três caminhos"), comece por
Capacidades.
Uma importação para tudo
Todo exemplo abaixo começa com a mesma linha:
A partir daí você chama native.<grupo>.<verbo>(...). A assinatura é a
mesma nos Modos A (WASM), B (servidor) e C (transpile) — o --mode escolhe
só como a chamada chega na Web API.
await vs async for — dois formatos¶
Há dois formatos de capacidade, e você sabe qual é pela forma como consome:
Um pedido, um resultado. A grande maioria das capacidades. Você await e
recebe o valor tipado de volta.
Uma assinatura, muitos eventos ao longo do tempo. Consumida com async for;
sair do laço (fim, break, cancelamento) fecha a assinatura automaticamente.
from tempestweb import native
async for pos in native.geolocation.watch(): # T-EV
app.set_state(lambda s: setattr(s, "here", pos))
As capacidades de stream correm sobre o canal de eventos nativo (T-EV) — veja o tutorial do canal de eventos.
No Modo B a chamada tem prazo
No Modo A a capacidade roda no mesmo processo. No Modo B ela é proxiada:
o servidor manda um native_call e espera o native_result do browser. Uma
aba fechada no meio, ou uma capacidade que quebrou antes de responder,
deixaria esse await suspenso para sempre — então ele falha com
NativeError("timeout") depois de
DEFAULT_NATIVE_CALL_TIMEOUT (30s), tempo de sobra para um prompt de
permissão ou um seletor de arquivo. Trate timeout como qualquer outro código
de erro; para mudar o prazo, construa a sessão com um
ProxyBridge(send_frame, timeout=...).
Contexto seguro e só-Chromium
Muitas capacidades exigem HTTPS (ou localhost) e algumas só existem no
Chromium (Chrome/Edge). Cada grupo de risco expõe um is_supported() para
você degradar com elegância — trate "não suportado" como fluxo normal, nunca
como crash.
Tier 1 — universal, barato, alto valor¶
Suporte amplo em todos os navegadores modernos. São a base da paridade PWA.
vibration — vibrar o dispositivo¶
Dê um retorno tátil (buzz) em um toque ou em um padrão on/off.
from tempestweb import native
async def on_success() -> None:
await native.vibration.vibrate([100, 50, 100]) # ms: vibra, pausa, vibra
badge — contador no ícone do PWA¶
Marque o ícone do app instalado com um número de não-lidos (ou um ponto genérico).
from tempestweb import native
async def sync_badge(unread: int) -> None:
if unread:
await native.badge.set_badge(unread) # 0 ou None também limpa
else:
await native.badge.clear()
wakelock — manter a tela acesa¶
Impeça o desligamento da tela durante uma leitura, receita ou vídeo. Guarde o id
que request() devolve para liberar depois.
from tempestweb import native
async def start_reading() -> str:
lock_id = await native.wakelock.request()
return lock_id
async def stop_reading(lock_id: str) -> None:
await native.wakelock.release(lock_id)
fullscreen — modo tela cheia¶
Entre e saia da tela cheia; leia o estado atual. Cada chamada devolve se a tela cheia está ativa depois.
from tempestweb import native
async def toggle_fullscreen() -> bool:
if await native.fullscreen.state():
await native.fullscreen.exit()
return False
return await native.fullscreen.enter()
network — condições de conexão¶
Leia (state) ou observe (watch, streaming) onLine, effectiveType,
downlink, rtt e saveData — ideal para adaptar a UI a redes lentas.
from tempestweb import native
async def read_network() -> None:
net = await native.network.state() # → NetworkState
print(net.online, net.effective_type, net.save_data)
async def follow_network() -> None:
async for net in native.network.watch(): # streaming (T-EV)
app.set_state(lambda s: setattr(s, "online", net.online))
imaging — comprimir, miniaturar e transformar antes do upload¶
Entre camera.capture() e http.upload() não havia nada: a app capturava uma
foto de 4 MB e subia 4 MB, ou reescrevia compressão com canvas na mão.
from tempestweb import native
async def upload_photo() -> None:
photo = await native.camera.capture(include_bytes=False)
small = await native.imaging.compress(photo, max_kb=200, max_width=1600)
print(small.size_kb, small.quality, small.attempts, small.within_budget)
await native.http.upload("/api/fotos", small.as_upload("foto.jpg"))
Os pixels ficam no browser
Toda função aqui recebe e devolve um handle opaco para bytes que o cliente está segurando. O Python endereça a imagem pelo nome; a imagem não atravessa a ponte:
Modo B, foto de 4 MB, comprimindo:
bytes: cliente →5,3MB→ servidor →5,3MB→ cliente (10,6 MB de rede)
handle: cliente →"blob:tw:7"→ servidor →"blob:tw:7"→ cliente (~40 bytes)
camera.capture(include_bytes=False) estende isso à primeira travessia, e
small.as_upload(nome) à última — o servidor recebe os bytes, o Python nunca.
Medido em Chrome 150, foto de 4000×3000 com estrutura (gradiente + formas):
| original | 871,5 KB |
depois de compress(max_kb=200, max_width=1600) |
124,8 KB (−85,7%) |
| qualidade escolhida | 0,91 |
| encodes gastos | 5 |
within_budget |
True |
| tempo | 545 ms |
| miniaturas 96 / 256 px | 4,9 KB / 29,7 KB |
Orçamento impossível responde, não trava
A busca de qualidade é binária e limitada por steps (6 por default).
Medido com ruído puro de 9,4 MB e orçamento de 200 KB: parou em 5 encodes com
within_budget=False e size_kb=573,9 — o menor que conseguiu. Uma imagem
grande demais que a app decide o que fazer vale mais que um spinner infinito.
Cheque o within_budget.
Quatro capacidades, mais duas de manutenção:
| Chamada | Devolve |
|---|---|
compress(source, max_kb=, max_width=, ...) |
CompressedImage(ref, size_kb, quality, attempts, within_budget, …) |
thumbnails(source, [96, 256]) |
list[Thumbnail], na ordem pedida |
transform(source, width=, rotate=, crop=, flip_horizontal=) |
ProcessedImage — tudo numa passada só |
info(source) |
ImageInfo(mime_type, width, height, size_kb), sem re-encodar |
read(source) |
ImageBytes — a saída de emergência, move a imagem inteira |
release(source) / release(all=True) |
libera handle |
Opção com nome errado levanta
CompressOptions e TransformOptions são extra="forbid":
compress(photo, maxWidth=1600) levanta em vez de ignorar em silêncio — o
silêncio subiria a foto em tamanho original e ninguém saberia. Já
CompressedImage, Thumbnail e afins ignoram campo desconhecido, senão
um cliente novo quebraria um Python antigo.
Handle é limitado, e handle vencido é erro nomeado
O cliente segura algumas dezenas de blobs e descarta o mais antigo, para uma
tela de captura rodando uma hora não acumular todo frame. Endereçar um handle
vencido levanta NativeError("not_found") — recupere capturando de novo, não
tentando de novo.
device — memória, núcleos e heap, para qualidade adaptativa¶
Descreve grosseiramente a máquina do usuário, para a app decidir se comprime a foto mais, cacheia menos, ou desiste de rodar o modelo ONNX localmente.
from tempestweb import native
async def choose_quality() -> int:
profile = await native.device.profile() # → DeviceProfile(memory_gb, cores, heap_used_mb, heap_limit_mb)
if profile.memory_gb is not None and profile.memory_gb <= 2:
return 60
network = await native.network.state()
if network.save_data or network.effective_type in {"slow-2g", "2g", "3g"}:
return 70
return 85
Todo campo é opcional, e None não quer dizer "fraco"
navigator.deviceMemory e performance.memory são só-Chromium. No Safari e
no Firefox a chamada funciona e responde None na maior parte. Uma app
que lê None como "aparelho fraco" degrada todo iPhone para o pior nível
de qualidade — o oposto do que adaptar queria. Ramifique sobre valor
conhecido e deixe o desconhecido cair no seu default.
Só hardware mora aqui
Tipo de conexão é network e uso de
armazenamento é quota. Repetir
os dois aqui daria dois nomes ao mesmo fato no contrato, e os dois nomes
driftariam.
Isto é para adaptar qualidade, não para identificar ninguém
Os campos são grosseiros de propósito. Não mande isto a lugar nenhum como identificador.
Medido em Chrome 150: memory_gb=32, cores=12, heap_used_mb=2.5,
heap_limit_mb=4192. O memory_gb é quantizado em potência de dois e o browser
pode capar — compare com <= contra um limite baixo, não com um valor exato.
visibility — aba em foco ou oculta¶
Saiba se a página está "visible" ou "hidden" — pause animações/polling quando
o usuário troca de aba.
from tempestweb import native
async def pause_when_hidden() -> None:
async for vis in native.visibility.watch(): # streaming (T-EV)
app.set_state(lambda s: setattr(s, "playing", vis == "visible"))
orientation — orientação da tela¶
Trave/destrave a orientação e leia o tipo/ângulo atuais; observe rotações em stream.
from tempestweb import native
async def lock_landscape() -> bool:
return await native.orientation.lock("landscape") # requer fullscreen
async def follow_rotation() -> None:
async for o in native.orientation.watch(): # streaming (T-EV)
app.set_state(lambda s: setattr(s, "angle", o.angle))
quota — uso e persistência de armazenamento¶
Estime uso/quota do origin e peça armazenamento persistente (isento de
despejo sob pressão). Pareia com storage/offline.
from tempestweb import native
async def ensure_durable() -> None:
est = await native.quota.estimate() # → StorageEstimate(usage, quota)
if not await native.quota.persisted():
await native.quota.persist()
clipboard (imagem) — copiar/colar imagens¶
Além de read/write de texto, agora lê e escreve imagens (base64 + MIME).
from tempestweb import native
async def paste_image() -> None:
img = await native.clipboard.read_image() # → ClipboardImage
app.set_state(lambda s: setattr(s, "png_b64", img.data_base64))
async def copy_image(png_b64: str) -> None:
await native.clipboard.write_image(png_b64, mime_type="image/png")
battery — nível e carga (streaming)¶
Observe nível, estado de carga e tempos estimados. Só streaming — cada mudança
emite um BatteryStatus fresco.
from tempestweb import native
async def follow_battery() -> None:
async for b in native.battery.watch(): # streaming (T-EV)
app.set_state(lambda s: setattr(s, "battery", b.level))
sensors — orientação e movimento (streaming)¶
Leituras contínuas do acelerômetro/giroscópio via Device Orientation / Motion.
from tempestweb import native
async def follow_tilt() -> None:
async for o in native.sensors.orientation(): # streaming (T-EV)
app.set_state(lambda s: setattr(s, "beta", o.beta))
async def follow_motion() -> None:
async for m in native.sensors.motion(): # streaming (T-EV)
app.set_state(lambda s: setattr(s, "accel", m.acceleration))
Permissão em iOS
Em Safari iOS, deviceorientation/devicemotion exigem permissão explícita
concedida por um gesto do usuário. A assinatura levanta NativeError
(permission_denied) quando negada — trate como fluxo normal.
Tier 2 — muito usados¶
Bem suportados na maioria dos navegadores; alguns pedem permissão.
speech — síntese (TTS) e reconhecimento (STT)¶
Fale texto em voz alta (single-shot) e liste vozes; reconheça fala em stream.
from tempestweb import native
async def announce(text: str) -> None:
await native.speech.speak(text, lang="pt-BR", rate=1.0)
async def dictate() -> None:
async for r in native.speech.listen(lang="pt-BR"): # streaming (T-EV)
if r.is_final:
app.set_state(lambda s: setattr(s, "said", r.transcript))
recorder — gravar áudio, vídeo ou tela¶
Comece a gravar do microfone ou da tela; stop devolve os bytes em base64.
from tempestweb import native
async def record_clip() -> None:
rec_id = await native.recorder.start(source="microphone")
# … usuário fala …
recording = await native.recorder.stop(rec_id) # → Recording
app.set_state(lambda s: setattr(s, "clip_b64", recording.data_base64))
filesystem — ler e gravar arquivos com handles vivos¶
Abra arquivos pelo seletor do sistema (com handle reutilizável para regravar) ou crie um novo arquivo com o seletor de salvar.
from tempestweb import native
async def open_and_edit() -> None:
files = await native.filesystem.open_file(accept=".txt", multiple=False)
if files:
handle = files[0] # → FileHandle
await native.filesystem.write_file(handle.id, handle.data_base64)
async def save_new(data_b64: str) -> None:
await native.filesystem.save_file("export.bin", data_b64)
bgsync — Background Sync + Periodic Sync¶
Registre trabalho que o service worker reexecuta quando a conexão volta (ou em um intervalo periódico) — o motor por trás do replay real da fila offline.
from tempestweb import native
async def queue_sync() -> None:
await native.bgsync.register("outbox")
await native.bgsync.register_periodic("refresh", min_interval_ms=3_600_000)
tabs — sincronizar entre abas¶
Transmita mensagens entre abas (BroadcastChannel) e coordene com locks nomeados (Web Locks). Receber mensagens é streaming.
from tempestweb import native
async def broadcast_theme(theme: str) -> None:
await native.tabs.broadcast("prefs", {"theme": theme})
async def follow_prefs() -> None:
async for msg in native.tabs.receive("prefs"): # streaming (T-EV)
app.set_state(lambda s: setattr(s, "theme", msg["theme"]))
idle — detecção de inatividade (streaming)¶
Saiba quando o usuário fica inativo ou a tela é bloqueada.
from tempestweb import native
async def follow_idle() -> None:
async for state in native.idle.watch(threshold_seconds=120): # streaming (T-EV)
app.set_state(lambda s: setattr(s, "away", state.user == "idle"))
Tier 3 — nicho, secure-context, maioria só Chromium¶
Poderosas mas de suporte restrito. Sempre cheque is_supported() primeiro e
tenha um fallback.
Só Chromium + contexto seguro
Os grupos abaixo (com raras exceções) só existem no Chromium (Chrome/Edge),
exigem HTTPS e a maioria abre um seletor do sistema que precisa de um
gesto do usuário. Firefox/Safari costumam retornar is_supported() == False.
bluetooth — Web Bluetooth (GATT)¶
Pareie um dispositivo BLE e leia/escreva características GATT.
from tempestweb import native
async def read_heart_rate() -> str:
if not await native.bluetooth.is_supported():
return ""
device = await native.bluetooth.request(
optional_services=["heart_rate"],
) # → BluetoothDevice
return await native.bluetooth.read(device.id, "heart_rate", "heart_rate_measurement")
usb — WebUSB¶
Peça acesso a um dispositivo USB pelo seletor do navegador.
from tempestweb import native
async def pick_usb() -> None:
if await native.usb.is_supported():
device = await native.usb.request(filters=[{"vendorId": 0x2341}])
print(device.product_name, device.vendor_id)
serial — Web Serial¶
Abra uma porta serial (Arduino, leitores, etc.); devolve um id opaco de porta.
from tempestweb import native
async def pick_serial() -> str:
if not await native.serial.is_supported():
return ""
return await native.serial.request(filters=[])
hid — WebHID¶
Peça acesso a dispositivos HID (gamepads exóticos, teclados especiais).
from tempestweb import native
async def pick_hid() -> list[dict[str, object]]:
if not await native.hid.is_supported():
return []
return await native.hid.request(filters=[])
nfc — Web NFC (escrita)¶
Grave registros NDEF em uma tag NFC próxima.
from tempestweb import native
async def write_tag(url: str) -> None:
if await native.nfc.is_supported():
await native.nfc.write([{"recordType": "url", "data": url}])
Leitura de NFC (scan) — streaming (T-EV)
Além da escrita, o scan de tags é um stream contínuo pelo canal de eventos:
Cada NdefMessage traz serial_number + records decodificados; sair do laço
aborta o scan. Ver Canal de eventos nativo.
contacts — Contact Picker¶
Deixe o usuário escolher contatos pelo seletor do sistema (Android/Chrome).
from tempestweb import native
async def pick_contact() -> list[dict[str, object]]:
if not await native.contacts.is_supported():
return []
return await native.contacts.select(properties=["name", "tel"], multiple=False)
payment — Payment Request API¶
Mostre a folha de pagamento nativa do navegador.
from tempestweb import native
async def checkout() -> dict[str, object]:
if not await native.payment.is_supported():
return {}
return await native.payment.request(
methods=[{"supportedMethods": "https://example.com/pay"}],
details={"total": {"label": "Total", "amount": {"currency": "BRL", "value": "9.90"}}},
)
pip — Picture-in-Picture¶
Solte um <video> numa janelinha flutuante.
from tempestweb import native
async def pop_video() -> bool:
return await native.pip.request(selector="video#player")
async def close_pip() -> None:
await native.pip.exit()
eyedropper — conta-gotas de cor¶
Deixe o usuário pegar uma cor de qualquer ponto da tela.
from tempestweb import native
async def pick_color() -> str:
return await native.eyedropper.open() # → "#3366ff" (ou "" se cancelado)
pointerlock — travar o ponteiro¶
Capture o mouse (jogos, visualizadores 3D), escondendo o cursor.
from tempestweb import native
async def start_game() -> None:
await native.pointerlock.request(selector="#canvas")
async def end_game() -> None:
await native.pointerlock.exit()
gamepad — Gamepad API¶
Leia um snapshot (state) ou observe os controles em stream (watch).
from tempestweb import native
async def read_pads() -> list[dict[str, object]]:
return await native.gamepad.state()
async def follow_pads() -> None:
async for pads in native.gamepad.watch(): # streaming (T-EV)
app.set_state(lambda s: setattr(s, "pads", pads))
midi — Web MIDI¶
Enumere portas, envie mensagens e escute mensagens de entrada em stream.
from tempestweb import native
async def play_note() -> None:
if not await native.midi.is_supported():
return
ports = await native.midi.request_access() # → MidiPorts
if ports.outputs:
await native.midi.send(ports.outputs[0]["id"], [0x90, 60, 0x7F])
async def follow_midi() -> None:
async for msg in native.midi.messages(): # streaming (T-EV)
app.set_state(lambda s: s.notes.append(msg.data))
webaudio — tom, frase e medidor¶
Três formatos, em ordem crescente do que conseguem dizer.
Um beep, sem precisar de um asset de áudio (diferente de audio.play):
from tempestweb import native
async def beep() -> None:
await native.webaudio.tone(frequency=880.0, duration_ms=150, type="sine")
Uma frase inteira, numa chamada. Cada Step ganha oscilador e ganho próprios
sobre um barramento compartilhado, com envelope de attack/release. Passos com o
mesmo start_ms soam juntos — é assim que se escreve um acorde:
from tempestweb import native
async def chord() -> None:
result = await native.webaudio.sequence(
[
native.webaudio.Step(frequency=261.63, duration_ms=700, gain=0.3),
native.webaudio.Step(frequency=329.63, duration_ms=700, gain=0.3),
native.webaudio.Step(frequency=392.00, duration_ms=700, gain=0.3),
]
)
print(result.scheduled, result.ends_in_ms, result.blocked) # 3 700 False
async def hush() -> None:
await native.webaudio.stop() # corta o que ainda soa; o contexto fica aberto
Por que uma frase, e não um grafo de nós
No Modo B cada chamada de capacidade é um round-trip. Uma API na forma do grafo do Web Audio colocaria a rede entre um oscilador e seu ganho. O que uma app precisa de "além de um tom" é agendamento e forma — os dois são por frase, então a frase é a unidade que atravessa o fio.
O envelope é o que separa uma nota de um clique
attack_ms/release_ms (default 5/40) rampeiam do silêncio ao gain e de
volta. Sem eles a onda começa e termina no meio do ciclo, e o que se ouve é um
estalo nas duas pontas.
Um medidor, em streaming, sobre a própria síntese ou o microfone:
from tempestweb import native
async def vu() -> None:
async for level in native.webaudio.watch_levels(interval_ms=100, bands=8):
app.set_state(lambda s: setattr(s, "vu", level.rms))
source="output" (default) escuta o barramento compartilhado: sem microfone e
sem prompt de permissão, uma app mede o áudio que ela mesma toca.
source="mic" abre getUserMedia({audio: true}) e falha com
permission_denied se o usuário recusar.
Verificado nos Modos A e B
sequence, stop e watch_levels medidos em Chrome real nos dois modos
interativos. Em Modo A, origem virgem: acorde reporta 3 notas juntas, 700 ms,
o medidor lê rms 0.374 · peak 0.766 enquanto ele soa, e stop devolve
parado: 2 osciladores.
Modo C não alcança stream nenhuma
Não é sobre áudio: o compilador ainda não conhece async for
(statement AsyncFor is not supported), então nenhuma capacidade de stream
é alcançável de uma app Modo C — o mesmo vale para geolocation.watch. A
fachada do Modo C já expõe watch_levels para JS escrito à mão, e
sequence/stop compilam normalmente.
Consumo longo vai para spawn
Os dois modos leem eventos em série. Um async for awaitado direto no
handler segura o dispatch e a app para de responder — passe por
tempestweb.runtime.spawn, como examples/webaudio_demo faz. Foi exatamente
isso, num artefato antigo servido pelo service worker de uma origem reusada,
que virou o falso positivo da
#171.
Medido em Chrome real
Acorde de 700 ms tocando: rms 0.365 → 0.376 → 0.353, peak 0.852 → 0.719; em
t=720 ms — quando o release acaba — volta a 0.000. O arpejo de 4 notas
escalonadas sobe rms 0.184 → 0.286 → 0.342 conforme elas se sobrepõem.
examples/webaudio_demo é esse app.
Recap¶
- Uma importação (
from tempestweb import native) e a mesma assinatura nos três modos. - Dois formatos: single-shot com
await, streaming comasync for(sobre o canal de eventos T-EV). - Tier 1 é universal; Tier 2 é muito usado; Tier 3 é só-Chromium/
secure-context e sempre traz
is_supported()+ fallback. - Streams (
geolocation.watch,sensors.*,network.watch,visibility.watch,orientation.watch,battery.watch,speech.listen,idle.watch,tabs.receive,gamepad.watch,midi.messages,nfc.scan) fecham a assinatura ao sair do laço. - O Trilho T está completo, sem lacunas de capacidade conhecidas.
Veja a ponte em ação no Painel do dispositivo e o
formato de wire das chamadas em
docs/contract.md. 🚀