API da ponte JavaScript
Abertura, atalhos e o botão voltar
Como o app foi aberto, atalhos de pressionar e segurar no ícone, o pedido de avaliação no app e como avisar o shell de que a página quer o toque em voltar.
AppMint.requestReview helper#
AppMint.requestReview(options)
Exemplo
Pede ao Google Play que mostre a própria folha de avaliação dentro do app (Play In-App Review) e informa o que aconteceu.
Retorna: uma Promise que sempre é resolvida com {result, openedStore, message?}. result é 'shown' (o Play exibiu sua folha — ele nunca informa se o usuário avaliou), 'not_from_play' (esta instalação não veio do Google Play: um APK instalado manualmente ou uma instalação de teste, em que o Play nunca mostra a folha), 'quota' (o Play decidiu não exibi-la agora: a cota dele para este usuário, ou o usuário já avaliou) ou 'error' (não foi possível consultar o Play: sem Play Store, offline). Com {openStore: true}, a página do app no Play abre quando a folha não apareceu, e openedStore é true. Requer: nada para ativar.
Qualquer código:
// A "Rate us" button: the sheet when Play shows it, the Play listing otherwise.
document.getElementById('rate-btn').onclick = async function () {
if (!(window.AppMint && AppMint.requestReview)) { location.href = 'https://play.google.com/store/apps/details?id=com.example.app'; return; }
var r = await AppMint.requestReview({ openStore: true });
if (r.result === 'shown' || r.openedStore) showToast('Thank you!');
};Pergunte depois de um momento de sucesso em vez de a partir de um botão, sem nunca sair do app:
async function onOrderDelivered(count) {
if (count !== 3 || !(window.AppMint && AppMint.requestReview)) return;
var r = await AppMint.requestReview(); // no openStore: nothing opens if Play says no
if (r.result === 'not_from_play') console.log('Test install: Play only shows the sheet for Play installs');
}Quando o criador ativou Pedir aos usuários que avaliem o app (etapa Exibição), o app pergunta por conta própria em um momento tranquilo e informa à página o que aconteceu:
window.addEventListener('appmint:review', function (e) {
// e.detail = { result, openedStore: false, source: 'auto' }
analytics.track('rate_prompt', { result: e.detail.result });
});Observações: O Google Play limita a frequência da folha e decide por conta própria se ela aparece; nunca recompense uma avaliação nem prometa a folha em um botão. Teste com uma instalação da faixa de teste interno do Play - um APK instalado manualmente sempre responde not_from_play.
Sem código: o link appmint://review. Um botão ou link com este endereço pede a mesma folha (e abre a página do Play quando o Play não a mostra, porque o usuário pediu para avaliar). Em uma mensagem no app, coloque-o em um botão na central de envios (o modelo Avalie-nos faz isso); em uma página, use como um link simples:
<a href="appmint://review" class="rate-link">Rate us on Google Play</a>A partir de um script, por exemplo, depois do terceiro pedido concluído:
function maybeAskForRating(ordersDone) {
if (ordersDone === 3) location.href = 'appmint://review'; // the app runs it; nothing is loaded
}Saiba o que o usuário escolheu em uma mensagem no app "Avalie-nos" antes de a folha abrir:
window.addEventListener('appmint:inapp', function (e) {
var d = e.detail;
if (d.type === 'answered' && d.question === 'stars' && d.value <= 3) showFeedbackForm();
if (d.type === 'clicked' && d.link === 'appmint://review') console.log('Opening the Play review sheet');
});AppMint.setBackHandler helper#
AppMint.setBackHandler(handler)
Exemplo
Dá à sua página a primeira palavra quando o usuário aperta o botão Voltar do celular (ou usa o gesto de voltar). Necessário para apps que trocam de tela mostrando e ocultando elementos, onde o histórico do Android está vazio e Voltar fecharia o app.
Retorna: nada. Seu handler deve retornar true (tratado, ficar aqui) ou false (deixar o app voltar / sair). Requer: nada. Instalado no início do documento, então você pode registrá-lo no seu primeiro script.
if (window.AppMint && AppMint.setBackHandler) {
AppMint.setBackHandler(function () {
if (isMenuOpen) { closeMenu(); return true; } // consumed
if (currentScreen !== 'home') { goTo('home'); return true; }
return false; // nothing to undo: normal Back (history back, exit dialog, exit)
});
}
// Remove it again, for example when the screen that needed it closes:
// AppMint.setBackHandler(null);O evento appmint:back - cancelável. Chame e.preventDefault() para consumir o toque. Ele roda antes do manipulador, e qualquer número de ouvintes pode usá-lo:
window.addEventListener('appmint:back', function (e) {
var sheet = document.querySelector('.bottom-sheet.open');
if (sheet) {
sheet.classList.remove('open');
e.preventDefault(); // consumed: the handler and the shell do nothing more
}
});Evite perder uma nota pela metade: Voltar mostra sua própria barra "Descartar alterações?" em vez de sair (um confirm() bloqueante perderia a janela de resposta de 350 ms, e o shell voltaria de qualquer jeito):
if (window.AppMint && AppMint.setBackHandler) {
var noteDirty = false;
document.getElementById('note').addEventListener('input', function () { noteDirty = true; });
AppMint.setBackHandler(function () {
if (!noteDirty) return false; // nothing to lose: normal Back
showDiscardBar(); // your in-page "Discard changes? / Keep editing" bar
return true; // stay here while the user decides
});
document.getElementById('discard-btn').onclick = function () {
noteDirty = false; // the next Back press passes straight through
hideDiscardBar();
closeEditor();
};
}Observações: A resposta deve ser síncrona: retornar uma Promise conta como "não tratado". Responda rápido - se a página não responder em cerca de 350 ms, o shell volta por conta própria. Ordem a cada toque: um menu lateral aberto (a gaveta do próprio app) fecha primeiro e a página não é consultada; depois os ouvintes de appmint:back → seu manipulador → o fechador de diálogos embutido do shell (fecha um <dialog> aberto, diálogo ARIA ou modal em tela cheia) → voltar no histórico / confirmação de saída / saída. Apps que usam history.pushState para cada tela (React Router e similares) não precisam disto. Apps criados com IA podem usar onBackGesture de @/lib/appmintNative.
__pullRefreshEligible ponte#
window.WebToApk.__pullRefreshEligible(eligible: Boolean)
Veja [pullPageEligible] / [pullProbeJs]. Chamado a cada touchstart.
Exemplo
Estrutura interna por trás de puxar para atualizar: a sonda de toque do próprio shell a chama a cada toque para dizer se um puxão para baixo ali deve atualizar a página. As páginas nunca a chamam - elas controlam puxar para atualizar com CSS padrão e eventos de toque.
Retorna: nada. Requer: Ativar puxar para atualizar ligado na Etapa 3 (Integrar), em Opções de exibição, ao compilar (sem isso, a sonda não é instalada).
A forma pública de impedir que o puxar para atualizar atue em parte da sua página — reivindique o gesto:
<style>
/* A map, drawing canvas or slider: this element owns vertical drags. */
#map, #canvas { touch-action: none; }
/* A horizontal carousel: pan-x also tells the shell "not a refresh". */
.carousel { touch-action: pan-x; }
</style>Ou reivindique a partir de um manipulador de toque, da forma padrão:
document.getElementById('canvas').addEventListener('touchstart', function (e) {
e.preventDefault(); // the page took this gesture: no pull-to-refresh
}, { passive: false });Impeça uma superfície de arrastar e dar zoom de atualizar a página reivindicando os arrastos verticais apenas com CSS:
<div id="board" style="touch-action: none; height: 60vh"></div>
<script>
// Dragging on #board moves the board; a pull from the top of the rest of the page still refreshes.
document.getElementById('board').addEventListener('pointermove', function (e) { moveBoard(e); });
</script>Observações: Um puxão só atualiza quando a caixa rolável mais próxima sob o dedo (ou a própria página) está rolada até o topo, apenas um dedo está apoiado e nenhuma das reivindicações acima se aplica. Um toque dentro de um iframe de outra origem nunca atualiza. Não chame este método você mesmo.
getAppIcon ponte#
window.WebToApk.getAppIcon(): String
O ícone do launcher em uso: "default" ou um dos nomes de getAvailableIcons().
Exemplo
O ícone do launcher em uso: 'default' ou um dos nomes de getAvailableIcons().
Retorna: uma string. 'default' em uma compilação sem ícones alternativos.
if (window.WebToApk && WebToApk.getAppIcon) {
const current = WebToApk.getAppIcon(); // 'default', 'Dark', …
document.querySelectorAll('[data-icon]').forEach(el => {
el.classList.toggle('selected', el.dataset.icon === current);
});
}Troque para o próximo ícone a partir de uma tela de configurações:
function nextIcon() {
if (!(window.WebToApk && WebToApk.getAvailableIcons && WebToApk.setAppIcon)) return;
const names = JSON.parse(WebToApk.getAvailableIcons());
if (names.length < 2) return; // no alternate icons in this build
const next = names[(names.indexOf(WebToApk.getAppIcon()) + 1) % names.length];
if (!WebToApk.setAppIcon(next)) alert('Could not change the icon.');
}Mostre o nome do ícone em um rótulo ou oculte-o fora do app:
const label = document.getElementById('icon-label');
if (window.WebToApk && WebToApk.getAppIcon) {
const name = WebToApk.getAppIcon();
label.textContent = name === 'default' ? 'Classic icon' : name + ' icon';
} else {
label.hidden = true; // in a browser there is no launcher icon
}getAvailableIcons ponte#
window.WebToApk.getAvailableIcons(): String
Array JSON dos nomes de ícones que setAppIcon aceita: "default" primeiro, depois os alternativos da compilação.
Exemplo
Os nomes de ícones que setAppIcon aceita, como uma string de array JSON: 'default' primeiro, depois os ícones alternativos do assistente de compilação na ordem em que você os adicionou.
Retorna: '["default","Dark","Gold"]', ou '["default"]' em uma compilação sem ícones alternativos.
Crie um seletor de ícones:
function iconPicker(container) {
if (!(window.WebToApk && WebToApk.getAvailableIcons)) return;
const names = JSON.parse(WebToApk.getAvailableIcons());
if (names.length < 2) { container.hidden = true; return; } // nothing to choose
const current = WebToApk.getAppIcon();
container.innerHTML = '';
names.forEach(name => {
const b = document.createElement('button');
b.textContent = name === 'default' ? 'Classic' : name;
b.className = name === current ? 'selected' : '';
b.onclick = () => { if (WebToApk.setAppIcon(name)) iconPicker(container); };
container.appendChild(b);
});
}
iconPicker(document.querySelector('#iconPicker'));Oculte o seletor em uma compilação sem ícones alternativos:
const picker = document.getElementById('iconPicker');
let names = ['default'];
if (window.WebToApk && WebToApk.getAvailableIcons) names = JSON.parse(WebToApk.getAvailableIcons());
picker.hidden = names.length < 2; // only 'default' means there is nothing to chooseTroque o ícone junto com o tema:
function matchIconToTheme(dark) {
if (!(window.WebToApk && WebToApk.getAvailableIcons && WebToApk.setAppIcon)) return;
const names = JSON.parse(WebToApk.getAvailableIcons());
const wanted = dark ? 'Dark' : 'default';
if (names.includes(wanted) && WebToApk.getAppIcon() !== wanted) WebToApk.setAppIcon(wanted);
}
matchIconToTheme(matchMedia('(prefers-color-scheme: dark)').matches);getLaunchUrl ponte#
window.WebToApk.getLaunchUrl(): String
O deep link que abriu ou retomou este app, ou "" se ele foi aberto normalmente. Leia sempre que seu app for montado e faça o roteamento você mesmo: const u = window.WebToApk?.getLaunchUrl?.(); if (u) router.navigate(new URL(u).pathname); O valor é MANTIDO, não consumido, então um app montado tarde ainda o vê; um link posterior que chegue com o app em execução o substitui e também dispara 'appmint:deep-link'.
Exemplo
O deep link que abriu (ou reabriu) o app, como string, ou "" quando o app foi aberto normalmente.
Retorna: uma string, de forma síncrona. Requer: nada. O valor é mantido, não consumido, então é seguro ler sempre que seu app for montado.
Um link pode chegar em três formatos: https://yoursite.com/path, o appmint-shortcut://<package>/<route> do próprio shell (atalhos do launcher, o widget da tela inicial) ou seu esquema personalizado myapp://segment/path. Este auxiliar transforma os três em uma rota:
function routeOf(link) {
var m = /^([a-z][a-z0-9+.-]*):\/\/([^\/?#]*)([^#]*)/i.exec(link || '');
if (!m) return '/';
var scheme = m[1].toLowerCase(), host = m[2], rest = m[3] || '';
if (scheme === 'http' || scheme === 'https' || scheme === 'appmint-shortcut') return rest || '/';
return '/' + host + rest; // myapp://product/42 -> /product/42
}
// 1. On start: the link that launched the app.
if (window.WebToApk && typeof window.WebToApk.getLaunchUrl === 'function') {
var link = window.WebToApk.getLaunchUrl();
if (link) goTo(routeOf(link));
}
// 2. While running: a new link replaces the old one and fires 'appmint:deep-link'.
window.addEventListener('appmint:deep-link', function (e) {
goTo(routeOf(e.detail.url)); // detail: { url }
});O mesmo aviso também é passado a um hook global, se você preferir um:
window.onAppMintDeepLink = function (detail) { goTo(routeOf(detail.url)); };Leia um código de indicação de um link como https://yoursite.com/invite?code=AB12 ou myapp://invite?code=AB12, uma vez por link (usa routeOf de cima):
function applyInvite(link) {
if (!link) return;
var done = '';
try { done = sessionStorage.getItem('handledLink') || ''; } catch (e) {}
if (link === done) return; // the same url is announced again after a reload
try { sessionStorage.setItem('handledLink', link); } catch (e) {}
var code = null;
try { code = new URL(link).searchParams.get('code'); } catch (e) { return; }
if (code && routeOf(link).indexOf('/invite') === 0) showInviteBanner(code);
}
if (window.WebToApk && typeof window.WebToApk.getLaunchUrl === 'function') {
applyInvite(window.WebToApk.getLaunchUrl());
window.addEventListener('appmint:deep-link', function (e) { applyInvite(e.detail.url); });
}Observações: appmint:deep-link também é disparado após cada carregamento de página enquanto um link é mantido, então o mesmo url pode chegar mais de uma vez. Ignore um link que você já tratou.
Apps no modo Site são diferentes: o próprio shell carrega um link https:// recebido (na abertura a frio e durante a execução), e um link de esquema personalizado myapp://product/42 é antes reescrito para o endereço do seu site (https://yoursite.com/product/42), então getLaunchUrl() retorna a forma https:// e sua página geralmente não tem nada a rotear. Um atalho do launcher ou toque em widget abre a rota no seu site (/today carrega https://yoursite.com/today), e getLaunchUrl() ainda retorna a forma appmint-shortcut:// (veja setAppShortcuts). Em apps HTML, ZIP e criados com IA (servidos de dentro do app), o shell nunca navega: sua página recebe o link e faz o roteamento. Um link de esquema personalizado permanece como chegou ali, porque não há endereço de site para mapeá-lo. Apps criados com IA podem usar launchUrl() de @/lib/appmintNative.
rateApp ponte#
window.WebToApk.rateApp(): String
Abre o fluxo de avaliação da página do Play. `rateApp()` já existia, mas só o item da gaveta nativa podia alcançá-lo, então um app que desenha a própria interface - toda compilação com IA, onde a gaveta está desligada - não tinha como pedir avaliação. É a única ação de loja que uma página realmente não pode fazer sozinha: `market://` é uma intent, não uma URL navegável. Controlado pelo próprio botão "Botão Avaliar app" do criador, exatamente como o item da gaveta e o item Compartilhar da navegação inferior: desligar Avaliar deve significar nenhuma superfície de avaliação em lugar nenhum, ou o botão parece quebrado.
Exemplo
Abre a página do app no Google Play para o usuário avaliá-lo.
Retorna: 'opened' quando a página foi aberta, 'off' quando o criador desativou Avaliar app (então nada acontece). Requer: o botão Botão Avaliar app na Etapa 5 (Menu) ao compilar (vem ativado por padrão; também adiciona um item "Avaliar app" ao menu lateral). Com ele desativado, a chamada não faz nada e retorna 'off'. Para a folha de avaliação no app do Google Play, use AppMint.requestReview().
var rateBtn = document.getElementById('rate-btn');
if (window.WebToApk && typeof window.WebToApk.rateApp === 'function') {
rateBtn.onclick = function () { window.WebToApk.rateApp(); };
} else {
rateBtn.hidden = true; // in a browser
}Ofereça a avaliação uma vez, depois que o usuário terminar cinco treinos, a partir de um cartão em que ele toque:
function maybeOfferRating(completedCount) {
if (!window.WebToApk || typeof window.WebToApk.rateApp !== 'function') return;
var asked = false;
try { asked = localStorage.getItem('rateAsked') === '1'; } catch (e) {}
if (asked || completedCount < 5) return;
var card = document.getElementById('rate-card'); // "Enjoying the app? Rate it on Google Play"
card.hidden = false;
card.querySelector('.rate-yes').onclick = function () {
window.WebToApk.rateApp(); // opens the Play listing
try { localStorage.setItem('rateAsked', '1'); } catch (e) {}
card.hidden = true;
};
card.querySelector('.rate-later').onclick = function () { card.hidden = true; };
}Uma linha "Avalie-nos" na sua tela de configurações que agradece quando o usuário volta do Google Play (o retorno da página ao primeiro plano é o único sinal de que o usuário voltou):
document.getElementById('settings-rate').onclick = function () {
if (!window.WebToApk || typeof window.WebToApk.rateApp !== 'function') return;
window.WebToApk.rateApp();
document.addEventListener('visibilitychange', function onBack() {
if (document.visibilityState !== 'visible') return;
document.removeEventListener('visibilitychange', onBack);
showToast('Thanks for taking a look!'); // your own toast; the user may not have rated
});
};Observações: Abre a página do app na Play Store (market://details?id=<package>), ou o site do Play quando o app Play Store não está instalado. Não é o cartão de avaliação dentro do app. Uma página não consegue abrir market:// por conta própria — é uma intent do Android, não um endereço web. Só peça depois que o usuário fizer algo de que gostou, e a partir de um toque.
requestReview ponte#
window.WebToApk.requestReview(optionsJson: String, callbackId: String)
Pede ao Play a folha de avaliação; [optionsJson] `{openStore?}`. Resolve AppMint.requestReview [callbackId].
Exemplo
Estrutura interna por trás de AppMint.requestReview() - as páginas usam esse auxiliar de Promise, que pede ao Google Play a folha de avaliação no app e resolve com o que aconteceu.
Retorna: nada diretamente; o resultado resolve a promise de AppMint.requestReview(). Requer: nada para ativar. A folha só aparece para um app instalado pelo Google Play.
A API pública:
async function askForRating() {
if (!(window.AppMint && AppMint.requestReview)) return; // a browser: no store here
var r = await AppMint.requestReview();
console.log(r.result); // 'shown' | 'not_from_play' | 'quota' | 'error'
}Pergunte depois de um bom momento e peça a página da loja quando a folha não aparecer:
async function afterHappyMoment() {
if (!(window.AppMint && AppMint.requestReview)) return;
const r = await AppMint.requestReview({ openStore: true }); // {result, openedStore, message?}
if (r.result === 'shown') thankUser();
else if (r.openedStore) console.log('Opened the Play listing instead');
}Ouça o pedido automático e informe um erro:
window.addEventListener('appmint:review', (e) => {
if (e.detail.source === 'auto') console.log('Rate prompt result:', e.detail.result);
});
AppMint.requestReview().then((r) => {
if (r.result === 'error') console.warn(r.message);
if (r.result === 'not_from_play') showMessage('Rating is available once the app is installed from Google Play.');
});setAppIcon ponte#
window.WebToApk.setAppIcon(name: String): Boolean
Troca o ícone do launcher do app por um dos ícones alternativos adicionados no assistente de compilação, pelo nome dado lá ("default" = o ícone normal). Retorna false para um nome que esta compilação não tem. O launcher redesenha no seu próprio tempo (alguns segundos em certos celulares), e alguns launchers fecham o app ou movem o ícone da tela inicial quando ele muda - troque a partir de uma tela de configurações, não no meio de uma tarefa.
Exemplo
Troca o ícone do launcher do app por um dos ícones alternativos que você adicionou no assistente de compilação (etapa Básico, "Ícones alternativos do app"), pelo nome que você deu lá. 'default' é o seu ícone normal.
Retorna: true quando a troca foi feita (ou já é esse ícone), false para um nome que esta compilação não tem. Requer: ícones alternativos adicionados no assistente (até 4). Alguns launchers levam alguns segundos para redesenhar o ícone, e alguns fecham o app ou movem o atalho da tela inicial quando ele muda: troque a partir de uma tela de configurações, não no meio de uma tarefa. Seus atalhos do launcher (setAppShortcuts) são republicados com o novo ícone automaticamente.
function useIcon(name) {
if (!(window.WebToApk && window.WebToApk.setAppIcon)) return false;
const ok = WebToApk.setAppIcon(name);
if (!ok) console.warn('no icon called', name);
return ok;
}
document.querySelector('#darkIcon').addEventListener('click', () => useIcon('Dark'));
document.querySelector('#normalIcon').addEventListener('click', () => useIcon('default'));Monte o seletor a partir dos ícones que esta compilação tem:
if (window.WebToApk && WebToApk.getAvailableIcons) {
const names = JSON.parse(WebToApk.getAvailableIcons()); // 'default' first, then your alternates
const current = WebToApk.getAppIcon();
names.forEach((n) => addRadio('icon', n, n === current, () => WebToApk.setAppIcon(n)));
}Verifique a resposta para um nome que a compilação não tem:
function pickIcon(name) {
if (!(window.WebToApk && WebToApk.setAppIcon)) return showMessage('Icons can be changed in the app only');
if (!WebToApk.setAppIcon(name)) {
showMessage('Unknown icon "' + name + '". Choose one of: ' + JSON.parse(WebToApk.getAvailableIcons()).join(', '));
} else {
showMessage('Done. Your launcher may take a few seconds to redraw the icon.');
}
}Observações: a primeira atualização que ADICIONA ícones alternativos move a entrada do launcher para um novo componente, então um atalho da tela inicial feito antes dessa atualização pode precisar ser colocado de novo.
setAppShortcuts ponte#
window.WebToApk.setAppShortcuts(itemsJson: String)
Atalhos do launcher (pressionar e segurar o ícone do app). Até quatro entradas: `[{id, title, route}]`, em que `route` é um caminho no app ('/new-note'). Um toque abre o app com essa rota como deep link de abertura (`appmint-shortcut://<package><route>`), que a página lê por `getLaunchUrl()` e roteia exatamente como qualquer outro deep link. Em um app no modo Site, o próprio shell abre a rota no site (`/new-note` → `https://<site>/new-note`). Passe `[]` para removê-los. Android 7.1+; sem efeito abaixo disso.
Exemplo
Define os atalhos do launcher que aparecem quando o usuário toca e segura o ícone do app.
Retorna: nada. Requer: nada. Android 7.1+ (sem efeito abaixo disso).
itemsJson é uma string JSON: um array de até quatro objetos {id, title, route}. route é um caminho dentro do seu app. Um toque abre o app com appmint-shortcut://<package><route> como link de abertura:
if (window.WebToApk && typeof window.WebToApk.setAppShortcuts === 'function') {
window.WebToApk.setAppShortcuts(JSON.stringify([
{ id: 'new-note', title: 'New note', route: '/new-note' },
{ id: 'search', title: 'Search', route: '/search' }
]));
}Leia o toque da mesma forma que qualquer outro deep link, na abertura e durante a execução:
function shortcutRoute(link) {
var m = /^appmint-shortcut:\/\/[^\/?#]*([^#]*)/i.exec(link || '');
return m ? (m[1] || '/') : null;
}
if (window.WebToApk && window.WebToApk.getLaunchUrl) {
var r = shortcutRoute(window.WebToApk.getLaunchUrl());
if (r) goTo(r);
}
window.addEventListener('appmint:deep-link', function (e) {
var r = shortcutRoute(e.detail.url);
if (r) goTo(r);
});Remover todos os atalhos:
if (window.WebToApk && window.WebToApk.setAppShortcuts) window.WebToApk.setAppShortcuts('[]');Observações: Cada chamada substitui a lista inteira. Entradas sem title são ignoradas; o rótulo é cortado em 25 caracteres (50 para o rótulo longo). id tem como padrão appmint-<index>. Uma rota sem / inicial ganha uma. O ícone é o ícone do app. Uma lista malformada é ignorada (registrada no log), nunca gera exceção. Apps criados com IA podem usar shortcuts de @/lib/appmintNative. Apps no modo site: um toque abre a rota no seu site — /new-note carrega https://<your site>/new-note — e getLaunchUrl() ainda retorna o link appmint-shortcut://, então uma página que faz o próprio roteamento continua funcionando.