Русский
Русский
English
Статистика
Реклама

Быстрый vps

Прыгни со скалы! взлеты и падения легендарного геймдизайнера Уоррена Спектора

02.06.2021 10:16:01 | Автор: admin
image

Будь у игровой индустрии зал славы, почетное место там занял бы Уоррен Спектор. Человек-легенда, визионер, автор культовых Deus Ex и System Shock, основоположник жанра immersive sim, давший игрокам неслыханную свободу выбора. Кажется, что с таким авторитетом и признанием открываются все двери и любая идея находит поддержку. Но, увы, не всё так просто, и биография Спектора тому подтверждение: много раз он прыгал со скалы, пытаясь сделать игру своей мечты, и почти всегда сталкивался с неразрешимыми проблемами и непониманием инвесторов. Давайте вместе с журналистом Джейсоном Шрайером проследим тернистый творческий путь легенды геймдева.

От настолок к созданию Deus Ex


С самого детства Уоррен был одержим созданием интерактивных историй. Он рано увлекся настольными ролевыми играми, а когда переехал в 22 года в Остин (штат Техас), присоединился к компании приятелей, регулярно играющих в Dungeons & Dragons. Любопытный факт одна из игровых сессий продлилась 10 лет, а гейм-мастером был будущий корифей жанра киберпанк Брюс Стерлинг. В Техасском университете Спектор изучал кинематограф, писал диссертацию и преподавал, чтобы было чем оплачивать счета, пока в один прекрасный день его не уволили. Спустя некоторое время друг пригласил его поработать редактором настолок в компанию Steve Jackson Games. Скромная зарплата Уоррена не смущала: тогда, по его словам, он был геймером-любителем, и такая работа давала возможность создавать собственные игровые системы и кампании.
image

Через три года, в 1986 г., ему позвонили из TSR Inc. висконсинской фирмы, стоявшей за созданием D&D, и предложили должность редактора: событие, сопоставимое приглашению в NBA студента, играющего за баскетбольную команду родного колледжа. Тем не менее, долго он там не задержался: сказывался не только суровый северный климат, но и однообразная работа, которая быстро наскучила Спектору. Вскоре он вернулся в Техас, чтобы присоединиться к недавно созданной Origin Systems, которую основал знаменитый Ричард Гэрриот.
image
Ultima

Благодаря успеху игры Ultima (к 1989 г. вышло уже несколько продолжений) и на волне бума видеоигровой индустрии, студия быстро расширялась. Работа в Origin научила Спектора ремеслу продюсера руководить командами дизайнеров, управлять проектами и делать, казалось бы, невозможные вещи: объединять упрямых талантливых людей единым творческим видением. Вместе с Гэрриотом Спектор работал над Ultima VI, помогая создавать детально проработанную историю об орде горгулий (где они применили новаторский для того времени прием, наделив антагонистов сложными мотивами действий), а вместе с Крисом Робертсом над известным космическим симулятором Wing Commander. Бизнес-модель у меня была следующая, вспоминает Спектор. Я запускал четыре проекта (два внешних и два внутренних) и каждый год два наименее успешных закрывал, призывая всех сотрудников работать так, чтобы именно их проект оказался в числе лучших.

image
System Shock

В эти же годы началась дружба Спектора и Пола Нейрата, главы кембриджской студии Looking Glass, сотрудничавшей с Origin. Вместе с Looking Glass были выпущены Ultima Underworld и System Shock игры разные по сеттингу, но близкие по геймдизайну, построенному на принципах Dungeons & Dragons. Обе они помогли сформироваться тому жанру, что сегодня часто называют immersive sim (букв. симулятор погружения). Речь об играх, которые дают игроку инструменты для решения игровых задач различными способами, множеством вариантом, в отличие от тех же стандартных экшн-шутеров, которые в процессе прохождения будет требовать только одного уничтожить противника. Дать игроку ощущение неограниченных возможностей и свободы выбора для индустрии начала 1990-ых это был фундаментальный сдвиг парадигмы.

image
Пол Нейрат

Увы, несмотря на новаторский геймдизайн, у Origin возникли проблемы с финансированием, и в 1992 г. Гэрриот продал компанию крупному издателю Electronic Arts (EA). Поначалу всё шло отлично: менеджеры EA дали Спектору большой бюджет и свободу в творчестве, однако ожидаемой отдачи не было. Затраты у команды Уоррена были большими, счет проектов в работе шел на десятки, но успешных, по меркам EA, среди них так и не оказалось. Начался прессинг: руководство издателя зачастило в Остин, а в адрес Спектора посыпались упреки в неэффективном управлении. EA заявляли, что их главный приоритет увеличение прибыли акционеров, а с теми продуктами и жанрами, которые культивирует Уоррен, добиться серьезного роста невозможно. В пример ставили коллегу Криса Робертса, чей Wing Commander стал чрезвычайно успешен как коммерческий продукт, в то время как проекты Спектора, хоть и высоко оценивались критиками и сообществом, приносили очень маленькую прибыль.

image
Джон Ромеро

На фоне всего этого оставалось только уволиться из Origin. После разговора с Нейратом, в 1996 г. Спектор открыл в Остине офис студии Looking Glass и начал работу над новым проектом научно-фантастической многопользовательской игрой Junction Point. Но и тут не ждал успех: не получив инвестиций, студия закрылась через несколько месяцев, а игра в итоге осталась на стадии проекта. В поисках финансирования, Спектор чуть было не подписал контракт на разработку РПГ по серии Command & Conquer (C&C), но тут в дело вмешался легендарный Джон Ромеро. Разругавшись с коллегами из id Software, автор Doom создал собственную студию под названием Ion Storm и вот теперь предлагал Уоррену вместо сделки по C&C поработать на него. Да еще и на шикарных условиях: Неограниченный бюджет на разработку и больший маркетинговый бюджет, чем когда-либо. Мне предложили сделать игру моей мечты, без какого-либо вмешательства. Какой дурак от такого откажется?, вспоминает Спектор.

image
Результатом стала Deus Ex уникальная смесь жанров и воплощение идеи immersive sim, в котором все препятствия игрок мог преодолевать совершенно разными способами. Как позже писал Спектор, цель была в том, чтобы сделать игру о самовыражении игрока, а не о том, насколько умны мы сами дизайнеры, программисты и рассказчики. Игра вышла в июне 2000 года и стала не только феноменом, новой страницей в истории индустрии, но и коммерческим успехом (было продано более миллиона копий).

Однако вскоре Спектору снова повезло: из независимой компании Ion Storm превратился в подразделение крупного издателя Eidos. Несмотря на успех Deus Ex, на активную работу над продолжением (Deus Ex: Invisible War) и над новой игрой из серии Thief, менеджеры и маркетологи издателя третировали Спектора и его подход к геймдизайну. В частности, запретили разрабатывать игру про Дикий Запад, аргументируя отказ тем, что эта тема никогда не принесет больших денег (что позже успешно опровергли Rockstar с серией Red Dead Redemption). Дошло до того, что боссы попросили его не употреблять слово история в дискуссиях о видеоиграх.

Индустрия игр построена на конфликте между двумя фракциями творческими людьми, пытающимися создать создать произведение искусства, и коммерсантами, которым нужно получить прибыль. И этот конфликт основная причина большинства проблем индустрии видеоигр.


Спектор работал, не задумываясь о требованиях прибыльности и оптимизации затрат: Я никогда в жизни, ни в одном своем проекте не составлял бюджет и расписание. И всегда спрашиваю в этом случае: можете ли вы назвать хоть одну по-настоящему замечательную игру, которая была выпущена в срок и в рамках бюджета? В глазах издателя эта позиция выглядела безответственно, и неудивительно, что в 2004 г. Спектор покинул Ion Storm (а в 2005 г. студия была закрыта).

Что же дальше? Куда податься почти 50-летнему геймдизайнеру в индустрии, где, кажется, правят бал двадцатилетние? На каждый проект уходит по 2-3 года, большая часть в итоге не доходит до релиза, сколько проектов он сумеет завершить?

Прыгни со скалы! Прыгни со скалы!


Возможно, именно эти слова супруги Спектора Кэролайн заставили его набраться храбрости и открыть собственную студию, названную Junction Point (в честь отмененной игры). К поиску заказчиков и инвесторов Уоррен привлек своего приятеля Шеймуса Блэкли, интеллектуала с хорошими связями в индустрии. На тот момент Блэкли как раз работал в качестве агента, помогающего привлекать финансирование и успевшего поработать с Тимом Шафером (Psychonauts, Grim Fandango) и Лорном Лэннингом (Oddworld). Первой стала сделка на разработку фэнтэзийной RPG под названием Sleeping Giants для компании Majesco, однако та отменила игру год спустя. Вторым проектом стал новый эпизод Half-Life 2, и его тоже через несколько месяцев отменили.

Трудно поверить, но команда опытнейших разработчиков во главе с легендой индустрии не могла найти финансирование на новый проект. Если бы эта ситуация возникла спустя несколько лет, то они вполне могли бы воспользоваться альтернативными способами, доступными инди-разработчикам: Kickstarter, самостоятельная дистрибуция, небольшие издатели. Но в середине 2000-ых таких возможностей еще не существовало. Единственным вариантом для Спектора были крупные издатели типа EA и Activision, но, похоже, никому из них творчество Спектора не было интересно

Это было похоже на конец карьеры. Прыгнув со скалы, можно легко разбиться о камни.

image

Спасение пришло оттуда, откуда никто не ожидал, из Disney. Перед игровым подразделением корпорации стояло две большие задачи: запустить собственное игровое подразделение (до этого лицензии на создание игр с персонажами отдавали другим компаниям) и разработать игру с Микки Маусом, который, хоть и был легендой анимации, но в играх серьезной популярности не имел. Игровое подразделение возглавил Грэм Хоппер, который нанял стажеров и поручил им делать игру о противостоянии Микки Мауса и кролика Освальда персонажа, которого Уолт Дисней создал на заре своей карьеры (глава студии Боб Айгер специально для этого случая выкупил права на Освальда у Universal). Прототип игры получился довольно впечатляющим, однако студийные боссы хотели, чтобы таким знаковым проектом занималась известная и крутая студия. Так состоялась встреча с Уорреном Спектором.

С одной стороны, для Спектора это было большой радостью: кто откажется работать над игрой о знаменитом на весь мир персонаже, да еще и за хорошую оплату (с учетом того, как тяжело было найти финансирование). Но с другой, было условие, которое шло вразрез со всем личным опытом: Disney настаивал на поглощении студии Junction Point. То, что раньше произошло с Origin и Ion Storm, ставших частью больших корпораций, теперь должно было случиться и с его собственной студией, и Спектор догадывался, чем это могло закончиться. Но другого выхода попросту не было за исключением Disney, денег никто не предлагал. Поэтому, после долгих обсуждений, размышлений и торгов, Спектор согласился на сделку.

На разработку игры ушло три года. Epic Mickey стал эксклюзивом для платформы Nintendo Wii, сочетавшим необычный способ управления (посредством контроллера Wiimote) с элементами immersive sim: каждый выбор игрока имел последствия, влияя на сюжет и диалоги. Не любивший укладываться в сроки и бюджет, Спектор часто конфликтовал с руководством Disney, несколько раз его чуть было не увольняли. Однако ему удалось продавить своё видение и график, и в конце 2010 года игра наконец вышла.

Продажи Epic Mickey оказались хорошими (1,3 млн копий за первый месяц), критикам и аудитории игра пришлась по душе. Проект почти окупился, что для консольного эксклюзива уже считалось успехом. Казалось, что победа одержана, всё замечательно и стоит ждать сиквела, но

Disney наносит ответный удар


image
Джон Плезантс

К концу нулевых игровая стратегия Disney кардинально изменилась. Виной тому была не только рецессия на глобальном рынке, но и стремительный рост популярности мобильных игр и игр на социальных площадках типа Facebook. Многие аналитики и вовсе всерьез считали, что игровые консоли скоро вымрут. За несколько месяцев до релиза Epic Mickey мышиный дом заключил сделку на 763 миллиона долларов с компанией Playdom, выпускавшей социальные игры. Руководителем Хоппера стал бывший босс Playdom Джон Плезантс, веривший в онлайн и, кажется, презиравший консольные игры, неудивительно, что Хоппер скоро уволился, тем самым оставив Спектора без защиты. С точки зрения Плезантса, корпеть годами над одной игрой, тем более сжигая деньги на сиквел для умирающих консолей, было крайне бессмысленно куда рациональнее было вкладываться в быстрые проекты, которые смогли бы приносить прибыль уже через год.

А Уоррен Спектор так не мог: для него три года были эталонным сроком создания игры. Вот и теперь, как когда-то в Origin, он разделил студию на команды, каждая из которых работала над своим проектом, и к концу 2010 года, уже через несколько месяцев, был готов прототип сиквела Epic Mickey, на который Спектор планировал потратить еще два-три года спокойной работы параллельно с разработкой новых игр.
Но сразу после рождественских каникул пришла директива: во-первых, никаких новых проектов. Во-вторых, Disney подсчитал, что окупиться продолжение Epic Mickey сможет, только если выйдет к осени 2012 года, и поэтому потребовал усилить штат студии и форсировать производство. Результатом стало не только то, что штат студии вырос до 200 человек (и это в то время, когда Disney закрывала целые студии!), но и снижение качества продукта: времени на поиск решений не хватало, а из-за притока новых людей возникали конфликты и недопонимания. Под давлением Disney разработчикам приходилось лезть в те области, в которых они были новичками, но корпорация настаивала на включение в игру популярных трендовых элементов, вплоть до вариантов с переходом на freemium-модель или превращения в MMORPG. Спектору это не улыбалось. Он всё меньше и меньше участвовал в разработке, предпочитая заниматься новыми прототипами и идеями.

image
В начале 2012 г. конфликт перешел в острую стадию. Во время обсуждения перспектив студии Плезантс и Спектор доспорились до того, что последний швырнул в оппонента указкой и ушел, хлопнув дверью. Ответной мерой стала директива по сокращению расходов часть сотрудников требовалось уволить, остальных загрузить сверхурочной работой. Спектор старался сохранить штат и оставить хотя бы 75 сотрудников (из 200), но ни один из его многочисленных планов спасения не поддержали. Всего лишь за пару лет Junction Point превратилась из флагманской студии Disney в бесправную жертву гонки за новыми трендами.

Epic Mickey 2 вышла 18 ноября и это оказалось провалом. Критики осуждали игру за скучный и однообразный игровой процесс и за непроработанность персонажей. Но главная беда продажи были удручающе низкими: по количеству проданных копий сиквел существенно уступал первой части, и это при том, что он уже не являлся эксклюзивом Wii, а был доступен на популярных платформах XBox и PlayStation. Это стало окончательным приговором для студии. И хотя Спектор никак не мог с этим смириться (он даже организовал мозговой штурм на тему создания мобильной игры), всё было кончено. 29 января 2013 г., через два месяца после предрелизных кранчей, Спектор собрал сотрудников в комнате отдыха и объявил, что студия закрывается. Сам он очень тяжело переживал произошедшее, винил в крахе студии себя, свое поведение на встречах, неучастие в разработке игры.

Я съехал с катушек. Несколько месяцев я только и делал, что валялся на диване с пультом и просто щелкал пультом. Я был подавлен и просто ничего не мог делать. Disney был моим лучшим этапом в моей профессиональной деятельности и одновременно худшим.


Долгий эпилог, который, хочется верить, станет новой главой


Уйти на пенсию 57-летнему Спектору помешал звонок из родного Техасского университета его пригласили читать курс по дизайну и разработке видеоигр, чем он с энтузиазмом занимался три года, пока не понял, что его больше привлекает не преподавание, а само создание игр. Да и грант, выделенный на курс, заканчивался, а финансировать продолжение желающих не нашлось. Игра Underworld Ascendant, в которой Спектор выступил как консультант и которую называли новым словом в разитии жанра immersive sim, провалилась: аудитория не оценила монотонный геймплей и забагованность (что, в свою очередь, возможно, стали результатом нехватки финансирования средств, привлеченных черех краудфандинг, оказалось недостаточно). В конце 2015 г. Джон Нейрат пригласил его поучаствовать в создании System Shock 3, продолжение культовой игры, к разработке которой Спектор имел непосредственное отношение. Но, увы, и тут возникли финансовые проблемы: шведский издатель Starbreeze, с которым был заключен контракт на финансирование, обанкротился, и Спектору пришлось, как в старые добрые времена, самому примерять роль коммерсанта и летать на встречи с инвесторами. В мае 2020 года спаситель наконец-то был найден: права на издание игры приобрел китайский холдинг Tencent. Новость оптимистичная, учитывая мощь и возможности корпорации, однако не то ли самое можно было когда-то сказать и о Disney?

image
Концепт-арт System Shock 3

***

В индустрии игр немного людей, которые отдали ей свыше тридцати лет своей жизни, как это сделал Спектор, и именно его биография показывает, почему их так немного. Все четыре студии, с которыми связана его карьера (Origin, Looking Glass, Ion Storm, Junction Point), закрылись или при нем, или через несколько лет после его ухода. Спектор делал игры, которые стали культовыми и были высоко оценены критиками, но так и не добился значительного коммерческого успеха. Его путь, при всей уникальности, это символ той нестабильности, с которой постоянно приходится сталкиваться разработчикам видеоигр (при том, что ему еще повезло: не было необходимости переезжать из города в город к новому работодателю, как это часто происходит в геймдеве).
Своими играми Спектор вдохновил тысячи разработчиков и оказал огромное влияние на всю индустрию, однако его собственным проектам и студиям это не помогло выжить. По крайней мере, пока



Облачные серверы от Маклауд быстрые и безопасные.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Подробнее..

Перевод Отслеживание и визуализация положения МКС с помощью 30 строк JavaScript-кода

15.05.2021 14:20:40 | Автор: admin


Предлагаю вашему вниманию перевод этой замечательной статьи.

В этом туториале мы создадим веб-приложение, визуализирующее положение любого спутника, например, Международной Космической Станции (далее МКС), в режиме реального времени (ну, почти).

Мы создадим приложение с нуля и примерим на себя роль настоящего ученого в области ракетостроения.

  • Мы узнаем, где найти данные для отдельного спутника, известные как двухстрочный набор элементов (two-line element set, TLE) (далее ДНЭ)
  • Мы используем библиотеку satellite-js для предсказания орбиты спутника по ДНЭ (это часть напрямую связана с ракетостроением)
  • Мы используем библиотеку CesiumJS для визуализации результата, однако, вы можете использовать любую библиотеку/движок, которые умеют работать с долготой, широтой и высотой

Превью конечного результата:



Здесь мы видим движение МКС по орбите со скоростью, увеличенной в 40 раз. Для того, чтобы увидеть текущее положение МКС, нажмите на иконку часов в верхнем левом углу панели управления.

1. Получение ДНЭ


ДНЭ это формат данных, описывающий движение объекта, вращающегося по орбите вокруг Земли. Он был создан Командованием воздушно-космической обороны Северной Америки (North American Aerospace Defense Command, NORAD). Подробнее об истории его создания можно прочитать здесь.

Имея описание орбиты, мы можем предсказывать локацию, в которой будет находиться спутник, в любой момент времени (см. ниже).

Это означает, что большинство устройств, отслеживающих положение спутников, делают это не совсем в режиме реального времени. Вместо того, чтобы получать непрерывно обновляющиеся данные, как, например, при отслеживании движения автомобиля, они используют последние доступные ДНЭ для предсказания положения объекта в определенный момент времени.

Где нам взять ДНЭ? Глобального реестра с такими данными не существует. За публикацию и обновление этих данных для космического сообщества отвечает тот, кому принадлежит тот или иной спутник (разумеется, если речь не идет о спутнике-шпионе).

Мы можем найти ДНЭ на сайте Space Track, который является реестром Космического командования Вооруженных сил США.

Другой ресурс этот список на CeleStrak (прим. пер.: для доступа к сайту требуется VPN), поддерживаемый доктором T.S. Kelso.

Мы будем использовать последний, поскольку он не требует регистрации. Для того, чтобы найти ДНЭ для МКС, нажмите на ссылку Space Stations.

Первой в списке будет МКС:

ISS (ZARYA)
1 25544U 98067A 21122.75616700 .00027980 00000-0 51432-3 0 9994
2 25544 51.6442 207.4449 0002769 310.1189 193.6568 15.48993527281553


Значения этих чисел можно найти в этой таблице. Большая часть из них является идентификаторами и метаданными спутника, например, временем его запуска.

На указанном ресурсе можно найти ДНЭ для метеорологических спутников, спутников GPS и даже для глобальной спутниковой системы Starlink, разворачиваемой SpaceX.

2. Предсказание орбиты спутника


Следующим нашим шагом является преобразование ДНЭ в определенную позицию во времени.

Для этого мы будем использовать satellite-js.

Подключаем библиотеку из CDN:




Затем передаем ей ДНЭ и время:

const ISS_TLE =
`1 25544U 98067A 21122.75616700 .00027980 00000-0 51432-3 0 9994
2 25544 51.6442 207.4449 0002769 310.1189 193.6568 15.48993527281553`;
// Инициализируем запись о спутнике с помощью ДНЭ
const satrec = satellite.twoline2satrec(
ISS_TLE.split('\n')[0].trim(),
ISS_TLE.split('\n')[1].trim()
);
// Получаем текущую позицию спутника
const date = new Date();
const positionAndVelocity = satellite.propagate(satrec, date);
const gmst = satellite.gstime(date);
const position = satellite.eciToGeodetic(positionAndVelocity.position, gmst);

console.log(position.longitude); // в радианах
console.log(position.latitude); // в радианах
console.log(position.height); // в км


Теперь у нас имеется текущее положение спутника (new Date()).

Данное положение является результатом построения определенной модели движения спутника. Эта модель называется SGP4/SDP4. Все ДНЭ следуют этой модели.

Если вас интересует, насколько точной является указанная модель, то короткий ответ звучит так: это зависит от нескольких факторов.

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


3. Визуализация результата


Теперь у нас имеется возможность получать позицию спутника в заданный момент времени. Мы можем использовать это для анимирования пути спутника.

Но сначала давайте посмотрим, как анимировать отдельную точку в космосе с помощью CesiumJS.

Подключаем библиотеку вместе со стилями:

<script src="http://personeltest.ru/aways/cesium.com/downloads/cesiumjs/releases/1.81/Build/Cesium/Cesium.js"></script><link href="http://personeltest.ru/aways/cesium.com/downloads/cesiumjs/releases/1.81/Build/Cesium/Widgets/widgets.css" rel="stylesheet">


Создаем контейнер:

<div id="cesiumContainer"></div>


Дальше нам нужно инициализировать так называемого обозревателя (viewer). Мы передаем ему несколько дополнительных настроек для отключения функциональности, которая требует наличия токена доступа:

const viewer = new Cesium.Viewer('cesiumContainer', {  imageryProvider: new Cesium.TileMapServiceImageryProvider({    url: Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII"),  }),  baseLayerPicker: false, geocoder: false, homeButton: false, infoBox: false,  navigationHelpButton: false, sceneModePicker: false});viewer.scene.globe.enableLighting = true;


Наконец, мы можем визуализировать положение спутника в виде красной точки в космосе:

const satellitePoint = viewer.entities.add({  position: Cesium.Cartesian3.fromRadians(    position.longitude, position.latitude, position.height * 1000  ),  point: { pixelSize: 5, color: Cesium.Color.RED }});


Вот полный код данного шага на Glitch.

4. Анимируем путь


Для анимирования пути нам всего лишь нужно получить еще несколько будущих позиций спутника. CesiumJS поддерживает интерполяцию (переход) между позициями в течение времени из коробки.

Реализация анимации несколько многословна. Вот соответствующий код на Glitch. Ниже описаны самые важные концепции.

Мы создаем SampledPositionProperty. Это объект, содержащий позиции во времени, между которыми осуществляется переход:

const positionsOverTime = new Cesium.SampledPositionProperty();


Мы перебираем позиции в любом количестве, и для каждой позиции создаем объект со временем, который называется JulianDate в CesiumJS, а также саму позицию и добавляем их в качестве образца (sample):

for (let i = 0; i < totalSeconds; i+= timestepInSeconds) {  const time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate());  // Получаем позицию с помощью satellite-js  const position = Cesium.Cartesian3.fromRadians(p.longitude, p.latitude, p.height * 1000);  positionsOverTime.addSample(time, position);}


Наконец, мы передаем positionsOverTime в нашу точку:

const satellitePoint = viewer.entities.add({  position: positionsOverTime,  point: { pixelSize: 5, color: Cesium.Color.RED }});


Точка будет двигаться вместе с временной шкалой. Для прикрепления камеры к движущейся точке делаем следующее:

viewer.trackedEntity = satellitePoint;


Заключение


Надеюсь, вам было интересно немного узнать о том, как создаются программы для отслеживания спутников. Конечно, многие вопросы остались без ответа, например, что означает каждый параметр ДНЭ? Как часто они обновляются? Каким именно образом происходит их обновление?

Лично мне было очень интересно узнать о существования таких данных, о том, как их получить и использовать прямо в браузере с помощью JavaScript.

Вот парочка идей о том, что еще можно с этим сделать:

  • Визуализация нескольких спутников, например, спутниковой системы Starlink. В качестве источника для вдохновения можно использовать Celestrak viewer, показывающий каждый спутник из каталога. Можно, например, визуализировать рост количества спутников Starlink в течение времени
  • Отслеживание и визуализация положения спутника на уровне городской улицы. Можно добавить поиск здания или высоты с наилучшими условиями для наблюдения за спутником

Вот прототип второй идеи на Glitch. Демо: .

Также советую взглянуть на приложение See a satellite tonight, разработанное James Darpinian, в котором используется комбинация CesiumJS и Google улиц.

Кроме того, те, кто разбирается/увлекается 3D-моделированием, могут представить спутники не в виде точек, а в реальном масштабе для демонстрации того, насколько близко друг к другу они находятся в космосе.

Прим. пер.: мой вариант приложения выглядит так:



Благодарю за внимание и хорошего дня!



Облачные серверы от Маклауд быстрые и безопасные.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Подробнее..

Реализация подписки на обновления с помощью Google Sheets, Netlify Functions и React. Часть 1

04.06.2021 10:04:57 | Автор: admin

В этом туториале мы реализуем ~~Real World App~~ подписку на обновления с помощью гугл таблиц, бессерверных функций и реакта.


Основной функционал нашего приложения будет следующим:


  • на главной странице отображается приветствие и предложение подписаться на обновления
  • при нажатии на кнопку "Подписаться", пользователь попадает на страницу с формой, содержащей два поля: имя и адрес электронной почты
  • для защиты от ботов используется гугл рекапча 2 версии
  • при заполнении полей и прохождения проверки разблокируется кнопка "Подписаться"
  • при нажатии этой кнопки данные пользователя отправляются в таблицу с помощью бессерверной функции

Дополнительный функционал:


  • с помощью скрипта осуществляется автоматическая рассылка уведомлений
  • в рассылаемых письмах содержится ссылка на отписку от обновлений
  • при переходе по этой ссылке адрес электронной почты передается бессерверной функции, с помощью которой из таблиц удаляется соответствующая строка пользователь больше не будет получать уведомлений

В первой части туториала мы реализуем основной функционал, во второй дополнительный.


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


Демо приложения, которое мы создадим, можно посмотреть здесь (оно вполне работоспособное, если хотите, можете подписаться на обновления).


Код приложения находится здесь.


Для реализации приложения мы будем использовать следующие технологии:


  • netlify-cli интерфейс командной строки для запуска сервера для разработки (инициализации бессерверных функций) и "деплоя" приложения на Netlify; требуется глобальная установка: yarn global add netlify-cli или npm i -g netlify-cli; обязательно
  • google-spreadsheet JavaScript-библиотека для работы с гугл таблицами; обязательно
  • react на мой взгляд, это лучший JavaScript-фреймворк для фронтенда, но вы можете использовать любую другую библиотеку; наши бессерверные функции не будут зависеть от конкретного фреймворка
  • react-router-dom React-библиотека для маршрутизации
  • semantic-ui-react React-CSS-фреймворк (компоненты с готовыми стилями, ну, почти готовыми, мы их немного поправим)
  • react-google-recaptcha React-компонент, позволяющий напрямую взаимодействовать с соответствующим сервисом
  • nodemailer наиболее популярная Node.js-библиотека для работы с электронной почтой (рассылки писем)
  • dotenv утилита для доступа к переменным среды окружения

Разумеется, на вашей машине должен быть установлен Node.js и, желательно, yarn (после того, как вы поработаете с этим пакетным менеджером, вы едва ли вернетесь к npm).


Также будьте готовы к тому, что придется создать несколько аккаунтов и, возможно, кое-что погуглить в случае, если я забуду об этом упомянуть в туториале (постараюсь этого не делать).


Надеюсь, мне удалось вас заинтриговать, и вы с нетерпением ждете, когда мы начнем писать код. Скоро, но сначала давайте создадим и настроим таблицу, а также свяжем ее с Google Cloud Platform.


Подготовка таблицы


Заходим в Google Cloud Platform (по ссылке, приведенной выше) и выполняем следующие действия:


  • создаем новый проект под названием, например, mail-list
  • ожидаем завершения создания проекта и выбираем его
  • переходим к обзору API (Go to APIs overview)
  • включаем Google Sheets API (Enable APIs and services)
  • создаем сервис-аккаунт для доступа к API (Create credentials)
  • переходим в созданный сервис-аккаунт
  • открываем вкладку Keys и добавляем ключ в формате JSON (Add key -> Create new key)
  • в скачанном файле (например, mail-list-315211-ca347b50f56a.json) нас интересуют свойства private_key и client_email; сохраните их где-нибудь, позже мы запишем их в переменные среды окружения

.


.


.


.


.


.


.


.


.


.


.


.


.


Заходим в Google Speadsheets и создаем новую таблицу (Пустой файл) с двумя графами: username и email.


.


Открываем настройки доступа, находим созданный нами сервис-аккаунт и добавляем его в качестве Редактора.


.


В поисковой строке между d/ и /edit находится идентификатор таблицы, также где-нибудь его сохраните.


На этом настройка нашей таблицы завершена.


Бессерверные функции


Приложение, реализацией которого мы занимаемся, представляет собой отличный пример использования бессерверных вычислений: нам не нужен полноценный сервер, мы всего лишь хотим отправлять данные из заполненной пользователем формы в гугл таблицу, читать эти данные и рассылать уведомления подписчикам. Netlify Functions это лишь один из вариантов использования AWS Lambda Functions для выполнения такого рода задач.


О том, что такое Netlify Functions, можно почитать здесь.


Функции, как правило, размещаются в директории functions в корне проекта. Создаем новый React-проект (mail-list название нашего проекта):


yarn create react-app mail-list# илиnpx create ...

Пьем чай в ожидании завершения создания проекта, переходим в созданную директорию и устанавливаем необходимые зависимости:


cd mail-listyarn add google-spreadsheet dotenv# илиnpm i ...

В корне проекта создаем файл .env (touch .env) и записываем в него сохраненные данные в следующем формате:


GOOGLE_SERVICE_ACCOUNT_EMAIL="YOUR_CLIENT_EMAIL"GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- YOUR_PRIVATE_KEY -----END PRIVATE KEY-----\n"GOOGLE_SPREADSHEET_ID="YOUR_SPREADSHEET_ID"

Создаем директорию functions, переходим в нее, создаем файл subscribe.js и открываем его в редакторе кода:


mkdir functionscd !$touch subscribe.jscode subscribe.js

В данном файле мы получаем доступ к сервис-аккаунту, загружаем таблицу, получаем данные от клиента (имя и адрес электронной почты пользователя) и записываем эти данные в таблицу. Перед записью данных пользователя, мы проверяем, что указанный пользователем email отсутствует в таблице. Если это не так, то мы сообщаем пользователю, что он уже подписан на обновления.


// Загружаем переменные среды окружения из файла ".env"require('dotenv').config()const { GoogleSpreadsheet } = require('google-spreadsheet')// Бессерверная функция (о ее сигнатуре мы поговорим позже)// В данном случае, нас интересует только первый аргумент, принимаемый функцией - `event`// `event` - это тоже самое, что `req` в `express`, т.е. объект запросаexports.handler = async (event) => {  // Создаем экземпляр класса, представляющего внутренний документ гугл таблиц  // Конструктор класса принимает идентификатор таблицы  const doc = new GoogleSpreadsheet(process.env.GOOGLE_SPREADSHEET_ID)  try {    // Выполняем авторизацию с помощью сервис-аккаунта    await doc.useServiceAccountAuth({      client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,      private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n')    })    // Загружаем данные документа    await doc.loadInfo()    // Получаем ссылку на созданную нами таблицу    const sheet = doc.sheetsByIndex[0]    // Получаем данные от клиента в формате JSON и преобразуем их в объект    const data = JSON.parse(event.body)    // Получаем строки таблицы    const rows = await sheet.getRows()    // Обратите внимание, что заголовки столбцов таблицы становятся одноименными свойствами строк    // Если какая-либо из строк содержит email, указанный пользователем,    // значит, пользователь уже оформил подписку на обновления    if (rows.some((row) => row.email === data.email)) {      // Формируем ответ      const response = {        statusCode: 400,        body: JSON.stringify({          error: 'Пользователь с таким email уже оформил подписку'        }),        // Про это поговорим позже        headers: {          'Access-Control-Allow-Origin': '*',          'Access-Control-Allow-Credentials': 'true'        }      }      // и возвращаем его      return response    }    // Добавляем данные пользователя в таблицу в виде новой строки    await sheet.addRow(data)    // Формируем ответ    const response = {      statusCode: 200,      body: JSON.stringify({ message: 'Спасибо за подписку!' }),      headers: {        'Access-Control-Allow-Origin': '*',        'Access-Control-Allow-Credentials': 'true'      }    }    // и возвращаем его    return response  } catch (err) {    // Обрабатываем ошибку, возникшую на стороне сервера    console.error(err)    const response = {      statusCode: 500,      body: JSON.stringify({ error: 'Что-то пошло не так. Попробуйте позже' }),      headers: {        'Access-Control-Allow-Origin': '*',        'Access-Control-Allow-Credentials': 'true'      }    }    return response  }}

Бессерверные функции имеют такую сигнатуру:


exports.handler = (event, context, callback) => {...}

Если очень коротко, то event, как было отмечено ранее, это объект запроса (данные, поступающие от клиента), context любая дополнительная информация, связанная с запросом, например, статус пользователя, callback нужен в случае синхронной (блокирующей) функции для возврата ответа (мы используем асинхронную функцию, поэтому нам данный аргумент не нужен, впрочем, как и аргумент context).


Что касается этих заголовков ответа:


headers: {  'Access-Control-Allow-Origin': '*',  'Access-Control-Allow-Credentials': 'true'}

То они связаны с внутренними настройками Netlify (с выполняемыми перенаправлениями при обращении к функции из клиента). Перенаправления блокируются CORS (Cross-Origin Resource Sharing доступ к ресурсу из другого источника), потому что бессерверные функции не совсем бессерверные, под капотом они работают на основе централизованного сервера. Эти заголовки не требуются для локальной разработки, но развернуть приложение на хостинге без них не получится. В официальной документации про это ни слова. Возможно, к тому моменту, когда вы будете читать данную статью, этот недостаток будет устранен.


Следует отметить, что эти заголовки можно указать для всех ответов в файле netlify.toml.


Итак, мы реализовали функцию для подписки на обновления. Вторую функцию мы реализуем позже, когда выясним, какие данные нам для этого нужны, как нам получить эти данные на клиенте и передать их в функцию.


Клиент


Устанавливаем зависимости для клиента:


yarn add react-router-dom semantic-ui-css semantic-ui-react react-google-recaptcha# илиnpm i ...

Код клиента находится в директории src. Удаляем из нее лишние файлы (оставляем только index.js и index.css), создаем директорию pages для страниц и hooks для пользовательских хуков. В директории pages создаем следующие файлы:


  • Home.js домашняя/главная страница
  • Subscribe.js страница с формой
  • Success.js страница с сообщением об успехе операции
  • NotFound.js резервная страница (ошибка 404)

В директории hooks создаем три файла:


  • useDeferredRoute.js хук для отложенной маршрутизации (опционально)
  • useTimeout.js хук-обертка для setTimeout
  • index.js экспорт индикатора загрузки и ре-экспорт хуков

Структура директории src:


src  hooks    index.js    useDeferredRoute.js    useTimeout.js  pages    Home.js    NotFound.js    Subscribe.js    Success.js  index.css  index.js

В index.css мы подключаем кастомный шрифт и вносим небольшие правки в стили semantic-ui:


@import url('https://fonts.googleapis.com/css2?family=Montserrat&display=swap');* {  font-family: 'Montserrat', sans-serif !important;}body {  min-height: 100vh;  display: grid;  align-content: center;  background: #8360c3;  background: linear-gradient(135deg, #2ebf91, #8360c3);}h2 {  margin-bottom: 3rem;}.ui.container {  max-width: 480px !important;  margin: 0 auto !important;  text-align: center;}.ui.form {  max-width: 300px;  margin: 0 auto;}.ui.form .field > label {  text-align: left;  font-size: 1.2rem;  margin-bottom: 0.8rem;}.ui.button {  margin-top: 1.5rem;  font-size: 1rem;  letter-spacing: 1px;  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) !important;}.email-error {  color: #f93154;  text-align: left;}

В index.js мы импортируем компоненты приложения и реализуем разделение кода на уровне маршрутов с помощью lazy и Suspense:


import React, { lazy, Suspense } from 'react'import ReactDOM from 'react-dom'// Средства для маршрутизацииimport { BrowserRouter as Router, Switch, Route } from 'react-router-dom'// Индикатор загрузкиimport { Spinner } from './hooks'// Стили `semantic-ui`import 'semantic-ui-css/semantic.min.css'// Кастомные стилиimport './index.css'// "Ленивые" компоненты - динамический импортconst Home = lazy(() => import('./pages/Home'))const Subscribe = lazy(() => import('./pages/Subscribe'))const Success = lazy(() => import('./pages/Success'))const NotFound = lazy(() => import('./pages/NotFound'))ReactDOM.render(  <Suspense fallback={<Spinner />}>    <Router>      <Switch>        <Route path='/' exact component={Home} />        <Route path='/subscribe' component={Subscribe} />        <Route path='/success' component={Success} />        <Route component={NotFound} />      </Switch>    </Router>  </Suspense>,  document.getElementById('root'))

Рассмотрим, что из себя представляют пользовательские хуки.


Хук useDeferredRoute добавляет искусственную задержку при начальной загрузке приложения и переходе пользователя к другой странице. Это совершенно не обязательно, возможно, кто-то даже скажет, что это плохая практика, но мне показалось, что так приложение будет более "живым". Кроме того, не так давно при работе над одним из проектов я столкнулся с необходимостью скрытия от пользователя позиционирования элементов фона отрендеренного компонента через искусственную задержку отображения компонента (вот где бы пригодился хук useTransition, но он пока еще экспериментальный).


import { useState, useEffect } from 'react'const sleep = (ms) => new Promise((r) => setTimeout(r, ms))export const useDeferredRoute = (ms) => {  const [loading, setLoading] = useState(true)  useEffect(() => {    const wait = async () => {      await sleep(ms)      setLoading(false)    }    wait()  }, [ms])  return { loading }}

Хук useTimeout, как было отмечено, это всего лишь обертка над нативным setTimeout:


import { useEffect, useRef } from 'react'export const useTimeout = (cb, ms) => {  const cbRef = useRef()  useEffect(() => {    cbRef.current = cb  }, [cb])  useEffect(() => {    function tick() {      cbRef.current()    }    if (ms > 1) {      const id = setTimeout(tick, ms)      return () => {        clearTimeout(id)      }    }  }, [ms])}

А вот как выглядит hooks/index.js:


// Мне не хотелось создавать директорию `components` для одного компонентаimport { Loader } from 'semantic-ui-react'export const Spinner = () => <Loader active inverted size='large' />export { useDeferredRoute } from './useDeferredRoute'export { useTimeout } from './useTimeout'

Теперь займемся страницами.


В Home.js нет ничего особенного. После скрытия индикатора загрузки, мы приветствуем пользователя и предлагаем ему подписаться на (кнопка "Подписаться" это на самом деле ссылка на страницу Subscribe):


import { Link } from 'react-router-dom'import { Container, Button } from 'semantic-ui-react'import { Spinner, useDeferredRoute } from '../hooks'function Home() {  const { loading } = useDeferredRoute(1500)  if (loading) return <Spinner />  return (    <Container>      <h2>Доброго времени суток!</h2>      <h3>        Подпишитесь на обновления, <br /> чтобы оставаться в курсе событий!      </h3>      <Button color='teal' as={Link} to='/subscribe'>        Подписаться      </Button>    </Container>  )}export default Home

В Success.js также нет ничего особенного. После скрытия индикатора загрузки, мы благодарим пользователя за подписку и выполняем автоматическое перенаправление на главную страницу через 3 секунды с помощью нашего хука useTimeout. На случай, если автоматического перенаправления не произошло, имеется кнопка-ссылка на страницу Home:


import { Link, useHistory } from 'react-router-dom'import { Container, Button } from 'semantic-ui-react'import { Spinner, useDeferredRoute, useTimeout } from '../hooks'function Success() {  const { loading } = useDeferredRoute(500)  const history = useHistory()  const redirectToHomePage = () => {    history.push('/')  }  useTimeout(redirectToHomePage, 3000)  if (loading) return <Spinner />  return (    <Container>      <h2>Спасибо за подписку!</h2>      <h3>Сейчас вы будете перенаправлены на главную страницу</h3>      <Button color='teal' as={Link} to='/'>        На главную      </Button>    </Container>  )}export default Success

Еще одна простая страница NotFound пользователь попадает на эту страницу при отсутствии совпадения с маршрутами приложения:


import { Link, useHistory } from 'react-router-dom'import { Container, Button } from 'semantic-ui-react'import { Spinner, useDeferredRoute, useTimeout } from '../hooks'function NotFound() {  const { loading } = useDeferredRoute(500)  const history = useHistory()  const redirectToHomePage = () => {    history.push('/')  }  useTimeout(redirectToHomePage, 2000)  if (loading) return <Spinner />  return (    <Container>      <h2>Страница отсутствует</h2>      <h3>Сейчас вы будете перенаправлены на главную страницу</h3>      <Button color='teal' as={Link} to='/'>        На главную      </Button>    </Container>  )}export default NotFound

На странице Subscribe используется компонент react-google-recaptcha, которому в качестве пропа передается ключ сайта (sitekey). Данный ключ можно получить в административной консоли Google ReCAPTCHA, но для этого приложение надо сначала развернуть на Netlify. К счастью, для локальной разработки можно использовать этот тестовый ключ (это официальный ключ для тестирования): 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI. Позже мы вернемся к этому вопросу.


Еще один важный момент это конечная точка отправки пользовательских данных. Она должна начинаться с /.netlify/, затем указывается путь к соответствующей функции: functions/subscribe /.netlify/functions/subscribe (название функции часть пути). Следует отметить, что часть пути /.netlify/functions можно изменить в netlify.toml.


В остальном, все просто: после скрытия индикатора загрузки, мы предлагаем пользователю ввести свое имя и адрес электронной почты, а также подтвердить, что он "не робот". После заполнения всех полей и прохождения проверки с кнопки "Подписаться" снимается блокировка. При нажатии этой кнопки данные пользователя отправляются в функцию, которая записывает их в таблицу.


import { useState } from 'react'import { useHistory } from 'react-router-dom'import { Container, Form, Button } from 'semantic-ui-react'import ReCAPTCHA from 'react-google-recaptcha'import { Spinner, useDeferredRoute } from '../hooks'// Утилита для проверки того, что все поля заполненыconst isEmpty = (fields) => fields.some((f) => f.trim() === '')// Простой вариант утилиты для проверки адреса электронной почтыconst isEmail = (v) => /\w+@\w+\.\w+/i.test(v)function Subscribe() {  const [formData, setFormData] = useState({    username: '',    email: ''  })  const [error, setError] = useState(null)  const [recaptcha, setRecaptcha] = useState(false)  const { loading } = useDeferredRoute(1000)  const history = useHistory()  const onChange = ({ target: { name, value } }) => {    setError(null)    setFormData({      ...formData,      [name]: value    })  }  const onSubmit = async (e) => {    e.preventDefault()    const email = isEmail(formData.email)    if (!email) {      return setError('Введен неправильный email')    }    try {      const response = await fetch('/.netlify/functions/subscribe', {        method: 'POST',        body: JSON.stringify(formData),        headers: {          'Content-Type': 'application/json'        }      })      if (!response.ok) {        const json = await response.json()        return setError(json.error)      }      history.push('/success')    } catch (err) {      console.error(err)    }  }  // Учитывая, что мы используем тестовый ключ, капча всегда будет иметь истинное значение  const disabled = isEmpty(Object.values(formData)) || !recaptcha  const { username, email } = formData  if (loading) return <Spinner />  return (    <Container>      <h2>Подписаться на уведомления</h2>      <Form onSubmit={onSubmit}>        <Form.Field>          <label>Ваше имя</label>          <input            placeholder='Имя'            type='text'            name='username'            value={username}            onChange={onChange}            required          />        </Form.Field>        <Form.Field>          <label>Ваш email</label>          <input            placeholder='Email'            type='email'            name='email'            value={email}            onChange={onChange}            required          />        </Form.Field>        <p className='email-error'>{error}</p>        <ReCAPTCHA          sitekey='6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'          onChange={() => setRecaptcha(true)}        />        <Button color='teal' type='submit' disabled={disabled}>          Подписаться        </Button>      </Form>    </Container>  )}export default Subscribe

Итак, у нас имеется клиентская часть приложения и функция для записи данных пользователя в таблицу. Пришло время проверить работоспособность приложения.


Если вы еще не установили netlify-cli, самое время это сделать:


yarn global add netlify-cli# илиnpm i -g ...

Все, что нужно сделать для запуска приложения и инициализации функции, это выполнить следующую команду:


netlify dev

После выполнения указанной команды клиент будет запущен по адресу localhost:3000, а сервер также на локальном хосте, но с портом 8888.


Прелесть в том, что netlify-cli умеет автоматически определять, какой фреймворк используется в проекте, и выполнять нужные команды для его запуска.


Нажимаем на кнопку "Подписаться", заполняем поля формы, проходим проверку и снова нажимаем на "Подписаться". Выполняется перенаправление на страницу с сообщением об успехе операции, затем еще одно перенаправление на главную страницу. Открываем таблицу, видим введенные нами данные.


.


.


.


Отлично, приложение работает, как ожидается.


В следующей части туториала мы развернем приложение на Netlify, закончим настройку капчи, реализуем автоматическую рассылку уведомлений и отписку от обновлений.


Благодарю за внимание и хорошего дня!




Облачные серверы от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

Реализация подписки на обновления с помощью Google Sheets, Netlify Functions и React. Часть 2

08.06.2021 12:19:01 | Автор: admin

Это вторая часть туториала, посвященного реализации Real World App подписки на обновления с помощью гугл таблиц, бессерверных функций и реакта.


Вот ссылка на первую часть.


Напомню, что основной функционал нашего приложения, который мы реализовали в первой части туториала, является следующим:


  • на главной странице отображается приветствие и предложение подписаться на обновления
  • при нажатии на кнопку Подписаться, пользователь попадает на страницу с формой, содержащей два поля: имя и адрес электронной почты
  • для защиты от ботов используется гугл рекапча 2 версии
  • при заполнении полей и прохождения проверки снимается блокировка с кнопки Подписаться
  • при нажатии этой кнопки данные пользователя отправляются в таблицу с помощью бессерверной функции

Дополнительный функционал, реализацией которого мы займемся в этой части:


  • с помощью скрипта осуществляется автоматическая рассылка уведомлений
  • в рассылаемых письмах содержится ссылка на отписку от обновлений
  • при переходе по этой ссылке адрес электронной почты передается бессерверной функции, с помощью которой из таблиц удаляется соответствующая строка

Демо приложения, разработкой которого мы занимаемся, можно посмотреть здесь (оно вполне работоспособное, если хотите, можете подписаться на обновления).


Код приложения находится здесь.


Для реализации приложения используются следующие технологии:


  • netlify-cli интерфейс командной строки для запуска сервера для разработки (инициализации бессерверных функций) и "деплоя" приложения на Netlify; требуется глобальная установка: yarn global add netlify-cli или npm i -g netlify-cli; обязательно
  • google-spreadsheet JavaScript-библиотека для работы с гугл таблицами; обязательно
  • react на мой взгляд, это лучший JavaScript-фреймворк для фронтенда, но вы можете использовать любую другую библиотеку; наши бессерверные функции не зависят от конкретного фреймворка
  • react-router-dom React-библиотека для маршрутизации
  • semantic-ui-react React-CSS-фреймворк
  • react-google-recaptcha React-компонент, позволяющий напрямую взаимодействовать с соответствующим сервисом
  • nodemailer наиболее популярная Node.js-библиотека для работы с электронной почтой (рассылки писем)
  • dotenv утилита для доступа к переменным среды окружения

Начнем с деплоя приложения на Netlify.


Деплой приложения


Заходим на Netlify, создаем аккаунт, затем вводим в терминале следующую команду:


netlify login

Вводим логин и пароль, получаем сообщение об успешной авторизации.


Выполняем сборку проекта:


yarn build# илиnpm run build

И разворачиваем приложение в тестовом режиме:


netlify deploy

Отвечаем на вопросы (новое приложение, название приложения (например, mail-list), директория для деплоя (build) и т.д.), получаем ссылку на развернутое приложение.


Переходим по ссылке, видим, что приложение не работает. Почему? Потому что мы не добавили переменные среды окружения.


Переходим в раздел sites, открываем наше приложение, выбираем вкладку Site settings, затем вкладку Build & deploy, находим раздел Environment, добавляем переменные (Environment variables).




Не будем ходить вокруг да около, а сразу развернем приложение в продакшн-режиме:


netlify deploy --prod

Готово. Легко, правда? Вот за что я люблю Netlify.


Теперь, когда у нас имеется URL, мы можем зарегистрировать наше приложение в Google ReCAPTCHA.


Заходим в консоль администратора и создаем новое приложение (+). Вводим название сайта (ярлык), выбираем reCAPTCHA v2, указываем домен (URL нашего приложения без протокола), принимаем условия использования (флажок "Отправлять владельцам оповещения" можно снять), нажимаем "Отправить". Получаем ключ сайта и секретный ключ, нам нужен только первый.





Добавляем в .env такую переменную:


REACT_APP_GOOGLE_RECAPTCHA_SITE_KEY=YOUR_SITE_KEY

Вносим изменение в Subscribe.js:


<ReCAPTCHAsitekey={process.env.REACT_APP_GOOGLE_RECAPTCHA_SITE_KEY}onChange={() => setRecaptcha(true)}/>

Повторно собираем и разворачиваем приложение:


yarn build# илиnpm run build# иnetlify deploy --prod

Если все сделано правильно, то на странице с формой появится настоящая капча.



Отлично.


Переходим к автоматизации рассылки уведомлений и реализации возможности отписаться от обновлений.


Автоматическая рассылка уведомлений


Давайте подумаем о том, что нам нужно для того, чтобы пользователь имел возможность отписаться от обновлений.


Мы, например, могли бы добавлять в уведомление специальную ссылку, при переходе по которой выполняется отписка. Для идентификации пользователя, выразившего такое желание, нам нужен его адрес электронной почты. Следовательно, ссылка должна формироваться из пути к соответствующей странице нашего приложения + email пользователя (для того, чтобы мы могли извлекать его из параметров строки запроса).


Для реализации автоматической рассылки уведомлений мы будем использовать nodemailer, а для тестирования Mailtrap.


Создаем аккаунт на Mailtrap, открываем автоматически созданный проект MyInbox, на вкладке SMTP Settings в разделе Integrations выбираем Node.js -> Nodemailer, получаем данные для авторизации.




Сохраняем эти данные в .env:


SMTP_USER='USER'SMTP_PASS='PASS'

В корне проекта создаем директорию send-mail, а в ней index.js следующего содержания:


require('dotenv').config()const nodemailer = require('nodemailer')const { GoogleSpreadsheet } = require('google-spreadsheet')const doc = new GoogleSpreadsheet(process.env.GOOGLE_SPREADSHEET_ID)// Тестовый транспортер для отправки сообщенийconst testTransporter = nodemailer.createTransport({host: 'smtp.mailtrap.io',port: 2525,auth: {user: process.env.SMTP_USER,pass: process.env.SMTP_PASS}})// Функция для создания сообщения в формате HTML// Она принимает имя пользователя и его email// Обратите внимание на значение атрибута `href` тега `a` -// URL соответствующей страницы нашего приложения (скоро мы ее создадим) + email пользователяconst createMessage = (username, email) => `<p><strong>Уважаемый ${username} </strong>, <em>спасибо за подписку</em>!</p><p>Для того, чтобы отписаться от обновлений, перейдите по <a href="http://personeltest.ru/aways/mail-list.netlify.app/unsubscribe/${email}" target="_blank">этой ссылке</a></p>`const sendMail = async () => {try {await doc.useServiceAccountAuth({client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n')})await doc.loadInfo()const sheet = doc.sheetsByIndex[0]const rows = await sheet.getRows()// Перебираем строки таблицы  данные пользователей,// создаем сообщение и отправляем его// text  резервный контент на случай, если почтовый клиент пользователя не поддерживаем сообщения в формате HTMLrows.forEach(async (row) => {await testTransporter.sendMail({from: 'Mail list <mail-list.netlify.app>',to: row.email,subject: 'Благодарность за подписку',text: 'Спасибо за подписку',html: createMessage(row.username, row.email)})})console.log('Сообщения отправлены')} catch (err) {console.error(err)}}sendMail()

Добавим в package.json (раздел scripts) команду для рассылки уведомлений:


send: node send-mail/index.js

Запускаем скрипт (разумеется, в таблице должны быть какие-то данные):


yarn send# илиnpm run send

Получаем Сообщения отправлены в терминале и письмо в Mailtrap.



Для взаимодействия с реальными почтовыми службами (yahoo в моем случае) нужен реальный SMTP-провайдер.


Среди наиболее популярных решений можно назвать SendGrid и SendingBlue, но в случае выбора одного из этих сервисов, нам придется долго и упорно убеждать их владельцев в том, что мы не собираемся заниматься рассылкой спама. Еще есть Mailgun, но он платный с трехмесячным free trial.


Поэтому мы будем использовать Gmail.


Безусловно, если очень хочется, можно поднять собственный SMPT-сервер. Также существуют инструменты для рассылки писем, которые, как заявляют их разработчики, работают без SMTP, например, sendmail.


Добавляем в .env переменные с данными вашего Gmail-аккаунта:


GMAIL_USER='USER'GMAIL_PASS='PASS'

И вносим изменения в send-mail/index.js:


/*const testTransporter = nodemailer.createTransport({host: 'smtp.mailtrap.io',port: 2525,auth: {user: process.env.SMTP_USER,pass: process.env.SMTP_PASS}})*/const gmailTransporter = nodemailer.createTransport({service: 'gmail',auth: {user: process.env.GMAIL_USER,pass: process.env.GMAIL_PASS}})rows.forEach(async (row) => {await gmailTransporter.sendMail({// ...})})

Запускаем скрипт (в таблице должен быть указан ваш email):


yarn send


Существует один нюанс, связанный с использованием Gmail в качестве сервиса для рассылки писем гугл может блокировать к нему доступ, считая приложение ненадежным (существует платная версия Gmail Google Workspace, которая с точки зрения гугла, конечно же, является надежной).


Для того, чтобы снять блокировку, заходим в настройки гугл аккаунта, открываем вкладку Безопасность, переходим в раздел Ненадежные приложения, у которых есть доступ к аккаунту и разрешаем небезопасные приложения.



С реализацией автоматической рассылки мы закончили, но при переходе по ссылке, указанной в письме, мы попадаем на резервную страницу, поскольку в нашем приложении нет страницы для отписки от обновлений, как и соответствующей бессерверной функции. Давайте это исправим.


Отписка от обновлений


Добавляем в приложение (src/pages) новую страницу Unsubscribe.js. На этой странице после скрытия индикатора загрузки, мы пытаемся получить email пользователя из параметров строки запроса с помощью хука useParams. Если email отсутствует, выполняется перенаправление на главную страницу. Иначе мы отправляем email в функцию, которая удаляет из таблицы соответствующую строку. Если пользователь с указанным email не оформлял подписку на обновления, выбрасывается исключение. При успешном завершении операции отображается сообщение о том, что пользователь больше не будет получать уведомлений.


import { useState, useEffect } from 'react'import { Link, useParams, useHistory } from 'react-router-dom'import { Container, Button } from 'semantic-ui-react'import { Spinner, useDeferredRoute } from '../hooks'function Unsubscribe() {const { loading } = useDeferredRoute(1000)const [error, setError] = useState(null)// Извлекаем email из параметров строки запросаconst { email } = useParams()const history = useHistory()useEffect(() => {// Если email отсутствует, выполняем перенаправление на главную страницуif (!email) {return history.push('/')}async function unsubscribe() {try {// Отправляем email в функциюconst response = await fetch('/.netlify/functions/unsubscribe', {method: 'POST',body: JSON.stringify(email),headers: {'Content-Type': 'application/json'}})// Если возникла ошибка, значит, пользователь не оформлял подпискуif (!response.ok) {const json = await response.json()setError(json.error)}} catch (err) {console.error(err)}}unsubscribe()// eslint-disable-next-line}, [])if (loading) return <Spinner />return (<Container>{error ? (<h3>{error}</h3>) : (<h3>Вы больше не будете получать уведомлений</h3>)}<Button color='teal' as={Link} to='/'>На главную</Button></Container>)}export default Unsubscribe

Все, что нам осталось сделать, это реализовать функцию unsubscribe. Она очень похожа на функцию subscribe загружается таблица, выполняется поиск и удаление соответствующей строки:


require('dotenv').config()const { GoogleSpreadsheet } = require('google-spreadsheet')exports.handler = async (event) => {const doc = new GoogleSpreadsheet(process.env.GOOGLE_SPREADSHEET_ID)try {await doc.useServiceAccountAuth({client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n')})await doc.loadInfo()const sheet = doc.sheetsByIndex[0]// Получаем email пользователяconst data = JSON.parse(event.body)const rows = await sheet.getRows()// Выполняем поиск соответствующей строкиconst index = rows.findIndex((row) => row.email === data)// Если строка не найдена, значит, пользователь не оформлял подпискуif (index === -1) {const response = {statusCode: 400,body: JSON.stringify({error: 'Пользователь с указанным email не найден'}),headers: {'Access-Control-Allow-Origin': '*','Access-Control-Allow-Credentials': 'true'}}return response}// Удаляем строкуawait rows[index].delete()const response = {statusCode: 200,body: JSON.stringify({ message: 'Пользователь удален' }),headers: {'Access-Control-Allow-Origin': '*','Access-Control-Allow-Credentials': 'true'}}return response} catch (err) {console.error(err)const response = {statusCode: 500,body: JSON.stringify({ error: 'Что-то пошло не так. Попробуйте позже' }),headers: {'Access-Control-Allow-Origin': '*','Access-Control-Allow-Credentials': 'true'}}return response}}

Еще раз (обещаю, что в последний) собираем и разворачиваем проект:


yarn build# илиnpm run build# иnetlify deploy --prod

Не забудьте обновить переменные среды окружения на Netlify.


Теперь при переходе по ссылке, указанной в письме, пользователь попадает на страницу с сообщением о том, что он успешно отписался от обновлений, а в таблице удаляется соответствующая строка. При переходе на данную страницу без указания email выполняется перенаправление на главную страницу, а при указании несуществующего email отображается сообщение о том, что пользователь с таким email не найден.




Туториал получился гораздо больше, чем я изначально планировал, но, надеюсь, вы не жалеете потраченного времени. Я постарался максимально подробно осветить все аспекты, связанные с разработкой нашего приложения, прошу меня извинить, если что-то упустил.


Как бы то ни было, если вы в точности следовали инструкциям, а еще лучше реализовали какие-то дополнительные возможности, то в вашем портфолио появилось настоящее Real World App, разработанное с использованием самых современных технологий (да, мы не использовали TypeScript, но для нашего небольшого проекта это было бы слишком круто).




Облачные серверы от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru