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

Node

Как npm обеспечивает безопасность

20.08.2020 12:11:50 | Автор: admin

Как npm обеспечивает безопасность


Всем привет! В предыдущих постах мы подробно поговорили про выбор зависимостей и использование lock-файлов в npm, однако я только вскользь касался вопросов безопасности. Пришло время исправить этот недочет: этот и следующий посты будут полностью посвящены безопасности в npm! И для начала мы рассмотрим, как безопасность обеспечивается на уровне инфраструктуры npm и экосистемы в целом.


Мы живем в опасное время


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


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


npm как источник угроз


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


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



Малоизвестно, но разработчики npm активно использовали помощь компании ^Lyft Security (эксперта по компьютерной безопасности) с самого начала разработки платформы: специалисты из Lyft не только активно проверяли исходный код npm на наличие уязвимостей, но и проводили регулярные аудиты безопасности и pen-тесты серверной инфраструктуры. Помимо прочего, консультанты из Lyft активно участвовали в разработке так называемой Node Security Platform (NSP) [платформы безопасности Node], которая отвечает за безопасность экосистемы Node и npm-пакетов. Партнерство двух компаний оказалось настолько удачным, что в начале 2018-го года npm Inc. просто купила ^Lyft Security, и её эксперты по безопасности напрямую влились в команду npm.


Таким образом, компания npm обладает выделенной командой экспертов по безопасности, которые занимаются исключительно этими вопросами, существенно улучшая здоровье самой крупной в мире экосистемы (JavaScript). За всё время работы над Node Security Platform компании npm удалось внедрить много важных решений и инструментов, которые значительно повышают безопасность и позволяют нам чуточку лучше спать по ночам.


Кроме того, в начале 2020 года компания GitHub (Microsoft) купила npm Inc., что автоматически увеличило ресурсы компании и открыло прямой путь для интеграции между npm и GitHub. А, как известно, у GitHub также имеется своя серьезная команда по безопасности GitHub Security Lab. Наличие таких значительных ресурсов и возможностей для коллаборации должно еще сильнее повысить безопасность экосистемы, ведь теперь Microsoft может полностью отследить и обезопасить путь от исходного кода на GitHub до скомпилированного пакета в npm registry.




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


Сканирование пакетов


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


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


Чтобы повысить эффективность сканера, команде npm удалось собрать самую большую в мире библиотеку вредоносного кода на JavaScript, которая постоянно пополняется. Эта библиотека, в том числе, содержит списки опасных доменных имен, IP-адресов и URL, которые могут использоваться злоумышленниками, а также другие индикаторы заражения. Команда npm планирует открыть доступ к этой библиотеке, которая получила название Security Insight API. Это позволит энтузиастам со стороны разрабатывать собственные решения, которые еще более повысят безопасность экосистемы.


Автоматический отзыв токенов аутентификации


npm поддерживает аутентификацию при помощи специальных токенов вместо логина и пароля. Это необходимо для публикации пакетов в npm registry, для работы с закрытыми (private) пакетами в рамках автоматизированных процессов (например, используя CI/CD-конвейер), а также для интеграции со сторонними решениями.


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



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


Однако нужно учитывать, что если npm автоматически отзовет токен, который необходим для работы какой-то критической инфраструктуры (например, конвейера по выкатке приложения в production), то это приведет к поломке, потому что интеграция с npm registry будет нарушена.


Компрометация паролей



Довольно часто пользователи используют один и тот же пароль между различными сервисами и аккаунтами. При этом они могут даже не догадываться, что их данные оказались в публичном доступе из-за утечки с одного из сайтов, где они были зарегистрированы. Сервис Have I Been Pwned содержит огромную базу данных утечек и позволяет по вашему E-mail адресу определить, попали ли именно ваши данные в одну из них. Например, мой адрес на GMail, которым я пользуюсь уже 11 лет, попал аж в 14 утечек. Я уверен, вас тоже ждут интересные новости!


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


Ручной аудит пакетов


Одним из важнейших способов обнаружения уязвимостей является ручной аудит пакетов. Несмотря на то, что команда безопасности npm работает круглосуточно в режиме 24/7, проверить все публикуемые пакеты в ручном режиме невозможно, но это и не требуется. Сообщество разработчиков JavaScript, а также различные партнеры и третьи стороны каждый день отправляют в npm отчеты о найденных уязвимостях с указанием проблемных пакетов и их версий. В неделю npm насчитывает порядка 25 подобных обращений. Специалисты из команды безопасности тщательно проверяют каждый отчет об уязвимости и стараются подтвердить проблему в лабораторных условиях на специальном исследовательском стенде.


В случае обнаружения вредоносного кода (malware), проблемный пакет удаляется из реестра npm, чтобы предотвратить его дальнейшее распространение. Кроме того, команда npm пытается вычислить и другие пакеты, которые могут содержать аналогичный вредоносный код или иметь схожее вредоносное поведение.


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


Ещё нужно понимать, что информация об уязвимости обязательно включается в базу данных npm в качестве так называемой security advisory. А учитывая очень широкий охват аудитории (через npm audit), информация становится доступна всем, включая злоумышленников, которые могут воспользоваться найденной уязвимостью в корыстных целях. По этой причине важно, чтобы автор уязвимого пакета узнал об этом первым и мог оперативно принять необходимые меры (выпустить исправленную версию). Если уязвимость уже публично известна, то команда npm дает автору пакета 48 часов на её устранение, после этого уязвимость будет добавлена в общую базу. Если же информация об уязвимости была передана в npm в частном порядке и не публиковалась в открытом доступе, то команда безопасности дает автору пакета 45 дней на выпуск обновления и не публикует информацию официально, чтобы она не попала в руки хакеров, которые непременно захотят ей воспользоваться.



По словам специалистов из npm, из всех отчетов об уязвимостях, которые им присылают, только 20 % в итоге подтверждаются и доходят до публикации. На момент написания этого поста в базе npm было 1427 рекомендаций по безопасности (security advisories). Полный список рекомендаций можно посмотреть на официальном сайте в специальном разделе. Также доступен список рекомендаций со стороны GitHub.


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


Продолжение следует


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


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


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


Если у вас есть вопросы или желание что-то добавить по теме, то не бойтесь оставлять комментарии, я с радостью приму участие в обсуждении и учту ваши пожелания в следующих постах.

Подробнее..

Перевод Руководство по Node.js для начинающих. Часть 1. Быстрый старт

01.07.2020 14:11:33 | Автор: admin


Доброго времени суток, друзья!

Представляю Вашему вниманию перевод этого руководства по Node.js.

Введение в Node.js


Node.js это открытая и кроссплатформенная среда выполнения JavaScript. Это отличное решение почти для любого проекта.

Node.js запускает движок JavaScript V8, ядро Google Chrome, вне браузера. Это делает Node.js очень производительным.

Node.js-приложения выполняются как один процессе, без создания нового потока для каждого запроса. Node.js предоставляет набор асинхронных примитивов ввода/вывода в стандартной библиотеке, что защищает JavaScript-код от блокировки и, обычно, библиотеки в Node.js написаны с использованием неблокирующих парадигм, что делает блокирующее поведение скорее исключением, чем правилом.

Когда Node.js выполняет операцию ввода/вывода, например, чтение (данных) из сети, доступ к базе данных или файловой системе, вместо того, чтобы блокировать поток и ожидать завершения циклов ЦП, Node.js продолжит выполнять операцию после получения ответа.

Это позволяет Node.js обрабатывать одновременно тысячи запросов посредством одного сервера без необходимости создания системы обеспечения согласованности потока, которая может стать источником серьезных ошибок.

Существенное преимущество Node.js состоит в том, что миллионы разработчиков, пишущих код на JavaScript для браузера, теперь имеют возможность писать серверный код в дополнение к клиентскому без необходимости изучать совершенно другой язык (программирования).

В Node.js новые ECMAScript-стандарты могут использоваться без проблем, вам не нужно ждать, пока все пользователи обновят браузеры вы сами решаете, какую версию ECMAScript использовать посредством изменения версии Node.js, вы также можете добавить экспериментальные возможности, запустив Node.js с (соответствующими) флагами.

Огромное количество библиотек

Npm с его простой структурой способствует быстрому росту экосистемы Node.js, на сегодняшний день в npm зарегистрировано свыше 1 000 000 открытых пакетов, которые вы может использовать совершенно бесплатно.

Пример Node.js-приложения

Наиболее распространенным примером использования Node.js является создание веб-сервера:

    const http = require('http')const hostname = '127.0.0.1'const port = process.env.PORT const server = http.createServer((req, res) => {    res.statusCode = 200    res.setHeader('Content-Type', 'text/plain')    res.end('Hello World!\n')})server.listen(port, hostname, () => {    console.log(`Server running at http://${hostname}:${port}/`)})

Первым делом мы подключаем модуль http.

Node.js имеет фантастическую стандартную библиотеку, включающую первоклассную поддержку работы с сетью.

Метод createServer() http создает новый HTTP-сервер и возвращает его.

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

При получениее нового запроса вызывается событие request, содержащее два объекта: запрос (объект http.IncomingMessage (входящее сообщение)) и ответ (объект http.ServerResponse (ответ сервера)).

Эти объекты необходимы для обработки вызова HTTP.

Первый объект содержит детали запроса. В нашем примере он не используется, но вы можете получить доступ к заголовкам запроса и его данным.

Второй объект используется для предоставления данных отправителю запроса.

В данном случае посредством

    res.statusCode = 200

мы присваиваем свойству statusCode значение 200 в качестве индикатора успешного выполнения запроса.

Мы устанавливает заголовок Content-Type (тип содержимого или контента)

    res.setHeader('Content-Type', 'text/plain')

и закрываем ответ, добавляя контент в качестве аргумента в end():

    res.end('Hello World\n')

Node.js-фреймворки и инструменты

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

Со временем многие из них стали очень популярными. Вот список некоторых из них:

  • AdonisJs: фуллстек-фреймворк с акцентом на эргономику, стабильность и надежность. Adonis один из самых быстрых веб-фреймворков для Node.js.
  • Express: предоставляет один из самых простых и одновременно мощных способов создания веб-сервера. Его ключем к успеху является минималистичный подход, простой функционал, акцент на основных возможностях сервера.
  • Fastify: веб-фреймворк, сфокусированный на обеспечении лучшего опыта разработки с минимальными накладными расходами и мощной архитектурой плагина. Fastify является одним из самых проивзодительных веб-фреймворков.
  • hapi: фреймворк с богатым функционалом для создания приложений и сервисов, позволяющий разработчикам сосредоточиться на написании переиспользуемой логики приложений вместо траты времени на построение инфраструктуры.
  • koa: создан командой Express в целях упрощения и уменьшения размера с учетом многолетнего опыта. Новый проект возник из-за необходимости внесения несовместимых изменений без разрушения сообщества, сформировавшего вокруг Express.
  • Loopback.io: облегчает создание современных приложений со сложной системой зависимостей.
  • Meteor: невероятно мощный фуллстек-фреймворк, предоставляющий изоморфный подход для создания приложений на JavaScript, разделяя код между клиентом и сервером. Входит в комлект таких библиотек, как React, Vue и Angular. Также может использоваться для создания мобильных приложений.
  • Micro: предоставляет легковесный сервер для создания асинхронных HTTP-микросервисов.
  • NestJS: основанный на TypeScript прогрессивный Node.js-фреймворк для создания корпоративных, надежных и масштабируемых серверных приложений.
  • Next.js: фреймоврк для рендеринга React-приложений на стороне сервера.
  • Nx: инструмент для фуллстек монолитной разработки посредством NestJS, Express, React, Angular и т.д. Nx помогает масштабировать разработку от одной до нескольких команд, работающих одновременно над множеством приложений.
  • Socket.io: движок для коммуникации в режиме реального времени для создания сетевых приложений.

Краткая история Node.js


Верите или нет, но Node.js всего 10 лет.

Для сравнения: JavaScript существует на протяжении 24 лет, а веб 30.

10 лет это небольшой срок для технологии, однако порой кажется, что Node.js был всегда.

Я познакомился с Node.js, когда прошло всего 2 года с момента его появления, но уже тогда, несмотря на ограниченность информации, чувствовалось, что его ждет большое будущее.

В этом разделе мы посмотрим на общую картину истории Node.js.

Немного истории

JavaScript это язык программирования, изобретенный в Netscape как скриптовый инструмент манипуляции веб-страницами в браузере Netscape Navigator.

Частью бизнес-модели Netscape являлась продажа веб-серверов, включающих среду Netscape LiveWire, способную создавать динамические страницы посредством серверного JavaScript. К сожалению, Netscape LiveWire провалился и серверный JavaScript не был популярен до появления Node.js.

Одним из ключевых факторов популярности Node.js является время (его появления). Несколькими годами ранее JavaScript был признан серьезным языком (программирования) благодаря Web 2.0-приложениям (таким как Flickr, Gmail и др.), показавших миру, как может выглядеть современный веб.

Движки JavaScript также стали значительно лучше, поскольку браузеры стремились к повышению производительности во благо пользователей. Команды разработчиком главных браузеров упорно трудились над реализацией лучшей поддержки JavaScript и его максимально быстрого выполнения. Движок, который использует Node.js, V8 (также известный как Chrome V8 открытый движок JavaScript проекта Chromium), вышел победителем из этого соревнования.

Node.js появился в нужное время и в нужном месте. К счастью, это не единственная причина его популярности. Проект содержал большое количество инновационных идей и подходов к серверной JavaScript-разработке, которые пришлись по вкусу многим разработчикам.

2009

  • Появился Node.js
  • Появился npm

2010

  • Express
  • Socket.io

2011

  • npm версии 1.0
  • Крупные компании начали внедрять Node.js: LinkedIn, Uber и др.
  • hapi

2012

  • Продолжается быстрый рост популярности Node.js

2013

  • Первая крупная блог-платформа на Node.js: Ghost
  • Koa

2014

  • Большой раскол: из Node.js выделился io.js (форк создание обособленной ветки в системе контроля версий git) ради поддержки синтаксиса ES6 и более динамичного развития

2015

  • Основание Node.js Foundation
  • IO.js вернулся в Node.js (мерж слияние веток в git)
  • В npm появились частные (приватные) модули
  • Node.js версии 4 (версий 1, 2 и 3 не было)

2016


2017

  • Повышение безопасности npm
  • Node.js 8
  • HTTP/2
  • V8 включил Node.js в комплект тестирования, официально признав Node.js движком JS в дополнение к Chrome
  • 3 млрд скачиваний npm каждую неделю

2018

  • Node.js 10
  • Экспериментальная поддержка ES-модулей с расширением .mjs

Как установить Node.js?


Node.js может быть установлен различными способами.

Дистрибутивы для основных платформ доступны на официальном сайте.

Очень удобным способом установки Node.js является использование пакетного менеджера. У каждой операционной системы он свой.

На macOS таковым является Homebrew, позволяющий легко установить Node.js с помощью командной строки:

brew install node

Список пакетных менеджеров для Linux, Windows и др. систем находится здесь.

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

Он также очень полезен при необходимости тестирования кода в старых версиях Node.js.

Для более подробной информации о nvm перейдите по этой ссылке.

Мой совет используйте официальный установщик, если только начинаете разрабатывать и ранее не пользовались Homebrew.

После установки Node.js вы получаете доступ к исполянемой программе node в командной строке.

Насколько хорошо вы должны знать JavaScript для работы с Node.js?


Будучи новичком, сложно определить свой уровень программирования.

Также сложно определить, где заканчивается JavaScript и начинается Node.js, и наоборот.

Лично я бы посоветовал хорошенько разобраться со следующими основными концепциями JavaScript перед погружением в Node.js:

  • Синтаксис или лексическая структура
  • Выражения (по сути, тот же синтаксис)
  • Типы (данных)
  • Переменные
  • Функции
  • Ключевое слово this
  • Стрелочные функции
  • Циклы
  • Область видимости
  • Массивы
  • Шаблонные или строковые литералы
  • Точка с запятой (вероятно, случаи ее обязательного использования, например, при работе с IIFE)
  • Строгий режим
  • ECMAScript 6, 2016, 2017

Освоение названных концепций начало пути профессионального фуллстек-разработчика.

Следующие концепции также являются ключом к пониманию асинхронного программирования, которое является фундаментальной частью Node.js:

  • Асинхронное программирование и функции обратного вызова (коллбэки)
  • Таймеры (счетчики)
  • Промисы (обещания)
  • Async/await
  • Замыкания
  • Цикл событий (стек вызовов)

Разница между Node.js и браузером


JavaScript может быть использован как в браузере, так и в Node.js.

Однако создание приложений для браузера сильно отличается от создания Node.js-приложений.

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

С точки зрения JavaScript-фронтендера (разработчика фронтенда клиентской части приложения), разработка приложений на Node.js имеет существенное преимущество, выражающееся в том, что везде, и на клиенте, и на сервере, используется один язык программирования JavaScript.

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

Единственное, что потребуется изучить это экосистема.

В браузере большую часть времени нам приходится иметь дело с DOM и другими веб-API, например, куки. Разумеется, их не существует в Node.js. В Node.js отсутствуют window, document и другие объекты, характерные для браузера.

С другой стороны, в браузерах отсутствуют замечательные API, предоставляемые Node.js посредством модулей, например, доступ к файловой системе.

Другим серьезным отличием является то обстоятельство, что в Node.js вы контролируете среду выполнения кода. Если вы не разрабатываете открытое приложение, которое любой желающий может развернуть где угодно, вы знаете какая версия Node.js используется для запуска приложения. Это очень удобно в отличие от браузерной среды выполнения, поскольку пользователи сами решают, какой браузер им использовать.

Это означает, что вы можете писать код на JavaScript, поддерживаемом вашей версией Node.js.

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

Для транспиляции кода в ES5 вы можете использовать Babel, в Node.js такой необходимости не возникает.

Еще одним отличием является то, что в Node.js используется модульная система CommonJS, а в браузерах реализована поддержка ES-модулей.

На практике это означает, что в Node.js мы используем require(), а в браузере import.

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

Продолжение следует
Подробнее..

Руководство по Node.js для начинающих. Часть 4

14.07.2020 14:04:30 | Автор: admin


Доброго времени суток, друзья!

Продолжаю публиковать перевод этого руководства по Node.js.

Другие части:

Часть 1
Часть 2
Часть 3
Часть 4

Файл package-lock.json


В пятой версии Node.js был представлен файл package-lock.json.

Что это такое? Для чего он нужен, если есть файл package.json?

Задачей package-lock.json является фиксация конкретных версий установленных пакетов для обеспечения 100% работы продукта при обновлении пакетов их разработчиками.

Это решает проблему package.json. В этом файле вы можете указать, какие пакеты нуждаются в обновлении (патчевом или минорном), используя спецификаторы версий, например:

  • Если указано ~0.13.0, значит, допустимы только патчевые релизы: 0.13.1 подойдет, а 0.14.0 нет.
  • Если указано ^0.13.0, значит, допустимы патчевые и минорные релизы: 0.13.1, 0.14.0 и т.д.
  • Если указано 0.13.0, значит, будет использоваться только указанная версия.

Обычно, вы не отправляете в Git папку node_moludes, и при установке проекта на другом компьютере с помощью npm install, если присутствует спецификатор ~, допускающий патчевые релизы, и пакет обновился, будет установлена новая версия. Тоже самое справедливо для спецификатора ^, допускающего патчевые и минорные релизы.

Это можете быть вы или другой человек на другом конце света, устанавливающий проект посредством npm install.

Таким образом, исходный проект и заново проиницализированный проект это два разных проекта. Даже если патчевый или минорный релизы не содержат критичных изменений, это может привести (и часто приводит) к ошибкам.

package-lock.json хранит записи о конкретных версиях установленных пакетов, и npm установит именно эти версии при выполнении npm install.

Эта концепция не является новой и другие пакетные менеджеры (такие как Composer в PHP) используют ее на протяжении многих лет.

package-lock.json следует отправлять в Git, чтобы другие люди могли его получить, если проект является открытым или у вас есть соавторы, или когда вы используете Git как источник для разработки.

Версии зависимостей в package-lock.json обновляются вручную с помощью npm update.

Пример

Вот пример структуры package-lock.json, который мы получаем при выполнении npm install cowsay в пустой директории:

{  "requires": true,  "lockfileVersion": 1,  "dependencies": {    "ansi-regex": {      "version": "3.0.0",      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",      "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="    },    "cowsay": {      "version": "1.3.1",      "resolved": "https://registry.npmjs.org/cowsay/-/cowsay-1.3.1.tgz",      "integrity": "sha512-3PVFe6FePVtPj1HTeLin9v8WyLl+VmM1l1H/5P+BTTDkMAjufp+0F9eLjzRnOHzVAYeIYFF5po5NjRrgefnRMQ==",      "requires": {        "get-stdin": "^5.0.1",        "optimist": "~0.6.1",        "string-width": "~2.1.1",        "strip-eof": "^1.0.0"      }    },    "get-stdin": {      "version": "5.0.1",      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",      "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g="    },    "is-fullwidth-code-point": {      "version": "2.0.0",      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="    },    "minimist": {      "version": "0.0.10",      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",      "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="    },    "optimist": {      "version": "0.6.1",      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",      "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",      "requires": {        "minimist": "~0.0.1",        "wordwrap": "~0.0.2"      }    },    "string-width": {      "version": "2.1.1",      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",      "requires": {        "is-fullwidth-code-point": "^2.0.0",        "strip-ansi": "^4.0.0"      }    },    "strip-ansi": {      "version": "4.0.0",      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",      "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",      "requires": {        "ansi-regex": "^3.0.0"      }    },    "strip-eof": {      "version": "1.0.0",      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="    },    "wordwrap": {      "version": "0.0.3",      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",      "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="    }  }}

Мы устанавливаем cowsay, который зависит от:

  • get-stdin
  • optimist
  • string-width
  • strip-eof

В свою очередь, эти пакеты нуждаются в других пакетах, указанных в свойстве requires:

  • ansi-regex
  • is-fullwidth-code-point
  • minimist
  • wordwrap
  • strip-eof

Они добавляются в файл в алфавитном порядке, каждый имеет поле version, поле resolved, указывающее на местонахождения пакета, и строку integrity, используемую для идентификации пакета.

Поиск версии установленного пакета


Для того, чтобы увидеть последние версии установленных пакетов, включая их зависимости, необходимо выполнить npm list.

Например:

 npm list/Users/joe/dev/node/cowsay cowsay@1.3.1   get-stdin@5.0.1   optimist@0.6.1    minimist@0.0.10    wordwrap@0.0.3   string-width@2.1.1    is-fullwidth-code-point@2.0.0    strip-ansi@4.0.0      ansi-regex@3.0.0   strip-eof@1.0.0

Вы, конечно, можете просто открыть файл package-lock.json, но такое представление является более наглядным.

npm list -g делает тоже самое, но для глобально установленных пакетов.

Для того, чтобы получить список пакетов верхнего уровня (обычно это те пакеты, которые вы устанавливали вручную), необходимо выполнить npm list --depth=0:

 npm list --depth=0/Users/joe/dev/node/cowsay cowsay@1.3.1

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

 npm list cowsay/Users/joe/dev/node/cowsay cowsay@1.3.1

Это также работает с зависимостями установленного пакета:

 npm list minimist/Users/joe/dev/node/cowsay cowsay@1.3.1   optimist@0.6.1     minimist@0.0.10

Если вы хотите увидеть последнюю доступную версию пакета, имеющуюся в npm, выполните npm view <package-name> version:

 npm view cowsay version1.3.1


Установка старых версий пакета


Вы можете устанавливать старые версии пакетов с помощью спецификатора @:

npm install <package-name>@<version>

Например:

npm install cowsay

установит версию 1.3.1 (последнюю на момент написания этих строк).

Установим версию 1.2.0:

npm install cowsay@1.2.0

Тоже самое можно делать с глобальными пакетами:

npm install -g webpack@4.16.4

Для того, чтобы увидеть список предыдущих версий пакета, необходимо выполнить npm view <package-name> versions:

 npm view cowsay versions[ '1.0.0',  '1.0.1',  '1.0.2',  '1.0.3',  '1.1.0',  '1.1.1',  '1.1.2',  '1.1.3',  '1.1.4',  '1.1.5',  '1.1.6',  '1.1.7',  '1.1.8',  '1.1.9',  '1.2.0',  '1.2.1',  '1.3.0',  '1.3.1' ]


Обновление зависимостей до последних версий


При установке пакета с помощью npm install <package-name>, в папку node_modules загружается последняя доступная версия пакета, запись о пакете добавляется в package.json и package-lock.json, находящиеся в текущей директории.

npm также устанавливает последние доступные версии зависимостей пакета.

Допустим, вы установили cowsay.

При выполнении npm install cowsay запись о пакете была добавлена в package.json:

{  "dependencies": {    "cowsay": "^1.3.1"  }}

А вот что было записано в package-lock.json (мы удалили вложенные зависимости для ясности):

{  "requires": true,  "lockfileVersion": 1,  "dependencies": {    "cowsay": {      "version": "1.3.1",      "resolved": "https://registry.npmjs.org/cowsay/-/cowsay-1.3.1.tgz",      "integrity": "sha512-3PVFe6FePVtPj1HTeLin9v8WyLl+VmM1l1H/5P+BTTDkMAjufp+0F9eLjzRnOHzVAYeIYFF5po5NjRrgefnRMQ==",      "requires": {        "get-stdin": "^5.0.1",        "optimist": "~0.6.1",        "string-width": "~2.1.1",        "strip-eof": "^1.0.0"      }    }  }}

Оба файла говорят нам, что мы установили cowsay версии 1.3.1, правило обновления (^1.3.1) гласит, что нам подойдут патчевые и минорные релизы: 1.3.2, 1.4.0 и т.д.

Если появится новая минорная или патчевая версия, мы выполним npm update, и установленная версия обновится, в package-lock.json появится запись о новой версии.

package.json не изменится.

Для того, чтобы увидеть новые релизы пакетов, необходимо выполнить npm outdated.

Вот список новых релизов пакетов из одного репозитория, который какое-то время не обновлялся:



Некоторые из релизов являются мажорными. Запуск npm update не обновит такие пакеты. Мажорные релизы никогда не обновляются таким способом, поскольку они включают в себя несовместимые изменения и npm заботится о работоспособности вашего приложения.

Для обновления мажорных версий всех пакетов, установите пакет npm-check-updates глобально: npm i npm-check-updates -g и запустите его: ncu -u.

Это обновит все правила обновлений пакетов, указанные в разделах dependencies и devDependencies файла package.json так, что npm сможет обновить их до новых мажорных версий.

После этого выполняем обновление: npm update.

Семантическое версионирование в npm


Концепция семантического версионирования очень проста: все версии имеют три цифры: x.y.z:

  • первая цифра основная (главная, мажорная) версия
  • вторая цифра второстепенная (минорная) версия
  • третья цифра патч (патчевая версия)

Когда вы делаете новый релиз, вы не присваиваете версию как вам хочется, но следуете определенным правилам:

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

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

Почему это так важно?

Потому что в package.json мы определяем правила или условия обновления, т.е. какие версии являются приемлемыми при выполнении npm update.

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

  • ^
  • ~
  • >
  • >=
  • <
  • <=
  • =
  • -
  • ||

Рассмотрим их подробнее:

  • ^: если указано ^0.13.0, значит, допустимы патчевые и минорные релизы: 0.13.1, 0.14.0 и т.д.
  • ~: если указано ~0.13.0, значит, допустимы только патчевые релизы: 0.13.1 подойдет, а 0.14.0 нет.
  • >: допустимы любые новые версии
  • >=: допустимы аналогичные или новые версии
  • <=: допустимы аналогичные или старые версии
  • <: допустимы любые старые версии
  • =: допустима только указанная версия
  • -: допустим диапазон версий. Например: 2.1.0-2.6.2
  • ||: допустима комбинация версий. Например: < 2.1 || > 2.6

Спецификаторы можно комбинировать, например: 1.0.0 || >= 1.1.0 < 1.2.0 допустима указанная версия или диапазон между версиями 1.1.0 и 1.2.0 (не включая последнюю).

Есть парочка дополнительных правил:

  • без символов: допустима только указанная версия
  • latest: последняя доступная версия


Удаление пакетов


Для удаления пакета, установленного локально (с помощью npm i <package-name> в папку node_modules), запустите npm unistall <package-name> из корневой директории проекта (директории, в которой находится node_modules).

Если добавить флаг -S или --save, то из package.json будет удалена запись об удаленном пакете.

Для удаление записи о пакете для разработки, запись о котором содержится в разделе devDependencies файла package.json, следует использовать флаг -D / --save-dev:

npm uninstall -S <package-name>npm uninstall -D <package-name>

Если пакет установлен глобально, необходимо использовать фалг -g / --global: npm uninstall <package-name> -g.

Например: npm uninstall -g webpack.

Глобальные и локальные пакеты


Основное различие между локальными и глобальными пакетами состоит в следующем:

  • локальные пакеты устанавливаются в директорию, в которой вы запускаете npm install <package-name>, и помещаются в папку node_modules, находящуюся в этой директории
  • глобальные пакеты устанавливаются в определенное место вашей файловой системы (которое зависит от настроек), независимо от того, где вы запускаете npm install -g <package-name>

Так какой способ лучше выбрать?

Как правило, все пакеты устанавливаются локально.

Это позволяет гарантировать, что разные приложения на вашем компьютере смогут использовать нужные им версии пакетов.

Обновление глобального пакета приведет к использованию всеми проектами нового релиза, это может привести к кошмару в терминах поддержки, поскольку некоторые пакеты или их зависимости могут оказаться несовместимыми с этим релизом.

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

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

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

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

  • npm
  • create-react-app
  • vue-cli
  • grunt-cli
  • mocha
  • react-native-cli
  • gatsby-cli
  • forever
  • nodemon

Возможно, в вашей системе уже установлены какие-то глобальные пакеты. Вы можете увидеть список таких пакетов, запустив npm list -g --depth 0.

dependencies и devDependencies


При установке пакета посредством npm install <package-name>, вы устанавливаете его как зависимость (dependency).

Запись об установленном пакете добавляется в раздел dependencies файла package.json.

При добавлении флага -D или --save-dev вы устанавливаете зависимость для разработки, запись о ней добавляется в раздел devDependencies.

Зависимости для разработки предназначены только для разработки, они не нужны в продакше. Например, для тестирования продукта, webpack (сборщик проекта) или Babel (транспилятор).

При выполнении npm install и при наличии package.json, npm предполагает, что вы разворачиваете тестовое приложение, поэтому все dependencies и devDependencies устанавливаются.

Во избежание установки зависимостей для разработке необходимо выполнить npm install с флагом --production: npm install --production.

Запуск пакета с помощью npx


npx это очень мощная команда, доступная начиная с версии 5.2, представленной в июле 2017.

Если вы не хотите устанавливать npm, то можете установить npx как самостеятельный пакет.

npx позволяет запускать сборку проекту и публиковать пакеты в реестре npm.

Запуск локальных команд

Node.js-разработчики раньше устанавливали исполняемые команды глобально с целью немедленного выполнения.

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

Запуск npx commandname автоматически находит правильную ссылку на команду в папке node_modules проекта без необходимости указывать точный путь к файлу и устанавливать пакет глобально.

Выполнение команды без установки

Еще одной замечательной особенностью npx является то, что она позволяет выполнять команды без установки пакетов.

Это очень полезно, поскольку:

  1. ничего не надо устанавливать
  2. можно запускать разные версии команды с помощью @version

Типичной демонстрацией использования npx является выполнение команды cowsay. В терминал выводится корова, говорящая переданную строку. Например:

cowsay "Hello" выведет следующее:

 _______< Hello > -------        \   ^__^         \  (oo)\_______            (__)\       )\/\                ||----w |                ||     ||

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

npx позволяет выполнить эту команду без установки пакета:

npx cowsay "Hello"

Это забавная, но бесполезная команда. Другие сценарии:

  • выполнение CLI vue для создания нового приложения и его запуска: npx vue create my-vue-app
  • создание нового React-проекта: npx create-react-app my-react-app

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

Запуск кода с использованием разных версий Node.js

Используйте @ для определения версии и комбинируйте его с пакетным менеджером:

npx node@10 -v #v10.18.1npx node@12 -v #v12.14.1

Это позволяет обойтись без nvm и других инструментов управления версиями.

Запуск проивзольного сниппета из URL

npx не ограничивает вас пакетами, опубликованными в реестре npm.

Вы можете запускать код, содержащийся в GitHub gist, например:

npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32

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

Продолжение следует
Подробнее..

Перевод Руководство по Deno примеры работы со средой выполнения TypeScript

26.07.2020 12:18:22 | Автор: admin


Доброго времени суток, друзья!

Представляю Вашему вниманию перевод статьи The Deno Handbook: A TypeScript Runtime Tutorial with Code Examples автора Flavio Copes.

В этой статье мы научимся работать с Deno. Мы сравним его с Node.js и создадим с его помощью простой REST API.

Что такое Deno?


Если вы знакомы с Node.js, популярной экосистемой серверного JavaScript, Deno это почти тоже самое. Почти, но не совсем.

Давайте начнем со списка возможностей Deno, которые мне больше всего нравятся:

  • Он основан на современном JavaScript
  • Он имеет расширяемую стандартную библиотеку
  • Он имеет первоклассную (в значении стандартной) поддержку TypeScript (это означает, что вам не нужно вручную компилировать TypeScript, Deno делает это автоматически)
  • Он поддерживает ES модули
  • Он не имеет пакетного менеджера
  • Он имеет первоклассный (в значении глобальный) await
  • Он имеет встроенное средство тестирования
  • Его цель максимальная совместимость с браузером. Для этого он предоставляет встроенный fetch и глобальный объект window

В данном руководстве мы изучим все эти возможности.

После знакомства с Deno и его возможностями, Node.js покажется вам немного устаревшим.

Особенно по причине того, что Node.js основан на функциях обратного вызова (он был написан до появления промисов и async/await). Едва ли они когда-нибудь там появятся, поскольку это означает необходимость внесения фундаментальных изменений.

Node.js прекрасен и останется фактическим стандартом в мире JavaScript. Однако, я полагаю, что популярность Deno будет быстро расти благодаря его поддержке TypeScript и современной стандартной библиотеке.

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

Почему Deno? Почему сейчас?


Deno был анонсирован почти 2 года назад создателем Node.js Ryan Dahl на JSConf EU. Смотрите видео на YouTube, оно очень интересное, и является обязательным к просмотру, если вы работаете с Node.js и JavaScript.

Каждый менеджер (создатель) проекта вынужден принимать решения. Райан жалеет о некоторых ранних решениях в Node. Кроме того, технологии развиваются, и сегодня JavaScript это совершенно другой язык, чем он был в 2009, когда появился Node. Вспомните о возможностях ES6/2016/2017 и т.д.

Поэтому он решил начать новый проект, своего рода вторую волну приложений на серверном JavaScript.

Причина, по которой я пишу эту статью только сейчас, заключается в том, что требуется довольно много времени для созревания технологии. Наконец, мы получили Deno 1.0 (он был представлен 13 мая 2020 года), первый стабильный релиз.

Это может показаться обычным числом, однако 1.0 означает, что радикальных изменений не будет вплоть до Deno 2.0. При изучении новой технологии вы не хотите, чтобы она слишком быстро менялась.

Следует ли изучать Deno?


Хороший вопрос.

Изучение чего-то нового, такого как Deno, требует больших усилий. Мой совет: если вы только начинаете изучать серверный JS и пока не знаете Node.js, и никогда раньше не писали код на TypeScript, начните с Node.

За выбор Node еще никого не увольняли (перефразирование известной цитаты).

Но если вам нравится TypeScript, не зависящий от тонны npm-пакетов, и вы хотите везде использовать await, Deno может быть тем, что вы ищите.

Заменит ли он Node.js?


Нет. Node.js это гигант, большой авторитет, невероятно хорошо поддерживаемая технология, которая в ближайшее десятилетие никуда не денется.

Первоклассная поддержка TypeScript


Deno написан на Rust и TypeScript, очень популярных на сегодня языках.

Это означает, что мы получаем много выгод от TypeScript, даже если пишем код на JavaScript.

Запуск TypeScript-кода с помощью Deno не требует предварительной компиляции Deno делает это автоматически.

Вы не обязаны писать код на TypeScript, однако тот факт, что ядро Deno написано на TypeScript, имеет огромное значение.

Во-первых, большой процент JavaScript-разработчиков любит TypeScript.

Во-вторых, используемые вами инструменты могут получать много информации о ПО, написанном на TypeScript, таком как Deno.

Это означает, что когда мы пишем код в VS Code, например (который имеет тесную интеграцию с TypeScript с момента появления), мы получаем такие преимущества, как проверка типов при написании кода или продвинутые возможности IntelliSense. Другими словами, помощь редактора кода становится гораздо эффективнее.

Отличия от Node.js


Поскольку Deno это, по сути, замена Node.js, имеет смысл их сравнить.

Общее:

  • Оба основаны на движке V8
  • Оба отлично подходят для разработки серверного JavaScript

Отличия:

  • Node написан на C++ и JavaScript. Deno написан на Rust и TypeScript.
  • Node имеет официальный пакетный менеджер npm. У Deno такого менеджера нет, вместо этого он позволяет импортировать любой модуль с помощью URL.
  • Node использует синтаксис CommonJS для импорта пакетов. Deno использует официальный способ ES модули.
  • Deno использует современные возможности ECMAScript во всех прикладных интерфейсах и стандартной библиотеке, в то время как Node.js использует основанную на колбеках стандартную библиотеку и не планирует ее обновлять.
  • Deno предлагает уровень (слой) безопасности песочницы через предоставление разрешений. Программа получает разрешение на выполнение определенных действий через пользовательские флаги. Node.js имеет доступ ко всему, к чему имеет доступ пользователь.
  • Deno долгое время искал возможность компиляции программ в выполняемые, т.е. такие, которые можно запускать без внешних зависимостей, как в Go, однако достичь этого пока не удалось. Это изменит правила игры.

Отсутствие пакетного менеджера


Отсутствие пакетного менеджера и использование URL для получения и импорта пакетов имеет свои преимущества и недостатки. Одним из основных преимуществ является большая гибкость, проявляющаяся в возможности создания пакетов без необходимости их опубликования в таком репозитории, как npm.

Думаю, что некая альтернатива пакетному менеджеру в Deno рано или поздно появится.

Официальный сайт Deno имеет хостинг для сторонних пакетов: https://deno.land/x/

Установка Deno


Хватит болтать! Давайте установим Deno.

Простейшим способом это сделать является использование Homebrew:

    brew install deno 



Другие способы установки указаны здесь.

После установки становится доступна команда deno. Вот помощь, которую можно получить, набрав deno --help:

flavio@mbp~> deno --helpdeno 0.42.0A secure JavaScript and TypeScript runtimeDocs: https://deno.land/std/manual.mdModules: https://deno.land/std/ https://deno.land/x/Bugs: https://github.com/denoland/deno/issuesTo start the REPL, supply no arguments:  denoTo execute a script:  deno run https://deno.land/std/examples/welcome.ts  deno https://deno.land/std/examples/welcome.tsTo evaluate code in the shell:  deno eval "console.log(30933 + 404)"Run 'deno help run' for 'run'-specific flags.USAGE:    deno [OPTIONS] [SUBCOMMAND]OPTIONS:    -h, --help            Prints help information    -L, --log-level <log-level>            Set log level [possible values: debug, info]    -q, --quiet            Suppress diagnostic output            By default, subcommands print human-readable diagnostic messages to stderr.            If the flag is set, restrict these messages to errors.    -V, --version            Prints version informationSUBCOMMANDS:    bundle         Bundle module and dependencies into single file    cache          Cache the dependencies    completions    Generate shell completions    doc            Show documentation for a module    eval           Eval script    fmt            Format source files    help           Prints this message or the help of the given subcommand(s)    info           Show info about cache or info related to source file    install        Install script as an executable    repl           Read Eval Print Loop    run            Run a program given a filename or url to the module    test           Run tests    types          Print runtime TypeScript declarations    upgrade        Upgrade deno executable to newest versionENVIRONMENT VARIABLES:    DENO_DIR             Set deno's base directory (defaults to $HOME/.deno)    DENO_INSTALL_ROOT    Set deno install's output directory                         (defaults to $HOME/.deno/bin)    NO_COLOR             Set to disable color    HTTP_PROXY           Proxy address for HTTP requests                         (module downloads, fetch)    HTTPS_PROXY          Same but for HTTPS

Команды Deno


Заметили раздел SUBCOMMANDS? Это список всех команд, которые мы можем запускать. Какие команды у нас есть?

  • bundle собирает модуль и зависимости проекта в один файл
  • cache кэширует зависимости
  • completions генерирует пополнения оболочки
  • doc показывает документацию по модулю
  • eval используется для вычисления блока кода, например, deno eval "console.log(1 + 2)"
  • fmt встроенное средство форматирования кода (такое как goFmt в Go)
  • help выводит список вспомогательных команд
  • info показывает информацию о кэше или файле
  • install устанавливает скрипт как выполняемый
  • repl цикл чтение-вычисление-вывод (по умолчанию)
  • run запускает программу с заданным именем или URL для модуля
  • test запускает тесты
  • types выводит список возможностей TypeScript
  • upgrade обновляет Deno до последней версии

Вы можете запустить deno <subcommand> help для получения информации об определенной команде, например, deno run --help.

Мы можем использовать команду deno для запуска цикла чтение-вычисление-вывод:



Это аналогично запуску deno repl.

Обычно, deno используется для запуска Deno-приложения, содержащегося в TypeScript-файле.

Вы можете запускать как TypeScript-файлы (.ts), так и JavaScript-файлы (.js).

Если вы не знакомы с TypeScript, не переживайте: Deno написан на TypeScript, но вы вполне можете писать свои клиентские приложения на JavaScript.

Первое приложение на Deno


Давайте создадим наше первое приложение.

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

Deno скачивает программу, компилирует ее и запускает:



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

Эта программа является очень простой и представляет из себя вызов console.log():

console.log('Welcome to Deno 
Подробнее..

Краткое руководство по Node.js для начинающих (SPA, PWA, mobile-first)

15.08.2020 14:04:40 | Автор: admin


Доброго времени суток, друзья!

Представляю вашему вниманию перевод Руководства по Node.js в формате одностраничного прогрессивного адаптированного приложения.

Данный формат означает следующее:

  • SPA новые данные (разделы или главы руководства) загружаются без перезагрузки страницы реализовано с помощью динамического импорта
  • PWA приложение можно установить на мобильный телефон или компьютер; приложение работает даже при отсутствии подключения к сети реализовано с помощью сервис-воркера и кэширования
  • mobile-first приложение предназначено для использования, в первую очередь, на смартфонах, но хорошо выглядит и на широких экранах

Посмотреть и установить приложение можно здесь: Netlify, PWA Store.

Код проекта на GitHub.

Песочница:


На десктопе приложение выглядит так:





А на смартфоне так:







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

Оригинальное руководство написано в 2019 году с учетом последней на тот момент версии Node.js и ES2018, что обуславливает его актуальность.

Руководство состоит из 54 разделов (глав), в которых в доступной форме излагаются основы и некоторые особенности Node.js. Руководство не сделает из вас специалиста по серверному JavaScript, но поможет начать его изучение и определить дальнейший путь развития.

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

Страницы переключаются с помощью кнопок и клавиатуры.

Для стилизации приложения использовался Materialize.

Несколько слов о реализации

Реализация приложения до неприличия проста.

Каждый раздел (глава) представляет собой модуль следующего содержания:

export default `разметка и текст`

В разметке главной страницы у нас имеются ссылки:

<a class="link" data-url="1" href="#">1. Введение</a>

и кнопки:

<!-- классы фреймворка удалены --><button>    <i class="left">navigate_before</i></button><button>    <i class="right">navigate_after</i></button>

При нажатии на ссылку или кнопку вызывается функция отображения страницы, которой передается номер страницы. При инициализации приложения номер страницы запрашивается из локального хранилища. При отсутствии такового, номер страницы = 1. При нажатии на ссылку номером страницы становится значение атрибута data-url ссылки. При нажатии на кнопку номер страницы увеличивается или, соответственно, уменьшается на 1. Сама функция выглядит так:

const showPage = i => {    // определяем адрес запрашиваемой страницы    const url = `./chapters/${i}.js`    // импортируем соответствующий модуль    import(url)        // вставляем разметку в основной контейнер        .then(data => container.innerHTML = data.default)    // записываем номер страницы в локальное хранилище    localStorage.setItem('NodejsGuidePageNumber', i)    // прокручиваем страницу    scrollTo(0, 0)}

Вот и все, как видите, ничего особенного. Первые две строки кода это навигационная панель Materialize. О сервис-воркерах можно почитать здесь.

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

Надеюсь, приложение вам понравится. Благодарю за внимание.
Подробнее..

Перевод Руководство по Express.js. Часть 1

27.08.2020 12:18:32 | Автор: admin


Доброго времени суток, друзья!

Представляю вашему вниманию перевод первой части Руководства по Express веб-феймворку для Node.js автора Flavio Copes.

Предполагается, что вы знакомы с Node.js. Если нет, то прошу сюда.

Без дальнейших предисловий.

1. Введение


Express это веб-фреймворк для Node.js.

Node.js замечательный инструмент для создания сетевых сервисов и приложений.

Express использует возможности Node.js, значительно облегчая процесс создания веб-сервера.

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

2. Установка


Express можно установить с помощью npm:

npm i express

Или yarn:

yarn add express

Для инициализации нового проекта выполните команду npm init или yarn init. Для автоматического заполнения полей следует добавить флаг -y.

3. Hello World


Мы готовы к созданию нашего первого сервера.

Вот его код:

const express = require('express')const app = express()app.get('/', (req, res) => res.send('Hello World!'))app.listen(3000, () => console.log('Сервер запущен'))

Сохраните этот код в файле index.js и запустите сервер:

node index.js

Откройте браузер на localhost:3000 и увидете сообщения Hello World! на экране и Сервер запущен в консоли.

4. Основы Express


Эти 4 строки кода делают множество вещей за кулисами.

Сначала мы импортируем библиотеку express.

Затем инициализируем приложение, вызывая метод app().

После получения объекта приложения мы указываем ему обрабатывать GET-запросы к пути "/" с помощью метод get().

Для каждого HTTP-метода или, как еще говорят, глагола (хотя среди методов встречаются и существительные) имеется соответствующий метод Express:

app.get('/', (req, res) => {})app.post('/', (req, res) => {})app.put('/', (req, res) => {})app.delete('/', (req, res) => {})app.patch('/', (req, res) => {})

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

Мы передаем его так:

(req, res) => res.send('Hello World!')

Аргументы req и res соответствуют объектам Request (запрос) и Response (ответ).

Request содержит информацию о запросе, включая параметры, заголовки, тело запроса и т.д.

Response это объект, отправляемый клиенту в ответ на запрос.

В нашем коде мы отправляем клиенту строку Hello World! с помощью метода Response.send().

Данный метод помещает строку в тело ответа и закрывает соединение.

Последняя строка кода запускает сервер и указывает ему слушать порт 3000. Передаваемый колбек вызывается при готовности сервера к получению запросов.

6. Параметры запроса


Объект Request содержит информацию о запросе.

Ниже приведены основные свойства этого объекта.
Свойство Описание
.app содержит ссылку на объект приложения
.baseUrl содержит ссылку на экземпляр маршрутизатора (express.Router())
.body содержит данные, помещенные в тело запроса (должны быть разобраны (parsed) и заполнены (populated) перед использованием)
.cookies содержит куки, установленные в запросе (требуется промежуточное программное обеспечение (далее ППО) cookie-parser)
.hostname название хоста сервера
.ip IP-адрес сервера
.method метод запроса
.params объект с именованными параметрами запроса (например, при запросе к /users/:id, id будет записано в req.params.id)
.path URL запроса
.protocol протокол запроса
.query объект с параметрами строки запроса (например, при запросе к /search?name=john, john будет записано в req.query.name)
.secure содержит true, если запрос является безопасным (если используется HTTPS)
.signedCookies содержит подписанные куки (требуется ППО cookie-parser)
.xhr содержит true, если запрос это XMLHttpRequest

7. Получение параметров из строки запроса


Строка запроса это часть URL после вопросительного знака (?).

Например:

?name=john

Несколько параметров могут передаваться с помощью амперсанда (&):

?name=john&age=30

Как извлечь эти значения?

Это делается посредством распаковывания объекта Request.query:

const express = require('express')const app = express()app.get('/', (req, res) => {    console.log(req.query)})app.listen(8080)

Данный объект содержит свойство для каждого параметра строки запроса.

Если параметры отсутствуют, объект является пустым.

Перебрать объект можно с помощью цикла for/in:

for (const key in req.query) {    console.log(key, req.query[key])}

Этот код выведет в консоль ключи и значения параметров строки запроса.

Также можно получить значение конкретного параметра:

req.query.name // johnreq.query.age // 30

8. Получение параметров строки POST-запроса


Параметры строки POST-запроса предоставляются клиентом при отправке формы или других данных.

Как нам получить эти параметры?

Если данные были отправлены в формате JSON с помощью Content-Type: application/json, такие данные необходимо разобрать с помощью ППО express.json(). ППО подключается с помощью метода app.use():

const express = require('express')const app = express()app.use(express.json())

Если данные были отправлены в формате JSON с помощью Content-Type: application/x-www-urlencoded, такие данные необходимо разобрать с помощью ППО express.urlencoded():

const express = require('express')const app = express()app.use(express.urlencoded())

В обоих случаях данные можно получить через Request.body:

app.post('/form', (req, res) => {    const name = req.body.name})

Обратите внимание, что в старых версиях Express для обработки данных в качестве ППО использовался модуль body-parcer. В настоящее время данный модуль встроен в Express.

9. Отправка ответа


Как отправить ответ клиенту?


В приведенном примере мы использовали метод Response.send() для отправки клиенту ответа в виде строки и закрытия соединения:

(req, res) => res.send('Hello World!')

При передачи строки, заголовок Content-Type устанавливается в значение text/html.

При передачи объекта или массива, заголовок Content-Type устанавливается в значение application/json, а данные преобразуются в формат JSON.

send() также автоматически устанавливает заголовок Content-Length и закрывает соединение с сервером.

Использование end() для отправки пустого ответа


Альтернативным способом отправки клиенту ответа, не содержащего тела, является использование метода Response.end():

res.end()

Установка статуса ответа


Для этого используется метод Response.status():

res.status(404).end()

Или:

res.status(404).send('Файл не найден')

sendStatus() является сокращением для res.status().send():

res.sendStatus(200) // === res.status(200).send('Ok')res.sendStatus(403) // === res.status(403).send('Forbidden')res.sendStatus(404) // === res.status(404).send('Not Found')res.sendStatus(500) // === res.status(500).send('Internal Server Error')

10. Отправка ответа в формате JSON


При обработке запросов маршрутизатором колбек вызывается с двумя параметрами экземпляром объекта Request и экземпляром объекта Response.

Например:

app.get('/', (req, res) => res.send('Hello World!'))

Здесь мы используем метод Response.send(), принимающий строку.

Ответ клиенту в формате JSON можно отправить с помощью метода Response.json().

Данный метод принимате объект или массив и конвертирует его в JSON:

res.json({ name: 'John' }) // { "name": "John" }

На сегодня это все. В следующей части мы поговорим об управлении куками, работе с HTTP-заголовками, перенаправлениях, маршрутизации и политике одного источника.

Следите за обновлениями. Благодарю за внимание и хорошего дня.
Подробнее..

Безопасность npm-проектов, часть 1

01.09.2020 12:04:25 | Автор: admin

Безопасность npm-проектов, часть 1


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


Скрипты установки


Команда npm install поддерживает ряд скриптов (например: preinstall, install/postinstall), которые могут выполняться в процессе установки пакета. При этом содержимое таких скриптов ничем не ограничивается и выполняется от лица пользователя, выполняющего установку. Такие скрипты являются одним из основных способов для злоумышленников влезть в компьютер, на котором устанавливается вредоносный пакет, и похитить важную информацию.


Пример пакета, содержащего вредоносный скрипт:


Пример пакета содержащего вредоносный скрипт


Если пользователь выполнит команду установки пакета из примера выше: npm install malicious-package, то это приведет к выполнению скрипта evil.sh, который потенциально может совершить любые действия от лица текущего unix-пользователя.


Возможно, вы слышали, что в середине 2018 года произошел громкий инцидент с пакетами eslint-scope и eslint-config-eslint, когда злоумышленник смог получить к ним доступ и опубликовать новую версию пакетов, содержащую вредоносный код в скриптах установки.

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

Чтобы изолировать проблему, команде npm пришлось отозвать все токены npm, которые были сгенерированы до даты начала атаки. Это привело к тому, что npm registry начал испытывать проблемы с доступностью, потому что пользователи начали массово перевыпускать токены, не говоря о том, что это нарушило работу огромного количества автоматизированных конвейеров, которые обращались к npm с использованием токенов.

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

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


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


npm install suspicious-package --ignore-scripts

Также, вы можете полностью запретить выполнение скриптов установки при помощи следующей команды (или добавить настройку в .npmrc):


npm config set ignore-scripts true

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


Ограничение среды выполнения


Ограничение среды выполнения


Чтобы дополнительно обезопасить себя от подобных атак, постарайтесь максимально ограничить среду, в которой выполняются команды npm, такие как npm install. Как вариант, их можно выполнять внутри Docker-контейнера, на виртуальной машине или в любой другой песочнице с ограниченным доступом. Разумеется, никогда не стоит запускать npm от лица root-пользователя; наоборот, лучше запускать эти команды от лица пользователя с минимальным доступом и набором прав. Антивирусы и брандмауэры также могут сократить риски. Принцип прост: чем меньше полномочий получит процесс npm, тем безопаснее будет его работа.


Также полезно будет настроить систему предупреждений: если вдруг процесс начнет совершать какие-то подозрительные действия, то вам следует об этом знать. Выявление угрозы безопасности поможет не только вам и вашей команде или компании, но и всему сообществу в целом. Поэтому не забудьте составить и отправить отчет о найденной уязвимости в npm.


Безопасность токенов и ключей


Безопасность токенов и ключей


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


Если вам всё же необходимо использовать токен аутентификации npm в каком-то автоматическом конвейере (например, для CI/CD), то ограничьте его полномочия. Например, если токен нужен только для установки пакетов из приватного репозитория, то создавайте его в режиме read-only, чтобы в случае его утечки злоумышленник не мог отправить вредоносный код в ваш репозиторий. Это позволит ограничить масштаб угрозы.


Также хорошей практикой является привязка токена аутентификации npm к определенному диапазону IP-адресов (CIDR). Закажите себе статический IP (или серию IP) и привяжите его к своему серверу. Как правило, любой облачный провайдер позволяет это сделать, даже если ваш сервер запускается по запросу (on-demand). Таким образом, если злоумышленник сможет украсть токен, то он не сможет использовать его с другой машины.


Не размещайте токен аутентификации в файле .npmrc, и тем более не добавляйте его в систему контроля версий (Git). Вместо этого используйте переменную окружения NPM_TOKEN в связке со специальным защищенным хранилищем для токенов (изучите документацию вашего провайдера).


Сгенерировать новый токен можно при помощи команды npm token create, которая опционально принимает следующие аргументы:


  • --read-only создаёт токен, который позволяет только считывать данные из репозитория (т. е. устанавливать пакеты, но не публиковать их);
  • --cidr=<CIDR> создаёт токен, который позволяет аутентифицироваться только хостам в указанном диапазоне IP. Рекомендую использовать калькулятор CIDR, чтобы быть уверенными в корректности задания диапазона IP. Пример: --cidr=192.0.2.0/24.

Также рекомендуется регулярно проводить аудит всех токенов, привязанных к npm-аккаунту, и отзывать ненужные. Посмотреть список всех токенов можно в личном кабинете на сайте npm, либо при помощи команды npm token list.


Учтите, что аутентификация при помощи команды npm adduser приравнивается к созданию токена и сохранению его в файле .npmrc в домашней директории пользователя. Удаление же токена приведет к разлогиниванию пользователя.


Аутентификация в файле ~/.npmrc выглядит следующим образом:


//registry.npmjs.org/:_authToken=00000000-0000-0000-0000-000000000000

Если вы используете различные независимые npm registry, то таких строк может быть несколько.


Убедитесь, что содержимое файла ~/.npmrc доступно только вашему unix-пользователю!

Что касается закрытых ключей шифрования (например, для SSH), то убедитесь, что они защищены сильным паролем и используют надежный алгоритм шифрования. В случае компрометации такого ключа злоумышленник не сможет им воспользоваться, т. к. не знает пароль, а методы математического взлома окажутся неэффективными. В случае с SSH, в настоящее время алгоритм EdDSA считается наиболее надежным. Однако стоит учесть, что старые системы могут его не поддерживать. Тогда используйте два ключа: Ed25519 (EdDSA) для всех совместимых систем и RSA (с минимум 2048 битным ключом) для устаревших систем.


Очепятки


Очепятки


Одним из старейших приемов в рукаве злоумышленников является использование названий, похожих на оригинальные, но отличающихся одним или несколькими символами. Пользователи часто совершают опечатки при вводе тех или иных названий. Особенно это критично при вызове команды, например, npm install packae-name: в лучшем случае это приведет к ошибке 404, а в худшем может вызвать вредоносный код.


Команда npm старается автоматически вычислять попытки подобного подлога и блокировать пакеты, которые выдают себя за другие. Вам же, как пользователю, следует просто внимательнее относиться к вводу названий пакетов с клавиатуры, по возможности пользуйтесь функцией copy-and-paste.


Безопасные пароли


Безопасные пароли


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


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

Для решения всех вышеописанных вопросов стоит использовать специальное ПО: менеджер паролей. Я использую открытый KeePass (есть версии практически для любой платформы). Базу данных паролей стоит защитить сложным мастер-паролем, который вам необходимо запомнить. Дополнительно можно использовать файл-ключ, который хранится, например, на флешке. Сам же файл базы данных можно положить в любое облачное хранилище (Яндекс.Диск, Google Drive или DropBox), чтобы иметь к нему синхронизированный доступ сразу со всех устройств.


Мой коллега недавно перевел статью, в которой подробнее разбирается вопрос оптимальной длины пароля, рекомендую ознакомиться!

Мультифакторная аутентификация (MFA)


Мультифакторная аутентификация (MFA)


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


К счастью, npm также поддерживает двухфакторную аутентификацию при помощи программ-аутентификаторов. Она работает в двух режимах: только аутентификация или аутентификация и запись. Лучше всего использовать режим аутентификация и запись, т. к. это гарантирует самый высокий уровень безопасности, потому что npm будет просить вас ввести токен OTP не только при вызове команды npm adduser, но и при любой попытке записать что-то в реестр (например, при вызове npm publish).


О том, как включить npm 2FA, написано подробно в официальной документации. Процесс в целом ничем не отличается от стандартного: вам нужно выбрать режим работы, а затем просканировать QR-код, который будет показан на экране при помощи выбранной вами программы-аутентификатора. Сделать это можно как через личный кабинет на официальном сайте, так и через CLI при помощи команд:


  • npm profile enable-2fa auth-and-writes
    режим аутентификация и запись (рекомендуется)
  • npm profile enable-2fa auth-only
    режим только аутентификация

При использовании CLI npm попросит вас ввести пароль от аккаунта (даже если вы уже аутентифицированы), а затем покажет QR-код. После сканирования кода нужно ввести одноразовый пароль (OTP), который покажет приложение на устройстве, чтобы подтвердить успешность процедуры.


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


Если утеря всё же произошла, вы можете ввести неиспользованный ранее код восстановления вместо одноразового пароля при аутентификации, а затем сбросить 2FA командой npm profile disable-2fa, введя затем еще один код восстановления. Если же вы потеряли и аутентификатор, и коды восстановления, то вам придется обратиться в службу поддержки npm (надеюсь, они защищены от социальной инженерии).


Ограничение публикуемых файлов


Ограничение публикуемых файлов


По умолчанию, когда вы выполняете команду npm publish, npm собирает в архив все файлы в директории пакета за исключением некоторых.


Полный список файлов, которые npm никогда не добавляет в архив пакета
  • .git
  • CVS
  • .svn
  • .hg
  • .lock-wscript
  • .wafpickle-N
  • .*.swp
  • .DS_Store
  • ._*
  • npm-debug.log
  • .npmrc
  • node_modules
  • config.gypi
  • *.orig
  • package-lock.json

Часто из-за этого в registry утекают файлы, которые не должны быть опубликованы, например, файлы с паролями. Это серьезная угроза безопасности. Если вы случайно отправили в реестр npm какие-то чувствительные файлы, то вам придется не только удалить архив пакета из реестра, но и полностью заменить все скомпрометированные данные.


А чтобы обезопасить себя от подобных утечек, я рекомендую всегда использовать поле files в файле package.json. Оно представляет собой массив, содержащий перечисление файлов (можно использовать шаблоны glob), которые должны попасть в архив собранного пакета. Такой явный подход гарантирует, что в публичный доступ случайно не утекут какие-то нежелательные файлы.


Нужно заметить, что следующие файлы всегда попадают в архив, даже если не перечислены в массиве files:


  • package.json
  • README
  • CHANGES, CHANGELOG, HISTORY
  • LICENSE, LICENCE
  • NOTICE
  • Файл, указанный в поле main

При этом файлы README, CHANGES, LICENSE и NOTICE могут иметь любой регистр символов в названии и расширение.


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


Чтобы проверить, что же попадет в архив при запуске команды npm publish, можно использовать флаг --dry-run либо команду npm pack --dry-run. Вторая команда, в отличие от первой, пропустит скрипты препаблишинга и сразу попытается собрать архив. Аргумент --dry-run гарантирует, что изменения будут произведены в тестовом режиме только на вашей машине, пакет не будет никуда отправляться, и архив не будет реально создан в файловой системе.


Продолжение следует


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


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




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


Если же у вас есть вопросы или желание что-то добавить по теме, то не стесняйтесь оставлять комментарии, я с радостью приму участие в обсуждении и учту ваши пожелания в следующих постах.


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

Подробнее..

Перевод Компоновщик в JavaScript

10.09.2020 18:21:56 | Автор: admin
Перевод статьи подготовлен в преддверии старта курса JavaScript Developer. Basic.



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

Главная задача компоновщика объединение множества объектов в единую древовидную структуру. Эта древовидная структура представляет иерархию, построенную по принципу от частного к целому.

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

В иерархии от частного к целому каждый объект коллекции является частью общей композиции. Эта общая композиция, в свою очередь, является коллекцией ее частей. Иерархия от частного к целому строится как древовидная структура, где каждый отдельный лист или узел воспринимается и обрабатывается точно так же, как любой другой лист или узел в любой части дерева. Таким образом, группа или коллекция объектов (поддерево листов/узлов) также является листом или узлом.

Визуально это можно представить приблизительно так:



Теперь, когда у нас есть более четкое понимание отношений между частным и целым, вернемся к термину компоновщик. Мы определили, что использование компоновщика призвано объединить упомянутые объекты (листы/узлы) в дерево согласно этому принципу.
Таким образом, мы получаем шаблон проектирования, в котором каждый элемент коллекции может также включать в себя другие коллекции, что позволяет выстраивать глубоко вложенные структуры.

Внутреннее строение


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

Где применяется этот шаблон?


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

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

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

Чем интересен этот шаблон?


Если кратко: Он очень мощный.
Компоновщик является настолько мощным шаблоном проектирования потому, что дает возможность обращаться к объекту как к композитному за счет использования всеми объектами общего интерфейса.

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

Примеры


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

Нам предстоит работать с классом Document, который будет иметь свойство signature со значением по умолчанию false. Если врач подпишет документ, то значение signature будет изменено на его подпись. Мы также определяем в этом классе метод sign, с помощью которого реализуется эта функция.

Вот так будет выглядеть Document:

class Document {  constructor(title) {    this.title = title    this.signature = null  }  sign(signature) {    this.signature = signature  }}


Теперь, применив компоновщик, мы обеспечим поддержку методов, схожих с теми, что определены в Document.

class DocumentComposite {  constructor(title) {    this.items = []    if (title) {      this.items.push(new Document(title))    }  }  add(item) {    this.items.push(item)  }  sign(signature) {    this.items.forEach((doc) => {      doc.sign(signature)    })  }}


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



Отлично! Похоже, мы на верном пути. То, что у нас получилось, соответствует схеме, представленной выше.



Итак, наша древовидная структура содержит два листа/узла Document и DocumentComposite. Оба они совместно используют один и тот же интерфейс и, следовательно, действуют как части единого композитного дерева.

Здесь следует отметить, что лист/узел дерева, не являющийся композитом (Document), не является коллекцией или группой объектов и, следовательно, не продолжит ветвления. Тем не менее лист/узел, являющийся композитом, содержит коллекцию частей (в нашем случае это items). Также помните, что Document и DocumentComposite используют общий интерфейс, а следовательно, разделяют и метод sign.
Так в чем же заключается эффективность такого подхода? Несмотря на то что DocumentComposite использует единый интерфейс, поскольку задействует метод sign, как и Document, в нем реализован более эффективный подход, позволяющий при этом достичь конечной цели.

Поэтому вместо такой структуры:

const pr2Form = new Document(  'Primary Treating Physicians Progress Report (PR2)',)const w2Form = new Document('Бланк Налогового управления (W2)')const forms = []forms.push(pr2Form)forms.push(w2Form)forms.forEach((form) => {  form.sign('Bobby Lopez')})


Мы можем видоизменить код и сделать его эффективнее, воспользовавшись преимуществами компоновщика:

const forms = new DocumentComposite()const pr2Form = new Document(  'Текущие сведения о производственных врачах (PR2)',)const w2Form = new Document('Бланк Налогового управления (W2)')forms.add(pr2Form)forms.add(w2Form)forms.sign('Роман Липин')console.log(forms)


При таком подходе нам потребуется лишь единожды выполнить sign после добавления всех нужных документов, и эта функция подпишет все документы
Убедиться в этом можно, просмотрев вывод функции console.log(forms):



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

Также не стоит забывать, что наш DocumentComposite может включать коллекцию документов.

Поэтому, когда мы проделали вот это:

forms.add(pr2Form) // Документforms.add(w2Form) // Документ


Наша схема обрела следующий вид:



Мы добавили две формы, и теперь эта схема почти полностью соответствует исходной:



Тем не менее наше дерево прекращает свой рост, поскольку последний его лист образовал только два листа, что не вполне соответствует схеме на последнем скриншоте. Если бы вместо этого мы сделали форму w2form композитом, как показано здесь:

const forms = new DocumentComposite()const pr2Form = new Document(  'Текущие сведения о производственных врачах (PR2)',)const w2Form = new DocumentComposite('Бланк Налогового управления (W2)')forms.add(pr2Form)forms.add(w2Form)forms.sign('Роман Липин')console.log(forms)


Тогда наше дерево могло бы продолжать расти:



В конечном итоге мы бы достигли той же цели все документы были бы подписаны:



В этом и заключается польза компоновщика.

Заключение


На этом у меня пока все! Надеюсь, эта информация оказалась для вас полезной. Дальше больше!
Найти меня на medium



Читать ещё:


Подробнее..

Перевод Руководство по Express.js. Часть 3

12.09.2020 16:16:00 | Автор: admin


Доброго времени суток, друзья!

Представляю вашему вниманию перевод второй части Руководства по Express веб-феймворку для Node.js автора Flavio Copes.

Предполагается, что вы знакомы с Node.js. Если нет, то прошу сюда.

Без дальнейших предисловий.

15. Шаблонизация


Express умеет работать с серверными шаблонизаторами (server-side template engines).

Шаблонизатор позволяет динамически генерировать HTML-разметку посредством передачи данных в представление (view).

По умолчанию Express использует шаблонизатор Pug, который раньше назывался Jade.

Несмотря на то, что с момента выхода последней версии Jade прошло почти 3 года, он все еще поддерживается в целях обеспечения обратной совместимости.

В новых проектах следует использовать Pug или другой шаблонизатор. Вот официальный сайт Pug: pugjs.org.

Среди других шаблонизаторов, можно назвать Handlebars, Mustache, EJS и др.

Использование Pug


Для использования Pug, его сначала нужно установить:

npm i pug 

Затем его следует добавить в Express:

const express = require('express')const app = express()app.set('view engine', 'pug')

После этого мы можем создавать шаблоны в файлах с расширением .pug.

Создадим представление about:

app.get('/about', (req, res) => {    res.render('about')})

И шаблон в views/about.pug:

p Привет от Pug 

Данный шаблон создаст тег p с текстом Привет от Pug.

Интерполировать переменные можно так:

app.get('/about', (req, res) => {    res.render('about', { name: 'Иван' })})

p #{name} говорит привет

Подробнее о Pug мы поговорим в следующем разделе.

При использовании шаблонизатора для динамической генерации страниц можно столкнуться с некоторыми проблемами, особенно, когда возникает необходимость преобразовать HTML, например, в Pug. Для этого в сети существуют готовые решения. Вот одно из них: html-to-pug.com.

Использование Handlebars


Попробуем использовать Handlebars вместо Pug.

Устанавливаем его с помощью npm i handlebars.

В директории views создаем файл about.hbs следующего содержания:

{{name}} говорит привет 

Перепишем код Express:

const express = require('express')const app = express()const hbs = require('hbs')app.set('view engine', 'hbs')app.set('views', path.join(__dirname, 'views'))app.get('/about', (req, res) => {    res.render('about', { name: 'Иван' })})app.listen(3000, () => console.log('Сервер готов'))

Вы также можете рендерить React на стороне сервера с помощью пакета express-react-views.

Устанавливаем данный пакет:

npm i express-react-views

Теперь вместо Handlebars укажем Express использовать express-react-views в качестве движка для обработки jsx-файлов:

const express = require('express')const app = express()app.set('view engine', 'jsx')app.engine('jsx', require('express-react-views').createEngine())app.get('/about', (req, res) => {    res.render('about', { name: 'Иван' })})app.listen(3000, () => console.log('Сервер запущен'))

В директории views создаем файл about.jsx:

const React = require('react')class HelloMessage extends React.Component {    render() {        return <div>{this.props.name} говорит привет</div>    }}module.exports = HelloMessage

16. Справочник по Pug


Что такое Pug? Это шаблонизатор или движок для динамического рендеринга HTML-разметки, используемый Express по умолчанию.

Установка:

npm i pug

Настройка Express:

const path = require('path')cpnst express = require('express')const app = express()app.set('view engine', 'pug')app.set('views', path.join(__dirname, 'views'))app.get('/about', (req, res) => {    res.render('about', { name: 'Иван' })})

Шаблон (about.pug):

p #{name} говорит привет 

Передача функции, возвращающей значение:

app.get('about', (req, res) => {    res.render('about', { getName: () => 'Иван' })})

p #{getName()} говорит привет 

Добавление элементу идентификатора или класса:

p#title p.title 
Установка doctype:

doctype html 

Мета-теги:

html     head         meta(charset='utf-8')        meta(http-equiv='X-UA-Compatible', content='IE=edge')        meta(name='description', content='Описание')        meta(name='viewport', content='width=device-width, initial-scale=1')

Добавление скриптов или стилей:

html     head         link(rel="stylesheet", href="style.css")        script(src="script.js", defer)

Встроенные скрипты:

    script alert('тест')        script        (function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]= function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date; e=o.createElement(i);r=o.getElementsByTagName(i)[0]; e.src='//www.google-analytics.com/analytics.js'; r.parentNode.insertBefore(e,r)}(window,document,'script','ga')); ga('create','UA-XXXXX-X');ga('send','pageview');

Циклы:

ul     each color in ['Red', 'Green', 'Blue']        li= colorul     each color, index in ['Red', 'Green', 'Blue']        li= 'Номер цвета ' + index + ':' + color

Условия:

if name     h2 #{name} говорит привет else    h2 Привет if name    h2 #{name} говорит привет else if anotherName    h2 #{anotherName} говорит привет else    h2 Привет 

Переменные:

- var name = 'Иван'- age = 30 - var petr = { name: 'Петр' }- var friends = ['Иван', 'Петр']

Инкремент:

age++

Приваивание переменной элементу:

p= name span= age 

Перебор переменных:

for friend in friends     li= friendul     each friend in friends         li= friend 
Получение количества элементов:

p #{values.length}

While:

- var n = 0ul     while n <= 5        li= n++

Включение одного файла в другой:

include otherfile.pug 

Определение блоков.

Хорошо организованная система шаблонов предполагает создание базового шаблона и его расширение другими шаблонами по мере необходимости.

Базовый шаблон расширяется с помощью блоков:

html    head        link(rel="stylesheet", href="style.css")        script(src="script.js", defer)        body            block header                        block main                h1 Домашняя страница                p Добро пожаловать                        block footer

Расширение базового шаблона.

Шаблон расширяется с помощью ключевого слова extends:

extends home.pug 

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

extends home.pug block main     h1 Другая страница    p Привет     ul        li Раз элемент списка        li Два элемент списка 

Можно переопределять как один, так и несколько блоков. Те блоки, которые не переопределяются, сохраняют оригинальное содержимое.

Комментарии.

Видимые (сохраняются в разметке):

// однострочный комментарий//    многострочный    комментарий

Невидимые (удаляются при рендеринге):

//- однострочный комментарий//-     многострочный    комментарий

17. Middleware (промежуточный слой, промежуточное программное обеспечение)


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

Обычно, middleware применяется для обработки запроса/ответа либо для перехвата запроса перед его обработкой роутером. Middleware в общем виде выглядит так:

app.use((req, res, next) => {/* */})

Метод next() служит для передачи запроса следующему middleware, если в этом есть необходимость.

Если опустить next() в middleware, то обработка ответа завершится и он будет отправлен клиенту.

Middleware редко пишется вручную, как правило, в качестве таковых используются npm-пакеты.

Примером подобного пакета является cookie-parser, применяемый для преобразования куки в объект req.cookies. Данный пакет устанавливается с помощью npm i cookie-parser и используется следующим образом:

const express = require('express')const app = express()const cookieParser = require('cookie-parser')app.get('/', (req, res) => res.send('Привет!'))app.use(cookieParser())app.listen(3000, () => console.log('Сервер готов'))

Middleware может использоваться в определенном маршрутизаторе. Для этого он передается роутеру в качестве второго аргумента:

const myMiddleware = (req, res, next) => {    // ...     next()}app.get('/', myMiddleware, (req, res) => res.send('Привет!'))

Для того, чтобы сохранить данные, сформированные middleware, для их передачи другим middleware или обработчику запроса, используется объект Request.locals. Он позволяет записывать данные в текущий запрос:

req.locals.name = 'Иван'

18. Обработка статических файлов


Обычной практикой является хранение изображений, стилей и других файлов в директории public и использование их на верхнем уровне:

const express = require('express')const app = express()app.use(express.static('public'))// ... app.listen(3000, () => console.log('Сервер готов'))

Если в директории public имеется файл index.html, он будет отправлен в ответ на запрос к localhost:3000.

19. Отправка файлов


Express предоставляет удобный метод для отправки файлов в ответ на запрос Reaponse.download().

После обращения клиента к маршруту, отправляющему в ответ файл, клиенту будет направлен запрос на скачивание файла.

Метод Response.download() позволяет отправлять файлы в ответ на запрос вместо отображения страницы:

app.get('/', (req, res) => res.download('./file.pdf'))

В контексте приложения это выглядит так:

const express = require('express')const app = express()app.get('/', (req, res) => res.download('./file.pdf'))app.listen(3000, () => console.log('Сервер готов'))

При отправке файла можно определить его название:

res.download('./file.pdf', 'some-custom-name.pdf')

Третим параметром рассматриваемого метода является колбэк, вызываемый после отправки файла:

res.download('./file.pdf', 'some-custom-name.pdf', error => {    if (error) {        // обрабатываем ошибку    } else {        console.log('Файл успешно отправлен')    }})

20. Сессии


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

Пользователи не могут быть идентифицированы нативными средствами.

Вот где в игру вступают сессии.

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

Мы будем использовать express-session, поддерживаемый командой Express.

Устанавливаем его:

npm i express-session

Инициализируем:

const session = require('express-session')

Добавляем в Express в качестве middleware:

const express = require('express')const session = require('express-session')const app = express()app.use(session(    'secret': '343ji43j4n3jn4jk3n'))

После этого все запросы к приложению будут сессионными.

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

Сессия включается в состав запроса, доступ к ней можно получить через req.session:

app.get('/', (req, res, next) => {    // req.session })

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

req.sessions.name = 'Иван'console.log(req.sessions.name) // Иван 

При записи данные сериализуются (преобразуются в формат JSON), так что можно смело использовать вложенные объекты.

Сессии используются для передачи данных другим middleware или для их извлечения при последующих запросах.

Где хранятся сессионные данные? Это зависит от того, как настроен модуль express-session.

Такие данные могут храниться в:

  • памяти только при разработке
  • базе данных, например, MySQL или Mongo
  • кэше, например, Redis или Memcached

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

По идентификатору сервер осуществляет поиск хранящихся на нем данных.

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

Лучшим решением является Redis, однако, она требует дополнительной настройки инфраструктуры.

Другим популярным решением является пакет cookie-session. Он сохраняет данные в куки на стороне клиента. Данный способ использовать не рекомендуется, поскольку данные будут включаться в каждый запрос клиента и размер данных ограничен 4 Кб. Кроме того, обычные куки не подходят для хранения конфиденциальной информации. Существуют безопасные куки, передаваемые по протоколу HTTPS, но такие куки требуют дополнительной настройки с помощью прокси-сервера.

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

Следите за обновлениями. Благодарю за внимание и хороших выходных.
Подробнее..

Подключение и настройка TradingView графиков

19.09.2020 20:08:00 | Автор: admin


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


Статья будет в формате "книги рецептов" с open source решениями для криптовалютной биржи Binance и Forex.


Привет, Хабр!


У библиотеки TradingView (charting_library) высокий порог входа, при этом менее популярной она не стала из-за того, что используется на одноименном сервисе TradingView.com. Решил сделать "книгу рецептов" с ответами на основные вопросы.


Эта статья продолжение поста: "Финансовые графики для вашего приложения".


Cook book


Контент буду дополнять по мере появляния новых сложностей. Если у Вас есть вопросы и Вы не нашли ответы в статье, пишите в комментариях, будем разбираться вместе :)


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


Лицензия


Можно использовать бесплатно в коммерческих и некоммерческих целях. Самый главный критерий сохранность логотипа компании на графиках.


При запросе доступа к графикам обязательно указывать конечный домен, где они будут использоваться. При реализации одного из проектов мы подключали Forex-дату к графику, все настроили и запустили. За 2-й месяц заказчик данные Forex не проплатил, из-за этого графики полностью не загружались и отсутствовал логотип. После сложившейся ситуации проверяющий связался с заказчиком последством эл.почты с вопросами для разъяснения ситуации.


Получение доступа


У библиотеки закрытый доступ на GitHub, чтобы его получить необходимо:


  1. Заполнить заявку на сайте
  2. Подписать договор
  3. Получить доступ к репозиторию на GitHub

Мой опыт получения доступа


Спустя примерно 4 недели после заполнения заявки мне прислали договор для подписания. Через 3 дня после подписания открыли доступ к библиотеке. Судя по отзывам, период получения доступа плавает и точных сроков нет.


Не отображается график, даже с тестовыми данными


Для решения нужно подключить виджет и глобально указать доступ к бибилиотеке.


Подключение виджета


// для Nodejsimport { widget } from '../public/charting_library/charting_library.min'const widget = new widget({ <options> })

Доступ к библиотеке


Указать глобальный путь к папке charting_library в опциях виджета library_path: '/charting_library/'


Глобальный путь будет отличаться от используемых модулей. В моем случае используется Vuejs с указанием в vue.config.js => publicPath: '/'. Структура папок: /public/index.html, /public/charting_library/ и настройки виджета, которые указаны выше.


Документация


Подключение данных


В базовом варианте используются тестовые данные. Далее необходимо подключить свой провайдер данных, используя одно из двух решений: JS API или UDF. Напрямую "скормить" массив данных не получится. Мы расмотрим JSAPI, UDF подключается аналогично, с отличием в указании конечной точки на сервере, откуда будет получать данные.


  • JS API подключение на стороне клиента
  • UDF подключение на серверной части

Основное отличие JSAPI от UDF, в отсутствии возможности для UDF добавить WebSocket подключение. При указании конечной точки на сервере, вы выставляете интервал для каждого запроса: datafeed: new Datafeeds.UDFCompatibleDatafeed('http://localhost:3000/datafeed', 1000)


Документация


TradingView JS API adapter


Чтобы настроить адаптер, нужно понимать, что каждый хук выполняется последовательно и для отладки лучше добавить вывод в консоль информации о запуске хука console.log('[<название хука>]: Method call').


Последовательность запуска: onReady => resolveSymbol => getBars => subscribeBars => unsubscribeBars.


Если вы меняете таймфрейм, символ, вызывается хук unsubscribeBars, который обращается к вашей функции, которая сбрасывает WebSocket подключение с провайдером данных. Если вы не используете subscribeBars, то и unsubscribeBars вам не нужен. getServerTime хук не обязательный, но если вам требуется использовать время сервера, подключайте его.


Если провайдер данных не отдает объемы, то можете указать в хуке resolveSymbol has_no_volume: true.


export default {    // Инициализация настроек, должна отдаваться АСИНХРОННО    onReady: (callback) => {        console.log('[onReady]: Method call');                // setTimeout(() => callback(<объект с настройками>))    },    /*     // Не требуется, если не используете поиск    searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {        console.log('[searchSymbols]: Method call');    },     */    // получение данных о конкретном символе    resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {        console.log('[resolveSymbol]: Method call', symbolName);                // onSymbolResolvedCallback({ ..., has_no_volume: true})    },    // получение исторических данные для конкретного символа    getBars: (symbolInfo, interval, from, to, onHistoryCallback, onErrorCallback, firstDataRequest) => {        console.log('[getBars] Method call', symbolInfo, interval)        console.log('[getBars] First request', firstDataRequest)    },    // подписка на обновления WebSocket    subscribeBars: (symbolInfo, interval, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback) => {        console.log('[subscribeBars]: Method call with subscribeUID:', subscribeUID);    },        // вызывается для отписки от стрима    unsubscribeBars: (subscriberUID) => {        console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);    },    getServerTime: (callback) => {}};

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


Документация JS API | Рабочий пример


TradingView UDF adapter


UDF адаптер актуален, когда данные запрашиваются со своего сервера. В конструкторе клиента нужно указать datafeed: new Datafeeds.UDFCompatibleDatafeed('http://localhost:3000/datafeed', 1000)


// пример оформления плагина для **Fastify**// main.jsconst app = Fastify()app.register(import('./modules/tradingview'), {})// tradingview.jsconst plugin = async (app, options) => {        // проверяем работу конечной точки    app.get('/', (req, res) => {        res.code(200).header('Content-Type', 'text/plain')            .send('Welcome to UDF Adapter for TradingView. See ./config for more details.')    })        // время сервера    app.get('/time', (req, res) => {        console.log('[time]: Method call')        const time = Math.floor(Date.now() / 1000)  // In seconds        res.code(200).header('Content-Type', 'text/plain').send(time.toString())    })         // аналог onReady        // https://github.com/tradingview/charting_library/wiki/UDF#data-feed-configuration-data    app.get('/config', (req, res) => {        console.log('[config]: Method call')    })    // вызывается если: supports_group_request: true & supports_search: false    app.get('/symbol_info', async (req, res) => {        console.log('[symbol_info]: Method call')    })    // вызывается если: supports_group_request: false & supports_search: true    app.get('/symbols', async (req, res) => {        console.log('[symbol_info]: Method call')        const symbol = await getSymbols(req.query.symbol)        return symbol    })        // аналог getBars, запрашивает исторических данные    app.get('/history', async (req, res) => {        console.log('[history]: Method call')    })}

Документация UDF


JS API getBars хук вызывается много раз


Так бывает, когда не хватает данных и библиотека самостоятельно пытается "догрузить" информацию. В хуке getBars есть параметр firstDataRequest, который возвращает булевское значение true\false, используйте его. Возвращает true только при загрузке маркета.


getBars: (symbolInfo, interval, from, to, onHistoryCallback, onErrorCallback, firstDataRequest) => {        console.log('[getBars] Method call', symbolInfo, interval)        console.log('[getBars] First request', firstDataRequest)                if (firstDataRequest) {                    console.log('do something')                }},

У моего провайдера нет WebSocket подключения


Не обязательно использовать UDF провайдер, если нет стрима. Интервал запросов задать не получится для JS API адаптера, но это не мешает нам добавить setInterval в subscribeBars и отдавать данные для обновления.


subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscribeUID,  onResetCacheNeededCallback) => {        console.log('[subscribeBars]: Method call with subscribeUID:', subscribeUID)        window.interval = setInterval(function () {            getLastKline(symbolInfo.ticker, resolution).then(kline => onRealtimeCallback(kline))        }, 1000 * 60) // 60s update intervalunsubscribeBars: (subscriberUID) => {        console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID)        clearInterval(window.interval)        console.log('[unsubscribeBars]: cleared')},},

Рабочий пример


Кастомизация дизайна


По умолчанию доступны две темы: theme: "Light" || "Dark". Также можно использовать собственные цветовые решение. Со временем столкнетесь с проблемой, когда цвета поменялись везде, кроме header_widget (верхний блок с кнопками поиска, сравнения и пр.), его нужно менять через .css.


В опциях виджета нужно указать: custom_css_url: '/tradingview.css', где / абсолютный путь от вашего index.html. С контентом:


.chart-controls-bar {    border-top: none !important;}.chart-page, .group-wWM3zP_M-  {    background: transparent !important;}.pane-separator {    display: none !important;}

Документация


Сохрание данных


Возможно понадобится сохранять "рисовалки".


Save\Load методы


Самый простой вариант, который можно использовать, если не планируется рисовать много на графиках. Простой, потому что можете вызвать объект со всеми данными графика widget.save(cb => this.setOverlay(cb)) и сохранить там, где будет удобно.


Рабочий пример


Save\Load adapter


Похож на UDF adapter. На сервере поднимаете конечные точки для сохранения\загрузки данных.


Документация


У меня что-то не работает, делаю все по документации


Реальный кейс, обратился фрилансер с проектом, проект был старый, он его переписывал. По итогу просто была старая версия библиотеки. Проверяйте версию.


Другая ситуация, когда пытаются вызвать методы у еще незагруженного графика, отслеживайте состояние через onChartReady. Если нет под капотом реактивности, чтобы отследить загрузку графика, используйте паттерн Observer.


widget.onChartReady(function() {    // It's now safe to call any other methods of the widget});

Графики бибилиотеки отличаются от версий на сайте TradingView.com


Да, это нормально.


Как добавить ордера на график


После добавления ордера на график, нет доступа массиву, поэтому необходимо самостоятельно отслеживать ордера. Поделюсь своим решением оформленное в формате миксина для Vuejs, суть будет понятна.


import orders from '../../../multiblock/orders/mixin'import createOrder from './createOrder'import openOrders from './openOrders'import trades from './trades'export default {    mixins: [orders, createOrder, openOrders, trades],    data: () => ({        lines: new Map()    }),    watch: {        onChartReady(val) {            if (val) {                //* Uncomment: Testing price line                // this.line({ id: 'test', price: 0.021, quantity: 100 })            }        },    },    methods: {        // Line: open orders        positionLine(data) {            this.line(data)                .onCancel(() => {                    this.deleteLine(data.id)                    this.$bus.$emit('market-orders-deleteOrder', data.id)                })                .onMove(() => this.$bus.$emit('market-orders-updateOrder', { id: data.id, price: this.lines.get(data.id).getPrice() }))        },        // Line: order mobule ('price', 'stopPrice')        orderLine({ id = 'price', ...data }) {            this.line({ id, ...data })                .onMove(() => {                    // Set new value on draging                    this.$store.commit('setMarketOrder', { [id]: this.lines.get(id).getPrice() })                })                .onCancel(() => {                    // Delete price line & set price = 0                    this.deleteLine(id)                    this.$store.commit('setMarketOrder', { [id]: 0 }) // set 0 value in vuex storage                })        },        line({ id = 'price', text = 'Price', color = '#ff9f0a', price, quantity, fontColor = '#fff', lineStyle = 2, lineLength = 25 }) {            if (this.lines.has(id)) this.deleteLine(id)            // Creating line from scratch            const widget = this.widget.chart().createOrderLine()                .setText(text)                .setPrice(price)                .setQuantity(quantity)                .onModify(res => res) // Need for dragging                // Customize color                .setLineColor(color)                .setBodyTextColor(fontColor)                .setBodyBorderColor(color)                .setBodyBackgroundColor(color)                .setQuantityBorderColor(color)                .setQuantityTextColor(fontColor)                .setQuantityBackgroundColor(color)                .setCancelButtonBorderColor(color)                .setCancelButtonBackgroundColor(color)                .setCancelButtonIconColor(fontColor)                .setLineLength(lineLength) // Margin right 25%                .setLineStyle(lineStyle)            this.lines.set(id, widget)            return widget // return for orderLine func()        },        deleteLine(id) {            this.lines.get(id).remove()            this.lines.delete(id)        },        deleteLines() {            this.lines.forEach((value, key) => this.deleteLine(key))        }    }}

Документация


Как добавить формы на график


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


Документация | Список доступных форм


Хочу использовать PineScript


charting_library не поддерживает такой функционал.


Хочу добавить свой индикатор


Посмотрите в сторону Custom Studies


Хочу использовать несколько графиков в одном окне


В бесплатной версии charting_library такой функционал отсутствует. При необходимости можно своими силами это сделать HTML+CSS.


Open source


  • tradingview-jsapi-binance подключенная биржа Binance c JS API адаптером и WebSocket стримом
  • tradingview-jsapi-forex данные Forex для JS API адаптера. Ежеминутное обновлением данных без WebSocket с Save\Load методами

Заключение


Статья будет дополняться. Если есть кейс с проблемой решением, пишите, дополню статью с указанием авторства. Возможно вопросы и пожелания, пишите в комментариях, будет интересно услышать Ваше мнение :)


Спасибо за внимание!

Подробнее..

Подключение и настройка графиков TradingView

19.09.2020 22:22:49 | Автор: admin


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


Статья будет в формате "книги рецептов" с open source решениями для криптовалютной биржи Binance и Forex.


Привет, Хабр!


У библиотеки TradingView (charting_library) высокий порог входа, при этом менее популярной она не стала из-за того, что используется на одноименном сервисе TradingView.com. Решил сделать "книгу рецептов" с ответами на основные вопросы.


Эта статья продолжение поста: "Финансовые графики для вашего приложения".


English version.


Cook book


Контент буду дополнять по мере появляния новых сложностей. Если у Вас есть вопросы и Вы не нашли ответы в статье, пишите в комментариях, будем разбираться вместе :)


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


Лицензия


Можно использовать бесплатно в коммерческих и некоммерческих целях. Самый главный критерий сохранность логотипа компании на графиках.


При запросе доступа к графикам обязательно указывать конечный домен, где они будут использоваться. При реализации одного из проектов мы подключали Forex-дату к графику, все настроили и запустили. За 2-й месяц заказчик данные Forex не проплатил, из-за этого графики полностью не загружались и отсутствовал логотип. После сложившейся ситуации проверяющий связался с заказчиком последством эл.почты с вопросами для разъяснения ситуации.


Получение доступа


У библиотеки закрытый доступ на GitHub, чтобы его получить необходимо:


  1. Заполнить заявку на сайте
  2. Подписать договор
  3. Получить доступ к репозиторию на GitHub

Мой опыт получения доступа


Спустя примерно 4 недели после заполнения заявки мне прислали договор для подписания. Через 3 дня после подписания открыли доступ к библиотеке. Судя по отзывам, период получения доступа плавает и точных сроков нет.


Не отображается график, даже с тестовыми данными


Для решения нужно подключить виджет и глобально указать доступ к бибилиотеке.


Подключение виджета


// для Nodejsimport { widget } from '../public/charting_library/charting_library.min'const widget = new widget({ <options> })

Доступ к библиотеке


Указать глобальный путь к папке charting_library в опциях виджета library_path: '/charting_library/'


Глобальный путь будет отличаться от используемых модулей. В моем случае используется Vuejs с указанием в vue.config.js => publicPath: '/'. Структура папок: /public/index.html, /public/charting_library/ и настройки виджета, которые указаны выше.


Документация


Подключение данных


В базовом варианте используются тестовые данные. Далее необходимо подключить свой провайдер данных, используя одно из двух решений: JS API или UDF. Напрямую "скормить" массив данных не получится. Мы расмотрим JSAPI, UDF подключается аналогично, с отличием в указании конечной точки на сервере, откуда будет получать данные.


  • JS API подключение на стороне клиента
  • UDF подключение на серверной части

Основное отличие JSAPI от UDF, в отсутствии возможности для UDF добавить WebSocket подключение. При указании конечной точки на сервере, вы выставляете интервал для каждого запроса: datafeed: new Datafeeds.UDFCompatibleDatafeed('http://localhost:3000/datafeed', 1000)


Документация


TradingView JS API adapter


Чтобы настроить адаптер, нужно понимать, что каждый хук выполняется последовательно и для отладки лучше добавить вывод в консоль информации о запуске хука console.log('[<название хука>]: Method call').


Последовательность запуска: onReady => resolveSymbol => getBars => subscribeBars => unsubscribeBars.


Если вы меняете таймфрейм, символ, вызывается хук unsubscribeBars, который обращается к вашей функции, которая сбрасывает WebSocket подключение с провайдером данных. Если вы не используете subscribeBars, то и unsubscribeBars вам не нужен. getServerTime хук не обязательный, но если вам требуется использовать время сервера, подключайте его.


Если провайдер данных не отдает объемы, то можете указать в хуке resolveSymbol has_no_volume: true.


export default {    // Инициализация настроек, должна отдаваться АСИНХРОННО    onReady: (callback) => {        console.log('[onReady]: Method call');                // setTimeout(() => callback(<объект с настройками>))    },    /*     // Не требуется, если не используете поиск    searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {        console.log('[searchSymbols]: Method call');    },     */    // получение данных о конкретном символе    resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {        console.log('[resolveSymbol]: Method call', symbolName);                // onSymbolResolvedCallback({ ..., has_no_volume: true})    },    // получение исторических данные для конкретного символа    getBars: (symbolInfo, interval, from, to, onHistoryCallback, onErrorCallback, firstDataRequest) => {        console.log('[getBars] Method call', symbolInfo, interval)        console.log('[getBars] First request', firstDataRequest)    },    // подписка на обновления WebSocket    subscribeBars: (symbolInfo, interval, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback) => {        console.log('[subscribeBars]: Method call with subscribeUID:', subscribeUID);    },        // вызывается для отписки от стрима    unsubscribeBars: (subscriberUID) => {        console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);    },    getServerTime: (callback) => {}};

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


Документация JS API | Рабочий пример


TradingView UDF adapter


UDF адаптер актуален, когда данные запрашиваются со своего сервера. В конструкторе клиента нужно указать datafeed: new Datafeeds.UDFCompatibleDatafeed('http://localhost:3000/datafeed', 1000)


// пример оформления плагина для **Fastify**// main.jsconst app = Fastify()app.register(import('./modules/tradingview'), {})// tradingview.jsconst plugin = async (app, options) => {        // проверяем работу конечной точки    app.get('/', (req, res) => {        res.code(200).header('Content-Type', 'text/plain')            .send('Welcome to UDF Adapter for TradingView. See ./config for more details.')    })        // время сервера    app.get('/time', (req, res) => {        console.log('[time]: Method call')        const time = Math.floor(Date.now() / 1000)  // In seconds        res.code(200).header('Content-Type', 'text/plain').send(time.toString())    })         // аналог onReady        // https://github.com/tradingview/charting_library/wiki/UDF#data-feed-configuration-data    app.get('/config', (req, res) => {        console.log('[config]: Method call')    })    // вызывается если: supports_group_request: true & supports_search: false    app.get('/symbol_info', async (req, res) => {        console.log('[symbol_info]: Method call')    })    // вызывается если: supports_group_request: false & supports_search: true    app.get('/symbols', async (req, res) => {        console.log('[symbol_info]: Method call')        const symbol = await getSymbols(req.query.symbol)        return symbol    })        // аналог getBars, запрашивает исторических данные    app.get('/history', async (req, res) => {        console.log('[history]: Method call')    })}

Документация UDF


JS API getBars хук вызывается много раз


Так бывает, когда не хватает данных и библиотека самостоятельно пытается "догрузить" информацию. В хуке getBars есть параметр firstDataRequest, который возвращает булевское значение true\false, используйте его. Возвращает true только при загрузке маркета.


getBars: (symbolInfo, interval, from, to, onHistoryCallback, onErrorCallback, firstDataRequest) => {        console.log('[getBars] Method call', symbolInfo, interval)        console.log('[getBars] First request', firstDataRequest)                if (firstDataRequest) {                    console.log('do something')                }},

У моего провайдера нет WebSocket подключения


Не обязательно использовать UDF провайдер, если нет стрима. Интервал запросов задать не получится для JS API адаптера, но это не мешает нам добавить setInterval в subscribeBars и отдавать данные для обновления.


subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscribeUID,  onResetCacheNeededCallback) => {        console.log('[subscribeBars]: Method call with subscribeUID:', subscribeUID)        window.interval = setInterval(function () {            getLastKline(symbolInfo.ticker, resolution).then(kline => onRealtimeCallback(kline))        }, 1000 * 60) // 60s update interval},unsubscribeBars: (subscriberUID) => {        console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID)        clearInterval(window.interval)        console.log('[unsubscribeBars]: cleared')}

Рабочий пример


Кастомизация дизайна


По умолчанию доступны две темы: theme: "Light" || "Dark". Также можно использовать собственные цветовые решение. Со временем столкнетесь с проблемой, когда цвета поменялись везде, кроме header_widget (верхний блок с кнопками поиска, сравнения и пр.), его нужно менять через .css.


В опциях виджета нужно указать: custom_css_url: '/tradingview.css', где / абсолютный путь от вашего index.html. С контентом:


.chart-controls-bar {    border-top: none !important;}.chart-page, .group-wWM3zP_M-  {    background: transparent !important;}.pane-separator {    display: none !important;}

Документация


Сохрание данных


Возможно понадобится сохранять "рисовалки".


Save\Load методы


Самый простой вариант, который можно использовать, если не планируется рисовать много на графиках. Простой, потому что можете вызвать объект со всеми данными графика widget.save(cb => this.setOverlay(cb)) и сохранить там, где будет удобно.


Рабочий пример


Save\Load adapter


Похож на UDF adapter. На сервере поднимаете конечные точки для сохранения\загрузки данных.


Документация


У меня что-то не работает, делаю все по документации


Реальный кейс, обратился фрилансер с проектом, проект был старый, он его переписывал. По итогу просто была старая версия библиотеки. Проверяйте версию.


Другая ситуация, когда пытаются вызвать методы у еще незагруженного графика, отслеживайте состояние через onChartReady. Если нет под капотом реактивности, чтобы отследить загрузку графика, используйте паттерн Observer.


widget.onChartReady(function() {    // It's now safe to call any other methods of the widget});

Графики бибилиотеки отличаются от версий на сайте TradingView.com


Да, это нормально.


Как добавить ордера на график


После добавления ордера на график, нет доступа массиву, поэтому необходимо самостоятельно отслеживать ордера. Поделюсь своим решением оформленное в формате миксина для Vuejs, суть будет понятна.


import orders from '../../../multiblock/orders/mixin'import createOrder from './createOrder'import openOrders from './openOrders'import trades from './trades'export default {    mixins: [orders, createOrder, openOrders, trades],    data: () => ({        lines: new Map()    }),    watch: {        onChartReady(val) {            if (val) {                //* Uncomment: Testing price line                // this.line({ id: 'test', price: 0.021, quantity: 100 })            }        },    },    methods: {        // Line: open orders        positionLine(data) {            this.line(data)                .onCancel(() => {                    this.deleteLine(data.id)                    this.$bus.$emit('market-orders-deleteOrder', data.id)                })                .onMove(() => this.$bus.$emit('market-orders-updateOrder', { id: data.id, price: this.lines.get(data.id).getPrice() }))        },        // Line: order mobule ('price', 'stopPrice')        orderLine({ id = 'price', ...data }) {            this.line({ id, ...data })                .onMove(() => {                    // Set new value on draging                    this.$store.commit('setMarketOrder', { [id]: this.lines.get(id).getPrice() })                })                .onCancel(() => {                    // Delete price line & set price = 0                    this.deleteLine(id)                    this.$store.commit('setMarketOrder', { [id]: 0 }) // set 0 value in vuex storage                })        },        line({ id = 'price', text = 'Price', color = '#ff9f0a', price, quantity, fontColor = '#fff', lineStyle = 2, lineLength = 25 }) {            if (this.lines.has(id)) this.deleteLine(id)            // Creating line from scratch            const widget = this.widget.chart().createOrderLine()                .setText(text)                .setPrice(price)                .setQuantity(quantity)                .onModify(res => res) // Need for dragging                // Customize color                .setLineColor(color)                .setBodyTextColor(fontColor)                .setBodyBorderColor(color)                .setBodyBackgroundColor(color)                .setQuantityBorderColor(color)                .setQuantityTextColor(fontColor)                .setQuantityBackgroundColor(color)                .setCancelButtonBorderColor(color)                .setCancelButtonBackgroundColor(color)                .setCancelButtonIconColor(fontColor)                .setLineLength(lineLength) // Margin right 25%                .setLineStyle(lineStyle)            this.lines.set(id, widget)            return widget // return for orderLine func()        },        deleteLine(id) {            this.lines.get(id).remove()            this.lines.delete(id)        },        deleteLines() {            this.lines.forEach((value, key) => this.deleteLine(key))        }    }}

Документация


Как добавить формы на график


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


Документация | Список доступных форм


Хочу использовать PineScript


charting_library не поддерживает такой функционал.


Хочу добавить свой индикатор


Посмотрите в сторону Custom Studies


Хочу использовать несколько графиков в одном окне


В бесплатной версии charting_library такой функционал отсутствует. При необходимости можно своими силами это сделать HTML+CSS.


Open source


  • tradingview-jsapi-binance подключенная биржа Binance c JS API адаптером и WebSocket стримом
  • tradingview-jsapi-forex данные Forex для JS API адаптера. Ежеминутное обновлением данных без WebSocket с Save\Load методами

Заключение


Статья будет дополняться. Если есть кейс с проблемой решением, пишите, дополню статью с указанием авторства.


Также интересно услышать ваше мнение, опыт, вопросы и пожелания.


Спасибо за внимание!

Подробнее..

Принимаем криптовалютные платежи с Coinbase Commerce

27.09.2020 00:06:12 | Автор: admin


Если Вы планиуете подключать криптовалютные платежи и еще не знакомы с Coinbase Commerce, стоит потратить 5 минуты Вашего времени. Расскажу о подключении, настройке и поделюсь готовым open source решениями для Nodejs.


Coinbase Commerce это крипто-эквайринг без комиссий, паспортов, с отличным API и Вашим личным счетом.


Привет, Хабр!


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


Главный вопросом был выбор сервиса, который подходит для целевой аудитории. В нашем случае, целевая аудитория это "штаты". Как известно, есть Америка и есть остальной мир, поэтому количество сервисов, которые нам подходят, быстро уменьшилось.


Список доступных криптовалют: USD Coin, Dai, Bitcoin, Bitcoin Cash, Ethereum, Litecoin


Coinbase Commerce (далее CC).


Плюсы и минусы


  • Быстрая настройка
  • Нет комиссий клиент переводит деньги напрямую на Ваш счет
  • Принимает стейблкоины USD Coin & DAI, а также много других
  • Глобальный сервис у Америки в приоритете биржа "Coinbase", имеет фактор доверия даже со стороны государства. Для всего остального мира не принципиально
  • Нет посредников. Только Ваш кошелек и Ваш аккаунт
  • Отсутствие возвратных платежей. Конечно, клиенту можно вернуть средства по требованию, но это на Ваше усмотрение
  • Тестирование хуков с панели сервиса можно отправлять тестовые вебхуки

Минусов почти нет, кромне одного и он весомый. Чтобы полноценно протестировать работу приложения, нужно использовать свои деньги. Да, хуки вы можете отправлять и "напильником" подогнать работу, однако этого не достаточно для продакшн-версии.


Регистрация



Процесс проходит в 5 этапов.


  1. Регистрация аккаунта email + пароль
  2. Подключение 2х этапной верификации
  3. Настройка кошелька
  4. Бекап кошелька
  5. Доступ к интерфейсу и прием платежей

Подключение 2х этапной верификации




Настройка кошелька



При создании кошелька, CC генерирует seed-фразы, которые нужно сохранить.




После ручного ввода seed-фраз, CC предлагает использовать Google Drive для бекапа кошелька.




Доступ к интерфейсу и прием платежей



Прием платежей


Есть два способа приема платежей, мы рассмотрим оба.


  1. С использованием интерфейса фиксированная оплата за товар\услугу или пожертвования. Не требует навыков программирования, подходит всем.
  2. С использованием API более сложный вариант, требующий настройки бэкенда. Позволяет создавать динамические платежи.

С использованием интерфейса



Создание позиции товара\услуги с фиксированной ценой.



Прием пожертвований.



Последним этапом, CC предлагает выбрать, какую информацию о пользователе будет собирать.



После того, как создаем позицию для оплаты, система нам предлагает варианты интеграции (ссылка или скрипт).




// link// https://commerce.coinbase.com/checkout/<id><a href="http://personeltest.ru/aways/commerce.coinbase.com/checkout/<id>" target="_blank">// embed<div>  <a class="buy-with-crypto"     href="http://personeltest.ru/aways/commerce.coinbase.com/checkout/<id>">Buy with Crypto</a>  <script src="http://personeltest.ru/aways/commerce.coinbase.com/v1/checkout.js?version=201807">  </script></div>

С использованием API


Вариант с API формирует платежку вручную и она активна ограниченное количество времени.


Схема работы


С клиента передаем информацию о сумме платежа на сервер, далее формируем временный
"checkout" и возвращаем ссылку https://commerce.coinbase.com/checkout/<id> на клиент, по которой пользователь переходит на страницу оплаты или происходит автоматическая переадресация.


В случае успешной оплаты, отмены или, если время платежки истечет, CC направит информацию на вебхук.


Настройка WebHook


Необходимо добавить url публичного вебхука Settings => Webhook subscriptions.



Протестировать работу вебхука можно на бесплатном сервисе Webhook.site.


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


// install$ npm install ngrok -g// `http://localhost:3000/coinbase-webhook` => `https://<ngrok-host>/coinbase-webhook`$ ngrok http 3000

Как вариант, можно использовать пример ответа вебхука с сайта Webhook.site и далее отправлять через Postman на локальную точку доступа.


После добавления вебхука, его можно протестировать. Интерфейс позволяет менять только события: charge:created, charge:confirmed, charge:failed, charge:delayed, charge:pending, charge:resolved.


Документация Webhook



Open Source


Подготовил вариант c использованием API coinbase-commerce-node.



Заключение


В целом, мне понравилась интеграции Coinbase Commerce. Поделитесь своим опытом подключения платежей для приложений.


Спасибо за внимание!

Подробнее..

Перевод Отправка ответа с Коа

04.12.2020 18:08:20 | Автор: admin

Перевод статьи подготовлен в преддверии старта курса Node.js Developer.


Koa - это небольшой фреймворк, позволяющий создавать бэкэнд-приложения, работающие на платформе Node.js.

В этой статье мы рассмотрим, как отправлять различные типы ответов с помощью Koa.

Отправка тела (Body)

Для отправки тела ответа можно установить атрибут тела ctx . Например, мы можем отправить тело ответа следующим образом:

const Koa = require('koa');const app = new Koa();app.use(async (ctx, next) => {    ctx.body = 'foo';});app.listen(3000);

В приведенном выше коде мы устанавливаем свойство ctx.body в 'foo' . Таким образом, это то, что мы получим, когда перейдем по адресу / с помощью нашего браузера или сделаем запрос на него с помощью HTTP-клиента.

Заголовок (Header) отправки ответа

Мы можем отправлять ответы в нашем коде Koa, установив свойство ctx.response.

Для установки заголовка мы можем установить заголовок ответа методом ctx.set.

Например, мы можем использовать его следующим образом:

const app = new Koa();app.use(async (ctx, next) => {  ctx.set('Access-Control-Allow-Origin', '*');  ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');  ctx.set('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');  ctx.body = 'hello';});app.listen(3000);

В вышеприведенном коде мы вызываем ctx.set для установки различных заголовков, включая заголовоки: Access-Control-Allow-Origin, Access-Control-Allow-Headers и Access-Control-Allow-Methods.

Как только мы сделаем запрос по адресу / , мы увидим эти заголовки в HTTP клиентах, таких как Chrome или Postman.

Код статуса ответа на отправку

Мы можем посылать коды статуса ответа, установив значение кода статуса в свойство respondbse.status.

Например, это можно сделать следующим образом:

const Koa = require('koa');const app = new Koa();app.use(async (ctx, next) => {  ctx.status = 202;  ctx.body = 'accepted';});app.listen(3000);

В коде выше мы установили значение ctx.status 's равным 202. Таким образом, когда мы сделаем запрос по адресу / , мы получим код состояния 202 из запроса.

Некоторые распространенные коды ответа, которые отправляются, включают в себя:

200 - OK

201 - created - создан

202 - accepted - принято

203 - non-authoritative information- неавторитетная информация

204 - no content- нет содержания

301 - moved permanently - перемещено навсегда

302 - found - найдено

303 - see other - см. прочее

307 - temporary redirect - временная переадресация

308 - permanent redirect - постоянная переадресация

400 - bad request - неверный запрос

401 - unauthorized - не аутентифицированный запрос

403 - forbidden- неавторизованный запрос

404 - not found - не найдено

405 - method not allowed- способ не разрешён

406 - not acceptable- неприемлемо

422 - unprocessable entity - необрабатываемая сущность

500 - internal server error- внутреннего ошибка сервера

501 - not implemented- не реализован

502 - bad gateway- неверный шлюз

504 - gateway timeout- таймаут шлюза

Отправка заголовков (Headers)

Мы можем установить свойство ctx.response.lastModified на нужную нам дату.

Например, мы можем установить его следующим образом:

const Koa = require('koa');const app = new Koa();app.use(async (ctx, next) => {  ctx.response.lastModified = new Date(2020, 0, 1);  ctx.body = 'foo';});app.listen(3000);

В приведенном выше коде мы устанавливаем свойство lastModified на 1 января 2020 года, поэтому, когда мы сделаем запрос по адресу /, мы получим Last-Modified из ответа со значением Wed, 01 января 2020 года 00:00:00 GMT .

Мы можем установить заголовок Content-Type, задав свойство ctx.type. Например, это можно сделать следующим образом:

const Koa = require('koa');const app = new Koa();app.use(async (ctx, next) => {  ctx.type = 'text/plain; charset=utf-8';  ctx.body = 'foo';});app.listen(3000);

В приведенном выше коде мы имеем следующую строчку:

ctx.type = 'text/plain; charset=utf-8';

Чтобы установить заголовок Content-Type в 'text/plain; charset=utf-8' . Затем мы увидим это в качестве значения заголовка Content-Type при выполнении запроса по адресу / .

Чтобы добавить еще один заголовок к ответу, мы можем вызвать метод ctx.append() для добавления новых заголовков. Он принимает ключ и значение в качестве своих 2 аргументов.

Например, мы можем использовать его следующим образом:

const Koa = require('koa');const app = new Koa();app.use(async (ctx, next) => {  ctx.append('a', 1);  ctx.body = 'foo';});app.listen(3000);

В приведенном выше коде мы вызвали метод ctx.append() с ключом заголовка 'a' и соответствующим ему значением 1.

Затем, когда мы сделаем запрос по адресу /, то получим заголовок ответа A со значением 1.

Заключение

Мы можем установить заголовки ответов, вызвав метод ctx.append(). Чтобы вернуть тело ответа, мы можем установить свойство ctx.body со значением.

Для установки кода статуса ответа, мы устанавливаем свойство ctx.status.


Записаться на бесплатный урок.

Подробнее..

Простой WebSocket-сервер на Node.JS

12.03.2021 18:21:40 | Автор: admin

Сейчас мы с вами напишем простой WebSocket-сервер на node.js. При подключении к этому серверу в ответ придёт приветственное сообщение. А так же будет доступна к выполнению пара не сложных команд.

Для этого потребуется установить Node.js с менеджером пакетов npm, он идёт в комплекте

Настройка проекта

В начале, нам потребуется создать директорию будущего проекта

mkdir websocket-server-node

Переходим в директорию

cd websocket-server-node

Далее нужно инициализировать новый проект через npm

npm init

Установщик потребует ответить на несколько вопросов, их можно пропустить

После инициализации проекта, необходимо добавить в проект библиотеку WS и настройку для работы с текстом в UTF-8

npm install ws
npm install --save-optional utf-8-validate

Код websocket-сервера

Теперь приступим к написанию кода. В директории проекта создадим новый файл server.js, откроем файл. Далее я последовательно опишу весь код, а вот ссылка на полный код на GitHub.

server.js:

В начале нужно подключить библиотеку для работы с websocket

const WebSocket = require('ws');

Далее, создадим константу, экземпляр класса WebSocket, с указанием порта на котором будет запущен WebSocket-сервер.

const wsServer = new WebSocket.Server({port: 9000});

В отличии от HTTP-сервера, WebSocket-сервер принимает подключение и удерживает его. HTTP-сервер принимает запросы напрямую, а WebSocket-сервер принимает запросы от подключения, такое соединение является полнодуплексное.

Напишем обработчик подключения, в качестве обработчика укажем функцию onConnect.

wsServer.on('connection', onConnect);

В момент подключения нового клиента в onConnection передаётся объекта ws-соединения, через него сервер будет общаться с каждым конкретным клиентом.

Функция onConnect принимает только один параметр, назовём его wsClient. В нашем конкретном примере мы подключим только два обработчика событий на объект wsClient: message и close.

message - обрабатывает событие входящего сообщения от клиента.

close - событие разрыва соединения с клиентом.

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

Далее опишу заготовку для функции onConnect:

function onConnect(wsClient) {  console.log('Новый пользователь');  // отправка приветственного сообщения клиенту  wsClient.send('Привет');wsClient.on('message', function(message) {    /* обработчик сообщений от клиента */  }wsClient.on('close', function() {    // отправка уведомления в консоль    console.log('Пользователь отключился');  }}

На событие close сервер выведет в консоль уведомление.

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

Формат JSON команд от клиента:

{  action: 'ECHO' | 'PING',  data?: string // необязательный параметр}

Как видно из формата, сервер будет принимать две команды:

  • echo-запрос, в ответ на который сервер отправит содержимое data

  • ping, в ответ сервер отправит pong

  • если команда не известна, сервер выведет в консоль уведомление "Неизвестная команда"

Содержимое обработчика сообщений от клиента:

try {  // сообщение пришло текстом, нужно конвертировать в JSON-формат  const jsonMessage = JSON.parse(message);  switch (jsonMessage) {    case 'ECHO':      wsClient.send(jsonMessage.data);      break;    case: 'PING':      setTimeout(function() {        wsClient.send('PONG');      }, 2000);      break;    default:      console.log('Неизвестная команда');      break;  }} catch (error) {  console.log('Ошибка', error);}

Как вы уже видите, на команду PING сервер не сразу ответит, а подождёт 2 секунды.

Добавим в конце файла server.js строку, которая выведет в консоль информацию, на каком порту запущен сервер.

console.log('Сервер запущен на 9000 порту');

Запуск сервера

Для запуска сервера необходимо перейти в консоль и ввести команду:

node server.js

Сервер доступен локально по адресу ws://localhost:9000. Остановить сервер можно сочетанием клавиш:

  • Для Windows и Linux (Ctrl + C)

  • Для MacOs (Cmd + C)

Если хотите проверить работу сервера с другого устройства в рамках локальной сети, то откройте ещё одно окно консоли и запустите команду

для Window:

ipconfig

для Linux и MacOS:

ifconfig

В моём случае локальный адрес 192.168.0.15, значит из локальной сети сервер будет доступен по адресу ws://192.168.0.15:9000.

Проверка работы сервера

Чтобы протестировать работу сервера, откроем любую страницу в браузере и нажмём клавишу F12. Откроется DevTools, перейдём в консоль браузера и скопируем следующий код:

const myWs = new WebSocket('ws://localhost:9000');// обработчик проинформирует в консоль когда соединение установитсяmyWs.onopen = function () {  console.log('подключился');};// обработчик сообщений от сервераmyWs.onmessage = function (message) {  console.log('Message: %s', message.data);};// функция для отправки echo-сообщений на серверfunction wsSendEcho(value) {  myWs.send(JSON.stringify({action: 'ECHO', data: value.toString()}));}// функция для отправки команды ping на серверfunction wsSendPing() {  myWs.send(JSON.stringify({action: 'PING'}));}

Запустите этот код. Далее в консоли браузера вызовите функцию wsSendPing:

wsSendPing()

Через 2 секунды сервер пришлёт ответ, и в консоли выведется:

Message: PONG

Вызовите функцию wsSendEcho, к примеру, с содержимым "Test!", и в консоли будет выведено:

Message: Test!

Вот и всё! Кому понравилось, ставьте Like, подписывайтесь. Всем Добра!

Ссылка на полный код GitHub

Подробнее..
Категории: Javascript , Node.js , Node , Websocket , Websocket server

NEST.JS. Работа с ошибками. Мысли и рецепты

14.03.2021 10:05:42 | Автор: admin

Холивар...

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

  • Некоторая... академичность. Разобрано много и интересно, но заканчивается всё стандартным: "ваш выбор зависит от вашей ситуации".

  • Абсолютно отсутствуют упоминания о бюджете. Никто же не будет спорить, что теоретически мерседес лучше, чем восьмёрка по всем показателям кроме.. цены.

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


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

Стартовые условия.

Выделим основные: язык, фреймворк, тип приложения. Раскроем кратко каждый пункт:

ЯЗК

Платформа и ЯП, очень сильно влияют на выбор подходов по работе с ошибками.

К примеру, в go не стоит вопрос, использовать ли исключения - там их нет. В функциональных языках, в частности в F#, было бы очень странно не использовать монады или discriminated union'ы (возврат одного из нескольких возможных типов значений), т. к. это там это реализовано очень удобным и естественным образом. В C#, монады тоже можно сделать, но получается намного больше букв. А это не всем нравится, мне например - не очень. Правда, последнее время всё чаще упоминается библиотека https://www.nuget.org/packages/OneOf/, которая фактически добавляет в язык discriminated union'ы.

А к чему нас подталкивает javascript/typescript?... К анархии! Можно много за что ругать JS и вполне по делу, но точно не за отсутствие гибкости.

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

ФРЕЙМВОРК

С nestjs уже интереснее. Выброс исключений из прикладного кода предлагается нам в документации как основной механизм возврата неуспешных ответов. То есть, если взять обычное http приложение, то чтобы клиенту вернулся статус 404 нам надо бросить NotFoundException..

На самом деле, довольно спорная концепция. И это можно обойти, причём разными способами. Убеждённые сторонники монад вполне могут делать что-то такое:

@Controller()class SomeController {  @Post()  do (): Either<SomeResult, SomeError> {    ...  }}

Для этого, правда придётся написать кое-какой обвязочный код, но можно. Мы не стали.

Важно также, что Фреймворк делает практически всё для того, чтобы нам не приходилось заботиться об устойчивости процесса приложения . Nest сам выстраивает для нас "конвейер" обработки запроса и оборачивает всё это в удобный глобальный "try/catch", который ловит всё.

Правда иногда случаются казусы

Например в одной из старых версий nest'а мы столкнулись с тем, что ошибка, вылетевшая из функции переданной в декоратор @Transform() (из пакета class-transformer) почему-то клала приложение насмерть. В версии 7.5.5 это не воспроизводится, но от подобных вещей, конечно никто не застрахован.

ТИП ПРИЛОЖЕНИЯ

Самое важное. Мы не пишем софт для спутников. Там вряд ли можно было бы себе позволить что-то в стиле "сервис временно недоступен, попробуйте позже". Для нас же - это вполне ожидаемая ситуация. Нежелательная, конечно, но и не фатальная.

Мы пишем веб-сервисы. Есть http-сервисы, есть rpc (на redis и RabbitMQ, смотрим в сторону gRPC), гибридные тоже есть. В любом случае, мы стараемся внутреннюю логику приложения абстрагировать от транспорта, чтобы в любой момент можно было добавить новый.

Мы фокусируемся на том, что у нас есть запрос, есть его обработчик и есть ответ (который иногда void). И мы допускаем, что обработка запроса может по каким-то причинам оказаться неудачной. В этом случае, либо запрос будет повторён (успешно), либо будет зафиксирован и затем исправлен баг.

При таком подходе, важно, чтобы ошибки не могли привести данные в неконсистентное состояние. Помогают нам в этом две вещи:

  • Транзакционность. То есть, либо получилось всё, либо не получилось ничего.

  • Идемпотентность. Повторное выполнение одной и той же команды не ломает и не меняет состояние системы.

Транзакции (особенно распределённые) и идемпотентность выходят за рамки данной статьи. Но во многом, эти вещи являются основой надёжности.

Ближе к делу.

Наши принципы обработки ошибок базируются на следующих соглашениях:

КОНФИГУРАЦИЯ ПРИЛОЖЕНИЯ.

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

@Injectable()export class SomeModuleConfig {  public readonly someUrl: URL;public readonly someFile: string;public readonly someArrayOfNumbers: number[];  constructor (source: ConfigurationSource) {    // Бросит ConfigurationException если не удастся распарсить Url. Можно    // также проверять его доступность, например, при помощи пакета is-reachable    this.someUrl = source.getUrl('env.SOME_URL');// Бросит ConfigurationException если файл не существует или на него нет прав.this.someFile = source.getFile('env.SOME_FILE_PATH');// Бросит ConfigurationException если там не перечисленные через запятую числаthis.someArrayOfNumbers = source.getNumbers('env.NUMBERS')  }}

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

Подход к валидации

Мы написали свои валидаторы. Их преимущетсво в том, что мы не только валидируем данные, но в ряде случаев, можем сделать дополнительные проверки (доступность файла или удалённого ресурса, как в примере выше, например).

Однако, вполне можно использовать joi или json-схемы (может ещё есть варианты) - кому что больше нравится.

Неизменным должно быть одно - всё валидируется на старте.

УРОВНИ АБСТРАКЦИИ.

Мы максимально чётко разделяем бизнес-код и инфраструктурный код. И всё инфраструктурное выносим в библиотеки. Более менее очевидно, но всё же приведу пример:

// Задача: скачать файл по ссылке.const response = await axios.get(url, { responseType: 'stream' });const { contentType, filename } = this.parseHeaders(response);const file = createWriteStream(path);response.data.pipe(file);file.on('error', reject);file.on('finish', () => resolve({ contentType, filename, path }));

Такому коду не место не только в бизнес-логике, но вообще в приложении. В нём нет ничего уникального, привязывающего его к какому-то контексту. Ему место в библиотеке, скажем в классе NetworkFile. Вызывающий же код может выглядеть примерно так:

const file: NetworkFile = await NetworkFile.download('https://download.me/please', {  saveAs: 'path/to/directory'});

Фактически, мы заворачиваем в подобные переиспользуемые "смысловые" абстракции почти все нативные нодовские вызовы и вызовы сторонних библиотек. Стратегия обработки ошибок в этих обёртках: "поймать -> завернуть -> бросить". Пример простейшей реализации такого класса:

export class NetworkFile {private constructor (  public readonly filename: string,    public readonly path: string,    public readonly contentType: string,    public readonly url: string  ) {}    // В примере выше у нас метод download принимает вторым аргументов объект опций  // Таким образом мы можем кастомизировать наш класс: он может записывать файл на диск  // или не записывать, например.  // Но тут для примера - самая простая реализация.  public static async download (url: string, path: string): Promise<NetworkFile> {    return new Promise<NetworkFile>(async (resolve, reject) => {      try {      const response = await axios.get(url, { responseType: 'stream' });        const { contentType, filename } = this.parseHeaders(response);        const file = createWriteStream(path);        response.data.pipe(file);// Здесь мы отловим и завернём все ошибки связанную с записью данных в файл.        file.on('error', reject(new DownloadException(url, error));        file.on('finish', () => {        resolve(new NetworkFile(filename, path, contentType, url));        })    } catch (error) {        // А здесь, отловим и завернём ошибки связанные с открытием потока или скачиванием        // файла по сети.        reject(new DownloadException(url, error))      }    });  }private static parseHeaders (    response: AxiosResponse  ): { contentType: string, filename: string } {    const contentType = response.headers['content-type'];    const contentDisposition = response.headers['content-disposition'];    const filename = contentDisposition// parse - сторонний пакет content-disposition      ? parse(contentDisposition)?.parameters?.filename as string      : null;    if (typeof filename !== 'string') {      // Создавать здесь специальный тип ошибки нет смысла, т. к. на уровень выше      // она завернётся в DownloadException.      throw new Error(`Couldn't parse filename from header: ${contentDisposition}`);    }    return { contentType, filename };  }}
Promise constructor anti-pattern

Считается не круто использовать new Promise() вообще, и async-коллбэк внутри в частности. Вот и вот - релевантные посты на stackoverflow по этому поводу.

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

Уследить за потоком управления в таком маленьком классе (на самом деле, его боевая версия лишь немногим больше) - не проблема. А в итоге, вызывающий код работает только с одним типом исключений: DownloadException, внутрь которого завёрнута причина, по которой файл скачать не удалось. И причина носит исключительно информативный характер и не влияет на дальнейшую работу приложения, т. к.:

В БИЗНЕС-КОДЕ НИГДЕ НЕ НАДО ПИСАТЬ TRY / CATCH.

Серьёзно, о таких вещах, как закрытие дескрипторов и коннектов не должна заботиться бизнес-логика! Если вам прям очень надо написать try / catch в коде приложения, подумайте.. либо вы пишете то, что должно быть вынесено в библиотеку. Либо.. вам придётся объяснить товарищам по команде, почему именно здесь необходимо нарушить правило (хоть и редко, но такое всё же бывает).

Так почему не надо в сервисе ничего ловить? Для начала:

ЧТО М СЧИТАЕМ ИСКЛЮЧИТЕЛЬНОЙ СИТУАЦИЕЙ?

Откровенно говоря, в этом месте мы сломали немало копий. В конце концов, копья кончились, и мы пришли к концепции холивар-agnostic. Зачем нам отвечать на этот провокационный вопрос? В нём очень легко утонуть, причём мы будем не первыми утопленниками )

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

Не смогли считать файл - до свиданья. Не смогли распарсить ответ от стороннего API - до свидания. В базе duplicate key - до свидания. Не можем найти указанную сущность - до свидания. Максимально просто. И механизм throw, даёт нам удобную возможность осуществить этот быстрый выход без написания дополнительного кода.

В основном исключения ругают за две вещи:

  • Плохой перформанс. Нас это не очень волнует, т. к. мы не highload. Если он нас всё же в какой-то момент настигнет, мы, пересмотрим подходы там, где это будет реально критично. Сделаем бенчмарки... Хотя, готов поспорить, оверхед на исключения будет не главной нашей проблемой.

  • Запутывание потока управления программы. Это как оператор goto который уже давно не применяется в высокоуровневых программах. Вот только в нашем случае, goto бывает только в одно место - к выходу. А ранний return из функции - отнють не считается анти-паттерном. Напротив - это очень широко используемый способ уменьшить вложенность кода.

ВИД ОШИБОК

Говорят, что надо обрабатывать исключения там, где мы знаем, что с ними делать. В нашем слое бизнес-логики ответ будет всегда один и тот же: откатить транзакцию, если она есть (автоматически), залогировать всё что можно залогировать и вернуть клиенту ошибку. Вопрос в том, какую?

Мы используем 5 типов рантайм-исключений (про конфигурационные уже говорил выше):

abstract class AuthenticationException extends Exception {  public readonly type = 'authentication';}abstract class NotAllowedException extends Exception {public readonly type = 'authorization';}abstract class NotFoundException extends Exception {  public readonly type = 'not_found';}abstract class ClientException extends Exception {  public readonly type = 'client';}abstract class ServerException extends Exception {  public readonly type = 'server';}

Эти классы семантически соответствуют HTTP-кодам 401, 403, 404, 400 и 500. Конечно, это не вся палитра из спецификации, но нам хватает. Благодаря соглашению, что всё, что вылетает из любого места приложения должно быть унаследовано от указанных типов, их легко автоматически замапить на HTTP ответы.

А если не HTTP? Тут надо смотреть конкретный транспорт. К примеру один из используемых у нас вариантов подразумевает получения сообщения из очереди RabbitMQ и отправку ответного сообщения в конце. Для сериализации ответа мы используем.. что-то типа either:

interface Result<T> {data?: T;  error?: Exception}

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

Базовый класс Exception выглядит примерно так:

export abstract class Exception {  abstract type: string;  constructor (    public readonly code: number,    public readonly message: string,    public readonly inner?: any  ) {}toString (): string {    // Здесь логика сериализации, работа со стек-трейсами, вложенными ошибками и проч...  }}
Коды ошибок

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

  • Бывает такое, что клиентское приложение должно предпринять различные действия в зависимости от пришедшей от сервера ошибки. С кодами мы можем решить это не добавляя новых http статусов и без, прости Господи, парсинга сообщений.

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

Насколько это всё нужно и полезно - жизнь покажет

Поле inner - это внутренняя ошибка, которая может быть "завёрнута" в исключение (см. пример с NetworkFile).

Реализации абстрактных дочерних классов содержат в себе только значение поля type. Это удобно для сериализации, но можно обойтись и без него. В буквальном смысле - тип ради типа.

ПРИМЕР ИСПОЛЬЗОВАНИЯ

Опустим AuthenticationException - он используется у нас только в модуле контроля доступа. Разберём более типовые примеры и начнём ошибок валидации:

import { ValidatorError } from 'class-validator';// ....export interface RequestValidationError {  // Массив - потому что ошибка может относиться к нескольким полям.  properties: string[];  errors: { [key: string]: string };nested?: RequestValidationError[]}// Небольшая трансформация стандартной ошибки class-validator'а в более удобный// "наш" формат.const mapError = (error: ValidationError): RequestValidationError => ({  properties: [error.property],  errors: error.constraints,  nested: error.children.map(mapError)});// Сами цифры не имеют значения.export const VALIDATION_ERROR_CODE = 4001;export class ValidationException extends ClientException {  constructor (errors: ValidationError[]) {    const projections: ValErrorProjection[] = ;    super(      VALIDATION_ERROR_CODE,      'Validation failed!',      errors.map(mapError)    );  }}

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

app.useGlobalPipes(new ValidationPipe({  exceptionFactory: errors => new ValidationException(errors);   });)

Соответственно, на выходе наш ValidationException замапится на BadRequestException с кодом 400 - потому что он ClientException.

Другой пример, с NotFoundException:

export const EMPLOYEE_NOT_FOUND_ERROR_CODE = 50712;export class EmployeeNotFoundException extends NotFoundException {  constructor (employeeId: number) {  super(      EMPLOYEE_NOT_FOUND_ERROR_CODE,      `Employee id = ${employeeId} not found!`    );  }}

Не очень приятно писать такие классы - всегда возникает лёгкое чувство... бойлерплейта ) Но зато, как приятно их потом использовать!

// Вместо, что не даст нам ни кодов, ни типа - ничего:throw new Error('...тут мы должны сформировать внятное сообщение...')// Простоthrow new EmployeeNotFoundException(id);

Сценарий использования NotAllowedException похож на предыдущий. Пользовать может иметь доступ к роуту getEmployeeById, но не иметь права запрашивать определённые категории работников. Соответственно, мы в сервисе можем проверить его доступ и выкинуть ошибку такого вида:

export const EMPLOYEE_NOT_ALLOWED_ERROR_CODE = 40565;export class EmployeeNotAllowedException extends NotAllowedException {  constructor (userId: number, employeeId: number) {  super(      EMPLOYEE_NOT_ALLOWED_ERROR_CODE,      `User id = ${userId} is not allowed to query employee id = ${employeeId}!`    );  }}

Ну и наконец, все ошибки, в которых клиент не виноват: он имеет нужный доступ, запросил существующий ресурс и нигде не напутал с полями, и всё-таки облом - значит ServerException. Здесь я даже не буду особо приводить примеров - их множество. Но для клиента они всё одно - ошибка на сервере.

МАППИНГ

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

export interface IExceptionsFormatter {  // Verbose - флаг, который мы держим в конфигурации. Он используется для того,  // чтобы в девелоперской среде всегда на клиент отдавалась полная инфа о  // ошибке, а на проде - нет.  format (exception: unknown, verbose: boolean): unknown;    // При помощи этого метода можно понять, подходит ли данных форматтер  // для этого типа приложения или нет.  match (host: ArgumentsHost): boolean;}@Module({})export class ExceptionsModule {  public static forRoot (options: ExceptionsModuleOptions): DynamicModule {    return {      module: ExceptionsModule,      providers: [        ExceptionsModuleConfig,        {          provide: APP_FILTER,          useClass: GlobalExceptionsFilter        },        {          provide: 'FORMATTERS',          useValue: options.formatters        }      ]    };  }}const typesMap = new Map<string, number>().set('authentication', 401).set('authorization', 403).set('not_found', 404).set('client', 400).set('server', 500);@Catch()export class GlobalExceptionsFilter implements ExceptionFilter {  constructor (    @InjectLogger(GlobalExceptionsFilter) private readonly logger: ILogger,    @Inject('FORMATTERS') private readonly formatters: IExceptionsFormatter[],    private readonly config: ExceptionsModuleConfig  ) { }  catch (exception: Exception, argumentsHost: ArgumentsHost): Observable<any> {    this.logger.error(exception);    const formatter = this.formatters.find(x => x.match(argumentsHost));    const payload = formatter?.format(exception, this.config.verbose) || 'NO FORMATTER';    // В случае http мы ставим нужный статус-код и возвращаем ответ.if (argumentsHost.getType() === 'http') {      const request = argumentsHost.switchToHttp().getResponse();      const status = typesMap.get(exception.type) || 500;      request.status(status).send(payload);      return EMPTY;    }// В случае же RPC - бросаем дальше, транспорт разберётся.    return throwError(payload);  }}

Бывает конечно, что мы где-то напортачили и из сервиса вылетело что-то не унаследованное от Exception. На этот случай у нас есть ещё интерцептор, который все ошибки, не являющиеся экземплярами наследников Exception, заворачивает в new UnexpectedException(error) и прокидывает дальше. UnexpectedException естественно наследуется от ServerException. Для нас возникновение такой ошибки - иногда некритичный, но всё же баг, который фиксируется и исправляется.


В принципе, это всё. Для 95% наших задач этого вполне хватает. Способ может и не "канонический", но удобный и вполне рабочий - то, к чему мы и стремились.

И всё же бывают ситуации

КОГДА ВСЁ НЕ ТАК ЯСНО.

Приведу два примера:

Первый. Операции допускающие частичный успех. Мы стараемся избегать таких вещей, но как избежать, если нам нужно реализовать загрузку данных из csv-файла а бизнес требует, чтобы при возникновении ошибок в отдельных строках, остальные всё же были загружены?

В таких случаях всё-таки приходится проявлять "фантазию", например, обернуть в try/catch обработку каждой строки csv-файла. И в блоке catch писать ошибку в "отчёт". Тоже не бином Ньютона )

Второй. Я сознательно не написал выше реализацию DownloadException.

export class DOWNLOAD_ERROR_CODE = 5506;export class DownloadException extends ServerException {  constructor (url: string, inner: any) {    super(      DOWNLOAD_ERROR_CODE,      `Failed to download file from ${url}`,      inner    );  }}

Почему ServerException? Потому что, в общем случае, клиенту всё равно почему сервер не смог куда-то там достучаться. Для него это просто какая-то ошибка, в которой он не виноват.

Однако, теоретически может быть такая ситуация, что мы пытаемся скачать файл по ссылке, предоставленной клиентом. И тогда, в случае неудачи, клиент должен получить 400 или может быть 404, но не 500.

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

ЗАКЛЮЧЕНИЕ

Большое спасибо всем, кто дочитал до конца. Не думаю, что я в этой статье кому-то "открыл Америку". И я, конечно, далёк от навязывания кому-либо, чего-либо. И всё же, надеюсь, что эта попытка структурировать работу с ошибками для кого-то послужит отправной точкой в разработке собственного подхода под собственные задачи.

P. S. К сожалению, опенсорс в нашей компании в процессе согласования, поэтому привести реальный используемый код я не могу. Когда будет такая возможность, мы выложим на github библиотеку, при помощи которой работаем с исключениями. А за одно и некоторые другие пакеты, которые могут оказаться кому-то полезными.

P. P. S. Буду очень благодарен, за комментарии, особенно если в них будут практические примеры ситуаций, в которых у нас могут возникнуть трудности при использовании данного подхода. С удовольствием, поучаствую в обсуждении.

Подробнее..

Javascript and Rocket Science

25.03.2021 08:15:07 | Автор: admin

Почему?

Я уже два с половиной года пишу на Js, и почти повсюду вижу примеры решения типовых задач для этого языка . Отрисовать очередную онлайн-витрину, подать асинхронный запрос на бек за очередным JSON. Уровнем повыше - построить структуру данных для SPA и на ее основе отобразить визуальные компоненты. Но в Js есть полноценная библиотека математических функций, он обеспечивает неплохую для скриптового языка скорость вычислений (здесь сравнение производительности по бенчмаркам Питона vs Node ), так что почему бы не попытаться сделать еще один шаг и не решить с его помощью какую-нибудь инженерную задачу. Например - из космической области.

Постановка задачи

Моделируется движение летательного аппарата в центральном поле тяготения, Земля - шар радиусом в 6.3711E+6 м, параметр гравитационного колодца - 0.398E+15 Н * м2/кг рассматривается движение материальной точки (в следующих итерациях может появиться анализ движения вокруг центра масс), управление ЛА осуществляется через изменение программного угла атаки и регулирование тяги двигателя.

Сила лобового сопротивления всегда направлена против вектора скорости ЛА, подъемная сила - по нормали к вектору скорости. Атмосферу определим кусочно-линейной интерполяцией из Стандартной Атмосферы ГОСТ 4401-81.

Обыкновенные дифференциальные уравнения движения точки в центральном поле тяготения интегрируются методом Эйлера-Коши второго порядка точности (он же - усовершенствованный метод Эйлера).

Заглянуть на текущее состояние проекта можно здесь: satMod на гитхабе.

How to use?

Вызвать консоль и указать ей node launcher.js

Спустя некоторое время в консоли появится сообщение о проведенном расчете с указанием потраченного в миллисекундах времени. А рядом появится csv-файл с результатами. Расшифровка следующая - время(с), скорость (м/с), высота (м), пройденный путь (м), масса (кг), продольные и поперечные ускорения от внешних сил (кроме силы тяжести, м/с2), расход массы (кг), число M, скоростной напор (Па), координаты радиус-вектора (X, Y, м) и вектора скорости (Vx, Vy, м/с) в глобальной системе координат, связанной с центром Земли (или любой другой выбранной планеты)

К сожалению, пока что обрабатываемый вариант и условия окончания интегрирования жестко забиты в launcher.js, так что если хочется посчитать что-то кроме дефолтного варианта, то придется вносить правки в launcher

Зачем нужна такая модель?

Можно оценить параметры выведения, когда точности метода сумм скоростей Циолковского с вычетом потерь на гравитацию, сопротивление и неоптимальный режим ДУ уже недостаточно. Можно оценивать аэродинамические маневры в атмосфере - аэроторможение, планирование и глиссирование (Зенгер-стайл) на входе в атмосферу, а затем - и тепловые нагрузки на обшивку летательного аппарата. Или рассчитывать сбор гелия-3 скиммером с ядерно-прямоточным двигателем в верхних слоях атмосферы Урана с последующим подъемом до танкера на низкой орбите.

Компоненты модели

Основная магия модели происходит в модулях vehicleStage и compositeVehicle. Первый заведует поведением отдельной ступени ЛА и вычислением производных для системы ОДУ баллистической задачи, второй - комбинирует несколько ступеней в единое целое и управляет схемой действия ЛА

1. класс vehicleStage, метод формирования производных
2. класс compositeVehicle. Управление данными многоступенчатого ЛА

Проверка возможностей. "Гагаринский кейс"

Начнем с самого простого кейса - круговой околоземной орбиты. Для высоты в 200 км первая космическая скорость составит 7788.14 м/с, а период обращения - 5301 с. Пренебрежем влиянием атмосферы и запустим программу исходными данными V = 7788 м/с, Th = 0.00 град, H = 200000 м и посмотрим, куда после 5300 секунд интегрирования прилетит наш спутник. Поскольку продолжительность интегрирования велика, а порядок метода интегрирования - второй, то ошибки будут неизбежны, однако они не исказят качественно картину решения

Начальная точка траектории

t,c

V, м/с

Th, град

H, км

L, км

X, м

Y, м

0

7788.1

0.00

200

0

0

6571100

0.5

7788.1

0.00

200

0.38

3894

6571099

Конечная точка (без атмосферы)

t,c

V, м/с

Th, град

H, км

L, км

X, м

Y, м

5301.0

7788.1

0.00

200

0

-2428

6571100

5301.5

7788.1

0.00

200

0.14

1466

6571100

Конечная точка (атмосфера)

t, с

V, м/с

Th, град

H, км

L, км

X, м

Y, м

5300.5

7788.6

0.00

199.25

40028

-2787

6570349

5301.0

7788.6

0.00

199.25

1.07

1107

6570349

В идеальной "безатмосферной" постановке задачи спутник прошел полный круг и вернулся в начальную точку с той же высотой и скоростью в пределах десятых долей секунды от теоретической цифры для круговой орбиты. В "атмосферной" постановке остаточный скоростной напор за 5300 секунд чуть-чуть (на 750 м) снизил орбиту. Так что еще одна область применения - быстрая оценка затухания орбиты, в том числе через управление площадью и ориентацией спутника. Надувные баллюты, паруса, баллоны вроде ретрансляторов Echo.

Возвращение на Землю

Усложним задачу и введем тормозной импульс спустя 500 секунд полета, после чего посмотрим, как наш ИСЗ начнет погружение в атмосферу (этот вариант исходных данных значится как simple_sat.json среди ИД). Небольшой ракетный двигатель с удельным импульсом в 255 с (пусть это тормозной РДТТ) и запасом топлива в 150 кг сбросит нам 263 м/с

Пик торможения, максимальные перегрузки (~ 10g)

t, с

V, м/с

Th, град

H, км

L, км

aX, м/c2

Q, Па

1255.3

3947.1

-5.36

37.4

9278

-98.53

49271

1255.8

3898.2

-5.42

37.21

9278

-98.62

49310

1256.3

3849.4

-5.47

37.03

9281

-98.6

49299

Торможение почти завершено. Дозвук

t, с

V, м/с

Th, град

H, км

L, км

aX, м/c2

Q, Па

1361.8

168.6

-80.06

9.96

9385

-11.82

5907

1445.3

91.4

-90.00

-0.009

9385

-10.24

5114

Проверим адекватность расчет сопротивления и сравним полученную скорость с аналитическим значением установившейся скорости (m = 1100 кг, Cxa = 0.8, S = 2.75 м2, Ro = 1.15 кг/м3)

v=\sqrt{\frac{mg}{0.5RoCxaS}}

Установившаяся скорость падения для высоты 0 ~ 1000м составит 90.7 - 92.4 м/с, что хорошо согласуется с полученной численным интегрированием скоростью снижения в конце участка снижения. На весь расчет от начала витка до спуска ушло 57 миллисекунд.

Выводы: Модель позволяет рассчитывать движение ИСЗ в центральном поле тяготения с учетом аэродинамического торможения и работы ДУ ИСЗ. Хорошим направлением развития будет оценка управляемого входа в атмосферу Земли с использованием подъемной силы, а также расчет аэроторможения в атмосфере другой планеты (Марс?).

Но это уже в следующих выпусках.

Подробнее..

Модуль для работы с sqlite3

07.05.2021 16:08:17 | Автор: admin

Сегодня Я хотел бы поделиться своей не большой разработкой, которая помогает мне уже более чем пол года: "Модуль для работы с sqlite3".

Концепция

Вместо написания SQL запросов мы будем передавать ключи, значения, названия таблиц, условия и callback'и, которые будут вызывать по завершению запросов(в каждый callback мы будем передавать ошибку и результат, если такой есть).

Представим модуль в виде класса.

Всего будет 4 метода:

  1. getData() - для получения данных из таблицы.

  2. insertData() - для добавления данных в таблицу.

  3. updateData() - для обновления данных в таблице.

  4. deleteData() - для удаления данных из таблицы.

Конечно же с помощью 4 методов приведенных выше мы не сможем исключить все виды запросов, но в моем случаи эти запросы самые частые.

Кодим

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

class DataBase {    /**     *      * @readonly     */    static sqlite3 = require('sqlite3').verbose();        /**    *     * @readonly    */   static database = new this.sqlite3.Database('./database/database.db');    static ToString(value) {        return typeof(value) === 'string' ? '\'' + value + '\'' : value;    }}module.exports = {    database: DataBase};

Для начало напишем метод, который будет возвращать данные из таблицы, не обязательно, но мы будем учитывать, кол-во строк, которые нам будут возвращаться(одна или несколько).

Начинаем собирать запрос с ключевого слова "SELECT", далее перебираем все столбцы, которые нам передали и добавляем их, но не забываем, что символ "*" не нужно "заковывать" в кавычки, т к этот символ говорит нам, что нужно получить все столбцы. Заканчиваем собор указанием таблицы и условия. Далее, исходя из кол-во строк, которые нам надо вернуть вызываем соответствующий метод.

class DataBase {    /**     *      * @readonly     */    static sqlite3 = require('sqlite3').verbose();        /**    *     * @readonly    */   static database = new this.sqlite3.Database('./database/database.db');        /**     *      * @param {String[]} keys      * @param {String} table      * @param {String} condition      * @param {Boolean} some      * @param {Function()} callback      */    static getData(keys, table, condition = '', some = true, callback = () => {}) {        let sql = 'SELECT ';        for (let i = 0; i < keys.length; i++) {            sql += keys[i] === '*' ? keys[i] : '`' + keys[i] + '`';            if (keys.length > i + 1)                sql += ', ';        }        sql += ' FROM `' + table + '` ' + condition;                if (some)            this.database.all(sql, (err, rows) => {                callback(err, rows);            });        else            this.database.get(sql, (err, row) => {                callback(err, row);            });    };    static ToString(value) {        return typeof(value) === 'string' ? '\'' + value + '\'' : value;    }}module.exports = {    database: DataBase};

Напишем метод отвечающий за обновление данных.

Начинаем с указания таблицы, установкой ключей и значений, а завершаем добавлением условия.

class DataBase {    /**     *      * @readonly     */    static sqlite3 = require('sqlite3').verbose();        /**    *     * @readonly    */   static database = new this.sqlite3.Database('./database/database.db');        /**     *      * @param {String[]} keys      * @param {String} table      * @param {String} condition      * @param {Boolean} some      * @param {Function()} callback      */    static getData(keys, table, condition = '', some = true, callback = () => {}) {        let sql = 'SELECT ';        for (let i = 0; i < keys.length; i++) {            sql += keys[i] === '*' ? keys[i] : '`' + keys[i] + '`';            if (keys.length > i + 1)                sql += ', ';        }        sql += ' FROM `' + table + '` ' + condition;                if (some)            this.database.all(sql, (err, rows) => {                callback(err, rows);            });        else            this.database.get(sql, (err, row) => {                callback(err, row);            });    };        /**     *      * @param {String[]} keys      * @param {Values[]} values      * @param {String} table      * @param {String} condition      * @param {Function()} callback      */    static updateData(keys, values, table, condition, callback = () => {}) {        let sql = 'UPDATE `' + table + '` SET ';        for (let i = 0; i < keys.length; i++) {            sql += '`' + keys[i] + '` = ' + this.ToString(values[i]);            if (keys.length > i + 1)                sql += ', ';        }        sql += ' ' + condition;                this.database.run(sql, (err) => {            callback(err);        });    }    static ToString(value) {        return typeof(value) === 'string' ? '\'' + value + '\'' : value;    }}module.exports = {    database: DataBase};

Остается совсем чуть-чуть, напишем метод для удаления данных(она максимально простой) и метод для добавления данных.

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

class DataBase {    /**     *      * @readonly     */    static sqlite3 = require('sqlite3').verbose();        /**    *     * @readonly    */   static database = new this.sqlite3.Database('./database/database.db');        /**     *      * @param {String[]} keys      * @param {String} table      * @param {String} condition      * @param {Boolean} some      * @param {Function()} callback      */    static getData(keys, table, condition = '', some = true, callback = () => {}) {        let sql = 'SELECT ';        for (let i = 0; i < keys.length; i++) {            sql += keys[i] === '*' ? keys[i] : '`' + keys[i] + '`';            if (keys.length > i + 1)                sql += ', ';        }        sql += ' FROM `' + table + '` ' + condition;                if (some)            this.database.all(sql, (err, rows) => {                callback(err, rows);            });        else            this.database.get(sql, (err, row) => {                callback(err, row);            });    };        /**     *      * @param {String[]} keys      * @param {Values[]} values      * @param {String} table      * @param {String} condition      * @param {Function()} callback      */    static updateData(keys, values, table, condition, callback = () => {}) {        let sql = 'UPDATE `' + table + '` SET ';        for (let i = 0; i < keys.length; i++) {            sql += '`' + keys[i] + '` = ' + this.ToString(values[i]);            if (keys.length > i + 1)                sql += ', ';        }        sql += ' ' + condition;                this.database.run(sql, (err) => {            callback(err);        });    }        /**     * @param {String[]} keys     * @param {String[]} values     * @param {String} table      * @param {Function()} callback      */    static insertData(keys, values, table, callback = () => {}) {        let sql = 'INSERT INTO `' + table + '` (';        for (let i = 0; i < keys.length; i++) {            sql += '`' + keys[i] + '`';            if (keys.length > i + 1)                sql += ', ';        }        sql += ') VALUES (';        for (let i = 0; i < values.length; i++) {            sql += this.ToString(values[i]);            if (values.length > i + 1)                sql += ', ';        }        sql += ')';        this.database.run(sql, (err) => {            callback(err);        });    };    /**     *      * @param {String} table      * @param {String} condition      * @param {Function()} callback      */    static deleteData(table, condition = '', callback = () => {}) {        this.database.run('DELETE FROM `' + table + '` ' + condition, (err) => {            callback(err);        });    }    static ToString(value) {        return typeof(value) === 'string' ? '\'' + value + '\'' : value;    }}module.exports = {    database: DataBase};

На этом все, спасибо за внимание!
Проект на GitHub

Подробнее..

Как написать пассивный доход Пишем качественного трейд бота на JS (часть 1)

12.06.2021 20:19:26 | Автор: admin

Начнем писать трейдинг бота, который будет работать на криптобирже Binance. Бот должен уметь:

  1. торговать самостоятельно, принося какой-то доход

  2. должен быть удобен для создания и обкатывания различных стратегий торговли

  3. тестировать стратегию на исторических данных

Пожалуй, начнем с архитектуры

У нас есть биржа Binance, у которой есть шикарное api. Поэтому архитектура могла бы выглядеть так:

Вызвать пару методов купи дешевле и продай дороже. Но задача для нас написать такого бота, при котором условный программист-трейдер сможет создавать и тестировать на прибыльность новые стратегии. Поэтому, необходимо отделить логику торговли от всего прочего. А также модулю логики должно быть все равно к какой бирже его подключили: к реальному API или к псевдо-API (для тестирования). С учетом всего этого получилась примерно вот такая архитектура:

Базу выбрал PostgreSQL. Тут нет никакого тайного умысла. Вы можете использовать любую.

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

Сервис для логов

Простой класс, который принимает на вход префикс для логирования и имеет два метода log и error. Эти методы печатают лог с текущим временем и перфиксом:

class LoggerService {  constructor(prefix) {    this.logPrefix = prefix  }  log(...props) {    console.log(new Date().toISOString().substr(0, 19), this.logPrefix, ...props)  }  error(...props) {    console.error(new Date().toISOString().substr(0, 19), this.logPrefix, ...props)  }}

Теперь подключим биржу

yarn add node-binance-api

Добавим класс BaseApiService. Сделаем в нем инициализацию Binance SDK, а также применим сервис LoggerService. Учитывая мой опыт с Binance могу сразу сказать, что в зависимости от торговой пары мы должны слать цену и обьем с разным количеством знаков после запятой. Все эти настройки для каждой пары можно взять, сделав запрос futuresExchangeInfo(). И написать методы для получения количества знаков после запятой для цены getAssetPricePrecision и объема getAssetQuantityPrecision.

class BaseApiService {  constructor({ client, secret }) {    const { log, error } = new Logger('BaseApiService')    this.log = log    this.error = error    this.api = new NodeBinanceApi().options({      APIKEY: client,      APISECRET: secret,      hedgeMode: true,    })    this.exchangeInfo = {}  }  async init() {    try {      this.exchangeInfo = await this.api.futuresExchangeInfo()    } catch (e) {      this.error('init error', e)    }  }  getAssetQuantityPrecision(symbol) {    const { symbols = [] } = this.exchangeInfo    const s = symbols.find(s => s.symbol === symbol) || { quantityPrecision: 3 }    return s.quantityPrecision  }  getAssetPricePrecision(symbol) {    const { symbols = [] } = this.exchangeInfo    const s = symbols.find(s => s.symbol === symbol) || { pricePrecision: 2 }    return s.pricePrecision  }}

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

async futuresOrder(side, symbol, qty, price, params={}) {  try {    qty = Number(qty).toFixed(this.getAssetQuantityPrecision(symbol))    price = Number(price).toFixed(this.getAssetPricePrecision(symbol))    if (!params.type) {      params.type = ORDER.TYPE.MARKET    }    const res = await this.api.futuresOrder(side, symbol, qty, price || false, params)    this.log('futuresOrder', res)    return res  } catch (e) {    console.log('futuresOrder error', e)  }}

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

class TradeService {  constructor({client, secret}) {    const { log, error } = new LoggerService('TradeService')    this.log = log    this.error = error    this.api = new NodeBinanceApi().options({      APIKEY: client,      APISECRET: secret,      hedgeMode: true,    })    this.events = new EventEmitter()  }  marginCallCallback = (data) => this.log('marginCallCallback', data)  accountUpdateCallback = (data) => this.log('accountUpdateCallback', data)  orderUpdateCallback = (data) => this.emit(data)  subscribedCallback = (data) => this.log('subscribedCallback', data)  accountConfigUpdateCallback = (data) => this.log('accountConfigUpdateCallback', data)  startListening() {    this.api.websockets.userFutureData(      this.marginCallCallback,      this.accountUpdateCallback,      this.orderUpdateCallback,      this.subscribedCallback,      this.accountConfigUpdateCallback,    )  }  subscribe(cb) {    this.events.on('trade', cb)  }  emit = (data) => {    this.events.emit('trade', data)  }}

При помощи метода из SDK this.api.websockets.userFutureData подписываемся на события из биржы. Самой главный колбек для нас this.orderUpdateCallback . Он вызывается каждый раз когда меняется статус у ордера. Ловим это событие и прокидываем через EventEmitter тому, кто на это событие подписался, используя метод subscribe.

Перейдем к базе данных

Для чего она нужна? В базе будем хранить все ордера, а также всю историю торговли бота. Пользователей с их ключами к бирже и балансами. В последствии сможем считать сколько бот принес прибыли/убытка. Тут останавливаться долго не буду. Подключаю sequlize.

yarn add sequelize-cli -Dyarn add sequelizenpx sequelize-cli init

Добавим docker-compose.yml файл для локальной базы:

version: '3.1'services:  db:    image: 'postgres:12'    restart: unless-stopped    volumes:      - ./volumes/postgresql/data:/var/lib/postgresql/data    environment:      POSTGRES_USER: root      POSTGRES_PASSWORD: example      POSTGRES_DB: bot    ports:      - 5432:5432    networks:      - postgresnetworks:  postgres:    driver: bridge

А также добавляю миграции и модели. User, Order

Продолжение следует.

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

Подробнее..

Как работает Middleware в Express?

16.06.2021 00:20:01 | Автор: admin

Статья переведена. Ссылка на оригинал

Эта статья представляет собой адаптированный отрывок из книги "Express API Validation Essentials". Она научит вас полноценной стратегии валидации API, которую вы можете начать применять в своих Express-приложениях уже сегодня.

__________________

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

  • Где правильное место для добавления этого middleware в мое приложение?

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

  • Почему важен порядок использования middleware?

  • Как я могу написать свой собственный код для обработки ошибок?

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

В этой статье мы подробно рассмотрим паттерн промежуточного ПО (middleware). Мы также рассмотрим различные типы middleware Express и то, как эффективно сочетать их при создании приложений.

Шаблон Middleware

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

Когда вы определяете маршрут в Express, функция-обработчик маршрута, которую вы указываете для этого маршрута, является функцией Middleware:

app.get("/user", function routeHandlerMiddleware(request, response, next) {    // execute something});

(Пример 1.1)

Middleware достаточно гибкое. Вы можете указать Express запускать одну и ту же функцию middleware для различных маршрутов, что позволит вам делать такие вещи, как общая проверка для разных конечных точек API.

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

Синтаксис Middleware

Вот синтаксис функции middleware:

/** * @param {Object} request - Express request object (commonly named `req`) * @param {Object} response - Express response object (commonly named `res`) * @param {Function} next - Express `next()` function */function middlewareFunction(request, response, next) {    // execute something}

(Пример 1.2)

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

Когда Express запускает функцию middleware, ей передаются три аргумента:

  • Объект запроса Express (обычно называемый req) - это расширенный экземпляр встроенного в Node.js класса http.IncomingMessage.

  • Объект ответа Express (обычно называемый res) - это расширенный экземпляр встроенного в Node.js класса http.ServerResponse.

  • Функция Express next() - После того как промежуточная функция выполнит свои задачи, она должна вызвать функцию next(), чтобы передать управление следующей промежуточной программе. Если вы передаете ей аргумент, Express принимает его за ошибку. Он пропустит все оставшиеся функции middleware, не обрабатывающие ошибки, и начнет выполнять middleware, которое обрабатывает ошибки.

  • Функции middleware не должны иметь значение return. Любое значение, возвращаемое промежуточным ПО, не будет использовано Express.

Два типа Middleware

Обычное промежуточное ПО (middleware)

Большинство функций Middleware, с которыми вы будете работать в приложении Express, являются тем, что я называю "простым" промежуточным ПО (в документации Express нет специального термина для них). Они выглядят как функция, определенная в приведенном выше примере синтаксиса middleware (пример 1.2).

Вот пример простой функции middleware:

function plainMiddlewareFunction(request, response, next) {    console.log(`The request method is ${request.method}`);    /**     * Ensure the next middleware function is called.     */    next();}

(Пример 1.3)

Middleware для обработки ошибок

  • Разница между middleware для обработки ошибок и обычным middleware заключается в том, что функции middleware для обработки ошибок задают четыре параметра вместо трех, т.е. (error, request, response, next).

Вот пример функции middleware для обработки ошибок:

function errorHandlingMiddlewareFunction(error, request, response, next) {    console.log(error.message);    /**     * Ensure the next error handling middleware is called.     */    next(error);}

(Пример 1.4)

Эта промежуточная функция обработки ошибок будет выполнена, когда другая промежуточная функция вызовет функцию next() с объектом ошибки, например.

function anotherMiddlewareFunction(request, response, next) {    const error = new Error("Something is wrong");    /**     * This will cause Express to start executing error     * handling middleware.     */    next(error);}

(Пример 1.5)

Использование middleware

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

  • Уровень маршрута

  • Уровень маршрутизатора

  • Уровень приложения

Если вы хотите, чтобы маршрут (или маршруты) обрабатывал ошибки, которые он вызывает, с помощью middleware для обработки ошибок, вы должны добавить его после определения маршрута.

Давайте рассмотрим, как выглядит настройка middleware на каждом уровне.

На уровне маршрута

Это самый конкретный уровень: любое промежуточное ПО, которое вы настроите на уровне маршрута, будет работать только для этого определенного маршрута.

app.get("/", someMiddleware, routeHandlerMiddleware, errorHandlerMiddleware);

(Пример 1.6)

На уровне маршрутизатора

Express позволяет создавать объекты Router. Они позволяют вам ограничить использование middleware определенным набором маршрутов. Если вы хотите, чтобы одно и то же middleware выполнялось для нескольких маршрутов, а не для всех, то такие объекты могут быть очень полезны.

import express from "express";const router = express.Router();router.use(someMiddleware);router.post("/user", createUserRouteHandler);router.get("/user/:user_id", getUserRouteHandler);router.put("/user/:user_id", updateUserRouteHandler);router.delete("/user/:user_id", deleteUserRouteHandler);router.use(errorHandlerMiddleware);

(Пример 1.7)

На уровне приложения

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

app.use(someMiddleware);// define routesapp.use(errorHandlerMiddleware);

(Пример 1.8)

Технически вы можете определить несколько маршрутов, вызвать app.use(someMiddleware), затем определить несколько других маршрутов, для которых вы хотите запустить someMiddleware. Я не рекомендую такой подход, поскольку он приводит к запутанной и трудноотлаживаемой структуре приложения.

Вы должны настраивать middleware на уровне приложения только в случае крайней необходимости, а именно, если его действительно нужно запускать для каждого маршрута в вашем приложении. Каждая функция middleware, независимо от того, насколько она мала, требует определенного времени для выполнения. Чем больше функций middleware необходимо запустить для маршрута, тем медленнее будут выполняться запросы к этому маршруту. Это действительно увеличивается по мере роста вашего приложения и конфигурации с большим количеством middleware. Старайтесь по возможности ограничивать middleware уровнями маршрута или маршрутизатора.

Подведение итогов

В этой статье мы узнали о шаблоне middleware в Express. Мы также узнали о различных типах middleware и о том, как их можно комбинировать при создании приложения на Express.

Если вы хотите прочитать больше о middleware, в документации Express есть несколько руководств:

__________________

Эта статья представляет собой адаптированный отрывок из книги "Express API Validation Essentials". Она научит вас полной стратегии валидации API, которую вы можете начать применять в своих Express-приложениях уже сегодня.

__________________

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

Статья переведена в преддверии старта курса "Node.js Developer". Всех, кто желает подробнее узнать о курсе и процессе обучения, приглашаем записаться на Demo Day курса, который пройдет 28 июня.

- ЗАПИСАТЬСЯ НА DEMO DAY КУРСА

Статья переведена. Ссылка на оригинал

Подробнее..

Strapi сохранение файлов на Яндекс Object Storage

14.03.2021 20:05:08 | Автор: admin

Возможно Вы уже пробовали Strapi и он Вам даже понравился настолько, что Вы решили сделать deploy на одном из серверов.

Например я делал deploy на VPS с Node, nginx, pm2. Поскольку VPS обычно обладает скудными возможностями по хранению чего либо - то хочется хранить все свое добро на одном из современных хранилищ.

Strapi разработало для нас коннектор для backet Amazon S3. Данный плагин уже включен в стандартный репозиторий strapi, ранее он фигурировал как plugin, разработанный сообществом.

Как подключить Amazon S3 bucket Вы можете посмотреть здесь:
ролик от Alex
(на сегодня конфигурационные файлы надо писать несколько по другому - но общая канва сохраняется).

В данной статье мы рассмотрим подключение Яндекс Object Storage. Для начала Вам конечно надо зарегистрироваться на Яндекс cloud. После этого в консоли управления выбираете Object Storage.

Нажимаете на желтую кнопку "Создать бакет" и заполните поля как показано на картинке - т.е. поставьте публичный доступ на чтение и на список объектов, класс хранилища - стандартное.

После того как Вы нажмете на кнопку "Создать бакет" - Вы увидите его в списке бакетов:

Дальше нам будет необходимо создать статический ключ для обращения к Яндекс из нашего сервера Strapi. Нажмите на иконку с Вашим облаком (на фото вверху выделил красной рамкой) (у Вас может быть несколько облаков) и выберите Ваш каталог - в моем случае каталог называется default - нажмите на него.

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

Нажмите на Ваш сервисный аккаунт - перед Вами откроется страница, на которой уже будет необходимая нам кнопка.

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

Сохраните эти два значения в блокнот - они нам понадобятся далее.

Запустите консоль на своем компьютере и введите команду
npx create-strapi-app strapi-yandex-cloud --quickstart

Через некоторое время Strapi установится в директории strapi-yandex-cloud и откроется окно браузера в которое Вы должны ввести данные администратора. После этого окно браузера можно закрыть.

Прекратите выполнение запущенного сервера, перейдите в директорию Вашего приложения - strapi-yandex-cloud и установите дополнительный плагин:
npm i -S strapi-provider-upload-aws-s3

Теперь остался последний шаг - создать конфигурационный файл плагина. Создайте файл сonfig/plugins.js (файл plugins.js в директории config) следующего содержания:

module.exports = ({ env })=>({  upload: {    provider: 'aws-s3',    providerOptions: {      endpoint: 'https://storage.yandexcloud.net',      accessKeyId: env('AWS_ACCESS_KEY_ID'),      secretAccessKey: env('AWS_ACCESS_SECRET'),      region: env('AWS_REGION'),      params: {        Bucket: env('AWS_BUCKET'),      },    },  },});

Осталось сделать последний шаг. В корне проекта создайте файл .env в котором запишите данные Вашего бакета:

HOST=0.0.0.0PORT=1337AWS_ACCESS_KEY_ID=pg2ywMziH_9zeZfA7t3wAWS_ACCESS_SECRET="aTiO354YNpnO9zKjqBiP1U3nm3F3CoXGLYcldZBC"AWS_REGION="ru-central1"AWS_BUCKET="strapi-backet-test"

Здесь strapi-backet-test - это название бакета, который Вы создали, два длинных ключа - это статический ключ доступа, который Вы создали на предыдущем шаге.

Собственно на этом процесс завершен!
Запускаете Ваш сервер - npm run develop.
Открываете в браузере strapi Media Library, загружаете туда какое либо изображение.

Далее открываем в cloud.yandex.ru наш вновь созданный бакет и видим:

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

В Media Library можно помещать не только изображения, но и видео, pdf а также любые другие файлы. Конечно же в Strapi есть готовый Rest API для записи файлов в Media Library.

P.S. Хочу поблагодарить Александра Власова, оказавшего неоценимую помощь в настройке конфигурации для Яндекс облака.

Подробнее..

Категории

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

  • Имя: Макс
    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