Наверное, это — своего рода продолжение темы «Отличия в адаптации сайта и AJAX веб-приложения для iOS» и развитие идеи из моего комментария там.

Тема старая, но для многих до сих пор актуальная. В целом, внутри будет много частностей, поскольку целью не является придумать универсальное решение, а всего лишь надо заставить один конкретный сайт работать как Web App, не прибегая к большим модификациям. В общем, как обычно, кому интересно — под кат, остальные проходят мимо, наверное.

Итак, обозначаем вводные, задачи и проблемы: надо сделать, чтобы сайтик (обычный, ничего особенного) работал как Web App, при этом не выпрыгивал в Safari и запоминал, на какой странице клиент был и что там делал. Думаю, что те, кто дочитал до этого момента, прекрасно знают, какие проблемы не позволяют осуществить всё перечисленное, ничего не делая: любая нормальная ссылка открывается в Safari, если из Web App переключиться на другое приложение, или свернуть его, то при открытии будет показана та страница, которую, собственно, сохранили как Web App — главная чаще всего.

В общем, сухие задачи: 1) сделать, чтобы ссылки не выкидывали в Safari; 2) сделать, чтобы запоминалась сессия; 3) сделать, чтобы запоминалась открытая страница. В отличие от исходной темы, я ставлю задачи именно в таком порядке, ниже будет понятно, почему.

Погнали решать!

Ссылки не должны выплёвывать в Safari


Этот вопрос давно избит и решён. Надо просто в JS отловить все клики по ссылкам и переделать их на location.assign(). (Почему-то издревле вместо location.assign() везде используется присвоение window.location — не знаю, с чем это связано, наверное, так считалось правильнее, когда деревья были большими.)

В целом, тут я ничего нового не придумал, только пришлось слегка расширить: 1) применяется это только в standalone режиме; 2) если ссылка «внешняя» (с другим хостом), то не применяется (открывается Safari); 3) если ссылка — это энкор (#some-shit), то подменяется только location.hash.

То есть, включаете для standalone этот обработчик, требуете наличия href у ссылки, а дальше уже отлаживаете для конкретного случая. Кода здесь не будет, потому что там одна строка и очень много частностей, которые никому не интересны, потому что это — не их частности.

Сессия должна запоминаться


Тут сложнее. Проблема в том, что при перезапуске Web App куки не отправляются, куки нет — сессия новая. Корзину протеряли. В общем, плохо. В исходной теме было решение со считыванием и записью кук прямо из JS, но как я написал в комментариях — не очень это хорошо работает в современных реалиях — сессионная кука теперь httpOnly, то есть в JS не видна никак.

Мы решать будем чуть-чуть по-другому: идентификатор будем хранить так же в localStorage, но сравнивать и управлять сессией будем со стороны сервера, а теребить всё это будем через ajax. В JS делаем примерно такую колбасу (тоже только для standalone режима):

$.post('/super-puper-server-script.json', {'sid': localStorage.getItem('sid')}, function(data) {
	localStorage.setItem('sid', data.sid);
	if (data.loc) window.location = data.loc;
});


Отправляем текущее значение sid из localStorage и сохраняем то, что вернётся, и если ещё вернётся location — переходим по нему. Следовательно, вся логика выносится на сервер, там мы можем получать текущее значение идентификатора сессии, и, что более важно, мы можем понять, какой идентификатор правильный.

(Ремарка: в Yii, который я использую, при авторизации идентификатор сессии меняется, при этом данные сессии сохраняются. То есть, чтобы позволить пользователю сохранять корзину (и другие данные сессии) при авторизации, и при этом поддерживать авторизацию в целом, нужно такие моменты учесть и не подставлять идентификатор сессии из localStorage в сессию, собственно.)

А внутри, со стороны сервера, всё тоже достаточно просто. Я не привожу свой код напрямую не потому, что я — жмот, а потому, что мне кажется, если я опишу сам алгоритм, вы легко сможете самостоятельно закодить это на любом языке, вместо того, чтобы кричать: «Фу! PHP — отписываюсь!»

Алгоритм примерно такой: получить идентификатор из JS, получить идентификатор из куки, сравнить, проверить исключения (авторизация, например, или другая правомерная смена идентификатора), если отличаются — применить из JS. Ответом отправить тот идентификатор, который в текущей сессии. Если что-то менялось, к ответу добавить текущий локейшн — чтобы страница перезагрузилась, и данные сессии уже были применены.

Страница должна запоминаться


А вот и замануха, почему же я не стал решать эту (более важную в целом) проблему до сессий. Ну, для начала, подход в исходной теме мне решительно не нравится — сохранять все клики в localStorage и восстанавливать оттуда. Плохо это, потому что есть редиректы, джампы и формы, которые локейшн меняют, а в localStorage сохраняться не будут. Также это плохо, потому что, как и везде, могут быть исключения, которые придётся выносить в JS.

И тут меня осенило — мы же только что с таким трудом научились восстанавливать сессию, почему бы не хранить там же и локейшн?! И всё оказалось так красиво и правильно: во-первых, имеет смысл восстанавливать локейшн только тогда, когда мы восстанавливаем сессию (перезапуск web app), а в остальных случаях это будут только лишние телодвижения. Во-вторых, больше возможностей по контролю того, что именно сохранять (а что не сохранять) в качестве текущей страницы. И, наконец, такой подход позволяет сделать перезагрузку страницы только один раз, чтобы и применить правильные данные сессии, и показать пользователю то, что он смотрел.

Вот примерный код того, что со стороны сервера, и, да, на PHP:

$sid = $_POST['sid'];
$loc = abs2rel($_SERVER['HTTP_REFERER']);
$resid = false;

if (!empty($sid) && !empty($_COOKIE['PHPSESSID']) && $sid != $_COOKIE['PHPSESSID']) {
    session_id($sid);
    $resid = true;
}
session_start();

$reloc = ($resid && isset($_SESSION['loc'])) ? $_SESSION['loc'] : false;
$_SESSION['loc'] = $loc;

return ['sid' => session_id(), 'loc' => $reloc];


Пара комментов к коду: естественно, это всё запускается только при POST через ajax; в проверке отличий идентификаторов можно добавить ещё условия, когда идентификатор должен смениться (правомерно); перед сохранением локейшна в сессию можно также исключить какие-то проверки; возвращаемый хэш у меня автоматически конвертится в JSON.

Возможно, я что-то упустил, но в целом оно таким образом работает очень и очень неплохо. Ну, и, по всей видимости, писать доходчивые статьи — это не моё. Сумбурно и непонятно получилось, но как есть…

Комментарии (0)