<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Anthonny Quérouil]]></title><description><![CDATA[Passionate developer :)]]></description><link>http://anthonnyquerouil.me</link><image><url>https://cloud.githubusercontent.com/assets/2006548/12876743/de1c3488-ce06-11e5-82aa-63de890f71be.jpg</url><title>Anthonny Quérouil</title><link>http://anthonnyquerouil.me</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 29 Sep 2025 11:48:30 GMT</lastBuildDate><atom:link href="http://anthonnyquerouil.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[How Workerify's Vite Plugin and Core Library Work Together: A Deep Dive]]></title><description><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>Building modern web applications with Service Workers can be complex. <a href="https://github.com/anthonny/workerify">Workerify</a>’s goal isn’t to simplify every aspect of Service Worker development, but rather to make it straightforward to create API-like endpoints directly in the browser. By providing a Fastify-like router that runs entirely inside a Service Worker, Workerify makes this specific use case both easy and powerful. In this post, we’ll explore <a href="https://github.com/anthonny/workerify">Workerify</a>’s architecture and how its Vite plugin and core library work hand in hand.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_architecture_overview">Architecture Overview</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Workerify is made of two main packages:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>@workerify/lib</strong>: the core routing library, handling request matching and processing</p>
</li>
<li>
<p><strong>@workerify/vite-plugin</strong>: a Vite plugin that manages Service Worker registration and build-time optimizations</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>These two communicate through the <strong>BroadcastChannel API</strong>, letting your app define routes while the Service Worker intercepts and handles HTTP traffic.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_the_journey_of_a_request">The Journey of a Request</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Let’s follow how a request flows through the system.</p>
</div>
<div class="sect2">
<h3 id="_1_initial_setup_the_vite_plugin">1. Initial Setup: The Vite Plugin</h3>
<div class="paragraph">
<p>When you add the Workerify plugin to your Vite config:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-ts" data-lang="ts">// vite.config.ts
import workerify from '@workerify/vite-plugin';

export default defineConfig({
  plugins: [workerify()]
});</code></pre>
</div>
</div>
<div class="paragraph">
<p>The plugin takes care of several things:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Generates a Service Worker file</strong> at <code>/workerify-sw.js</code></p>
</li>
<li>
<p><strong>Provides a virtual module</strong> (<code>virtual:workerify-register</code>) for easy registration</p>
</li>
<li>
<p><strong>Handles both dev and build modes</strong>, served from memory in dev, emitted as a build asset in prod</p>
</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="_2_application_initialization">2. Application Initialization</h3>
<div class="paragraph">
<p>In your app code, you register the Service Worker and define routes:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-ts" data-lang="ts">import { registerWorkerifySW } from 'virtual:workerify-register';
import Workerify from '@workerify/lib';

await registerWorkerifySW();

const app = new Workerify({ logger: true });

app.get('/todos', async () =&gt; JSON.stringify(todos));

app.post('/todos', async (request, reply) =&gt; {
  todos.push(request.body?.todo);
  reply.headers = { 'HX-Trigger': 'todos:refresh' };
});

await app.listen();</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_3_the_registration_dance">3. The Registration Dance</h3>
<div class="paragraph">
<p>When <code>app.listen()</code> runs:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>A unique <strong>consumer ID</strong> is created</p>
</li>
<li>
<p>The instance <strong>registers with the Service Worker</strong> at <code>/__workerify/register</code></p>
</li>
<li>
<p>Routes are <strong>broadcast</strong> via BroadcastChannel</p>
</li>
<li>
<p>The Service Worker <strong>acknowledges</strong> the registration</p>
</li>
</ol>
</div>
</div>
<div class="sect2">
<h3 id="_4_the_service_worker_traffic_controller">4. The Service Worker: Traffic Controller</h3>
<div class="paragraph">
<p>The Service Worker, generated by the plugin, maps clients to consumers and routes. When a <code>fetch</code> event occurs, it looks for a matching route and, if found, hands off processing to the right consumer through the channel.</p>
</div>
</div>
<div class="sect2">
<h3 id="_5_request_pipeline">5. Request Pipeline</h3>
<div class="olist arabic">
<ol class="arabic">
<li>
<p><strong>Interception</strong> — Service Worker catches the request</p>
</li>
<li>
<p><strong>Route Matching</strong> — Finds the right handler</p>
</li>
<li>
<p><strong>Broadcast</strong> — Sends request details to the consumer</p>
</li>
<li>
<p><strong>Processing</strong> — Consumer runs the handler</p>
</li>
<li>
<p><strong>Response</strong> — Returned via BroadcastChannel</p>
</li>
<li>
<p><strong>Delivery</strong> — Service Worker replies to the browser</p>
</li>
</ol>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_multi_tab_support">Multi-Tab Support</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Service Workers are shared across tabs. Workerify ensures isolation by giving each tab its own consumer ID and routes. Closing a tab automatically cleans up its routes, avoiding conflicts between tabs.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_the_communication_protocol">The Communication Protocol</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Workerify relies on a simple, well-defined protocol over BroadcastChannel:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Route registration</strong>:</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">{
  type: 'workerify:routes:update',
  consumerId, routes
}</code></pre>
</div>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Request handling</strong>:</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">{
  type: 'workerify:handle',
  id,
  consumerId,
  request
}</code></pre>
</div>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Response delivery</strong>:</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">{
  type: 'workerify:response',
  id,
  status,
  headers,
  body
}</code></pre>
</div>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Debugging</strong>:</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">{ type: 'workerify:routes:list' },
{ type: 'workerify:clients:list' }</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_build_time_optimizations">Build-Time Optimizations</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The Vite plugin improves both dev and production workflows:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p><strong>Pre-compiled templates</strong> for the Service Worker</p>
</li>
<li>
<p><strong>Virtual module generation</strong> for registration code</p>
</li>
<li>
<p><strong>Automatic base path handling</strong></p>
</li>
<li>
<p><strong>Memory serving in dev</strong> for faster updates</p>
</li>
</ol>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_why_this_architecture">Why This Architecture?</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Unlike traditional Service Worker setups, Workerify removes boilerplate and gives developers:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>A <strong>familiar API</strong> (Fastify-like)</p>
</li>
<li>
<p><strong>Zero network latency</strong>, requests handled in-browser</p>
</li>
<li>
<p><strong>SPA-friendly design</strong>, perfect with <a href="https://htmx.org">HTMX</a></p>
</li>
<li>
<p><strong>Smooth dev experience</strong>, hot reload and route updates without reinstalling the SW</p>
</li>
<li>
<p><strong>Full TypeScript support</strong></p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_performance_considerations">Performance Considerations</h2>
<div class="sectionbody">
<div class="ulist">
<ul>
<li>
<p><strong>Minimal overhead</strong> with BroadcastChannel</p>
</li>
<li>
<p><strong>Lazy route matching</strong> only when needed</p>
</li>
<li>
<p><strong>Automatic cleanup</strong> of closed tabs</p>
</li>
<li>
<p><strong>Memory efficiency</strong> by storing routes once in the Service Worker</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_practical_example_a_todo_app">Practical Example: A Todo App</h2>
<div class="sectionbody">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-ts" data-lang="ts">import { registerWorkerifySW } from 'virtual:workerify-register';
import Workerify from '@workerify/lib';
import htmx from 'htmx.org';

await registerWorkerifySW();

const app = new Workerify({ logger: true });
const todos: string[] = [];

app.get('/todos', async () =&gt;
  `&lt;ul&gt;${todos.map(t =&gt; `&lt;li&gt;${t}&lt;/li&gt;`).join('')}&lt;/ul&gt;`
);

app.post('/todos', async (request, reply) =&gt; {
  todos.push(request.body?.todo);
  reply.headers = { 'HX-Trigger': 'todos:refresh' };
  return { success: true };
});

await app.listen();
htmx.trigger('#app', 'workerify-ready');</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_start_now">Start now</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Getting started with Workerify is straightforward. You can scaffold a new project in seconds with:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">npx @workerify/create-htmx-app</code></pre>
</div>
</div>
<div class="paragraph">
<p>This will generate a ready-to-use setup with Vite, HTMX, and Workerify so you can start experimenting right away.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_conclusion">Conclusion</h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="https://github.com/anthonny/workerify">Workerify</a> makes Service Worker routing simple and powerful by:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Separating route definition (lib) from SW management (plugin)</p>
</li>
<li>
<p>Using BroadcastChannel for fast communication</p>
</li>
<li>
<p>Handling multi-tab isolation automatically</p>
</li>
<li>
<p>Offering a familiar, developer-friendly API</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>This design opens the door to offline-first apps, zero-latency SPAs, and new client-side architectures. Workerify is still evolving, but it’s a useful starting point for experimenting with Service Workers in new ways — and it can even serve as a stepping stone toward simpler application models, especially those built with <a href="https://htmx.org">HTMX</a>.</p>
</div>
<div class="paragraph">
<p>👉 Try it out, experiment with your own projects, and feel free to share your feedback and experiences with me, I’d love to hear how you use <a href="https://github.com/anthonny/workerify">Workerify</a>!
<a href="https://discord.gg/9wW8KFXEnx">A fresh Discord is available</a> if you’d like to join and share with me 😁</p>
</div>
</div>
</div>]]></description><link>http://anthonnyquerouil.me/2025/09/16/how-workerifys-vite-plugin-and-core-library-work-together-a-deep-dive.html</link><guid isPermaLink="true">http://anthonnyquerouil.me/2025/09/16/how-workerifys-vite-plugin-and-core-library-work-together-a-deep-dive.html</guid><category><![CDATA[Workerify]]></category><category><![CDATA[Htmx]]></category><category><![CDATA[Service worker]]></category><category><![CDATA[SPA]]></category><dc:creator><![CDATA[Anthonny Quérouil]]></dc:creator><pubDate>Tue, 16 Sep 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[How to use accounts-ui with Elm and Meteor in 10 minutes]]></title><description><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>With <a href="https://www.meteor.com/">Meteor</a> you can quickly add an account management system using the packages <a href="https://docs.meteor.com/packages/accounts-ui.html">accounts-ui</a> and <a href="https://docs.meteor.com/api/passwords.html">accounts-password</a>.
It is really useful for prototyping your application, before going deeper and creating your own design for the accounts management (login, register&#8230;&#8203;).
In this post we will see how to use these packages inside our <a href="https://elm-lang.org/">Elm</a> / <a href="https://www.meteor.com/">Meteor</a> application.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
If you want to play with the code, you can use my starter kit <a href="https://github.com/anthonny/meteor-elm-starter-kit" class="bare">https://github.com/anthonny/meteor-elm-starter-kit</a>. You will find details about this setup <a href="https://anthonnyquerouil.me/2020/06/17/how-i-use-meteor-elm-and-tailwindcss-together.html">here</a>
</td>
</tr>
</table>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">git clone https://github.com/anthonny/meteor-elm-starter-kit.git meteor-elm-app
cd meteor-elm-app
meteor npm i</code></pre>
</div>
</div>
<hr>
<div id="toc" class="toc">
<div id="toctitle" class="title">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_add_the_accounts_ui_widget_in_our_app">Add the accounts-ui widget in our app</a>
<ul class="sectlevel2">
<li><a href="#_the_meteor_side">The Meteor side</a></li>
<li><a href="#_the_elm_side">The Elm side</a></li>
</ul>
</li>
<li><a href="#_manage_the_possible_states">Manage the possible states</a>
<ul class="sectlevel2">
<li><a href="#_the_elm_side_2">The Elm side</a></li>
<li><a href="#_the_meteor_side_2">The Meteor side</a></li>
</ul>
</li>
<li><a href="#_synchronize_the_two_applications">Synchronize the two applications</a>
<ul class="sectlevel2">
<li><a href="#_the_elm_side_3">The Elm side</a></li>
<li><a href="#_the_meteor_side_3">The Meteor side</a></li>
</ul>
</li>
<li><a href="#_conclusion">Conclusion</a></li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_add_the_accounts_ui_widget_in_our_app">Add the accounts-ui widget in our app</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_the_meteor_side">The Meteor side</h3>
<div class="paragraph">
<p>We will use the <a href="https://docs.meteor.com/packages/accounts-ui.html">accounts-ui</a>, the <a href="https://docs.meteor.com/api/passwords.html">accounts-password</a> and the jquery packages.
So we will install the following elements:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell"># under meteor-elm-app
meteor add accounts-ui accounts-password jquery
meteor npm i jquery</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now we will be able to use the <code>Template.loginButtons</code> in our code to display the login form.</p>
</div>
<div class="paragraph">
<p>We want to manage the visibility of the form inside our <a href="https://elm-lang.org/">Elm</a> application.
A custom element is the perfect feature to do that, we will be able to use <code>Html.node</code> in <a href="https://elm-lang.org/">Elm</a> to work with it.</p>
</div>
<div class="paragraph">
<p>We can create a file <strong>imports/ui/accounts-ui.ts</strong></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-ts" data-lang="ts">import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';

export class AccountsUi extends HTMLElement {
    view: Blaze.View;
    container: HTMLDivElement;
    constructor() {
        super();
        this.container = document.createElement('div');
        this.view = Blaze.render(Template.loginButtons, this.container);
    }
    connectedCallback() {
        this.appendChild(this.container);
    }
    disconnectedCallback() {
        if (this.view) {
            Blaze.remove(this.view);
        }
    }
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>And we can use it in the file <strong>client/main.ts</strong> and define a custom element called <code>accounts-ui</code></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-ts" data-lang="ts">import { AccountsUi } from '/imports/ui/accounts-ui';
import { init } from 'meteor/elm-app';
import { Meteor } from 'meteor/meteor';

customElements.define('accounts-ui', AccountsUi);

Meteor.startup(() =&gt; {
    const ports = init({
        node: document.getElementById('main'),
        flags: {},
    });
});</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_the_elm_side">The Elm side</h3>
<div class="paragraph">
<p>With the <code>Html.node</code>, it is really easy to use WebComponents inside your <a href="https://elm-lang.org/">Elm</a> application.</p>
</div>
<div class="paragraph">
<p>Lets modify our main file <strong>packages/elm-app/app/src/Main.elm</strong></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">import Html exposing (Html, div, node, text)


view : Model -&gt; Html msg
view model =
    div []
        [ node "accounts-ui" [] []
        , text model
        ]</code></pre>
</div>
</div>
<div class="paragraph">
<p>We can now start our <a href="https://www.meteor.com/">Meteor</a> application <code>meteor npm start</code> and open <code><a href="http://localhost:3000" class="bare">http://localhost:3000</a></code>
You should see the <strong>login form</strong> from <code>accounts-ui</code>:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/85476607-fd823400-b5b8-11ea-9d9a-77d481d2519b.png" alt="85476607 fd823400 b5b8 11ea 9d9a 77d481d2519b">
</div>
</div>
<div class="paragraph">
<p>And if your click on <strong>Sign in</strong>:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/85476717-2d313c00-b5b9-11ea-9edd-055e6d617b8b.png" alt="85476717 2d313c00 b5b9 11ea 9edd 055e6d617b8b">
</div>
</div>
<div class="paragraph">
<p>In maybe 5 minutes we have added the account management in our application.
But we still need to inform our <a href="https://elm-lang.org/">Elm</a> application about the state of the authentication.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_manage_the_possible_states">Manage the possible states</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_the_elm_side_2">The Elm side</h3>
<div class="paragraph">
<p>There are three states we need to handle:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>when the user is logged in,</p>
</li>
<li>
<p>when the user is not logged in, anonymous,</p>
</li>
<li>
<p>when the user is logging in, it occurs when the application starts and when the user submit the login form.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>So we can change our <code>Model</code> to:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">type Model
    = LoggedIn String
    | LoggingIn
    | Anonymous</code></pre>
</div>
</div>
<div class="paragraph">
<p>The <code>LoggedIn</code> state will have an associated <code>String</code>: the <code>email</code> of the current user.</p>
</div>
<div class="paragraph">
<p>Now we can adapt our <code>view</code> function:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">view : Model -&gt; Html msg
view model =
    div []
        [ node "accounts-ui" [] []
        , case model of
            Anonymous -&gt;
                text "anonymous"

            LoggedIn email -&gt;
                text &lt;| "Hello " ++ email

            LoggingIn -&gt;
                text "Logging in..."
        ]</code></pre>
</div>
</div>
<div class="paragraph">
<p>Our code does not compile yet, we have to fix the init function.
We have two possible states during the init phase:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Anonymous</p>
</li>
<li>
<p>LoggingIn</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>We can manage these states with a simple boolean <code>isLoggingIn</code> in our type <code>Flags</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">type alias Flags =
    { isLoggingIn : Bool
    }


main : Program Flags Model msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }


init : Flags -&gt; ( Model, Cmd msg )
init flags =
    ( if flags.isLoggingIn then
        LoggingIn

      else
        Anonymous
    , Cmd.none
    )</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_the_meteor_side_2">The Meteor side</h3>
<div class="paragraph">
<p>If you start the application <code>meteor npm start</code>, you should see this error:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/85526559-6776f980-b60a-11ea-8e38-6a5cd64e1678.png" alt="85526559 6776f980 b60a 11ea 8e38 6a5cd64e1678">
</div>
</div>
<div class="paragraph">
<p>The message is clear, we must define the <code>isLoggingIn</code> in our flags object.</p>
</div>
<div class="paragraph">
<p>We modify the interface <code>Flags</code> in the file <strong>index.ts</strong>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-ts" data-lang="ts">interface Flags {
    isLoggingIn: boolean;
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>And we can use it in the <strong>client/main.ts</strong>:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/85527547-419e2480-b60b-11ea-8ebb-25251415b04e.png" alt="85527547 419e2480 b60b 11ea 8ebb 25251415b04e">
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-ts" data-lang="ts">Meteor.startup(() =&gt; {
    const ports = init({
        node: document.getElementById('main'),
        flags: {
            isLoggingIn: Meteor.loggingIn(),
        },
    });
});</code></pre>
</div>
</div>
<div class="paragraph">
<p>You should see this content:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/85528904-cb022680-b60c-11ea-8609-bcac20fc564b.png" alt="85528904 cb022680 b60c 11ea 8609 bcac20fc564b">
</div>
</div>
<div class="paragraph">
<p>The problem is if you create a new user and you logging in, the state does not change.
It only changes if you refresh the page.
To synchronize our <a href="https://elm-lang.org/">Elm</a> application with <a href="https://www.meteor.com/">Meteor</a>, we will use ports.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_synchronize_the_two_applications">Synchronize the two applications</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_the_elm_side_3">The Elm side</h3>
<div class="paragraph">
<p>The <a href="https://www.meteor.com/">Meteor</a> application must indicate to the <a href="https://elm-lang.org/">Elm</a> application when</p>
</div>
<div class="ulist">
<ul>
<li>
<p>the user is logging in,</p>
</li>
<li>
<p>the user is logged in,</p>
</li>
<li>
<p>the user is logged out</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>In our file <strong>packages/elm-app/app/src/Main.elm</strong>, we will add three ports:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">port loggingIn : (() -&gt; msg) -&gt; Sub msg


port loggedIn : (String -&gt; msg) -&gt; Sub msg


port loggedOut : (() -&gt; msg) -&gt; Sub msg</code></pre>
</div>
</div>
<div class="paragraph">
<p>We will create three messages, one message per event</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">type Msg
    = GotLoggingIn
    | GotLoggedIn String
    | GotLoggedOut</code></pre>
</div>
</div>
<div class="paragraph">
<p>And we have to adapt the update function and to define subscriptions:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">update : Msg -&gt; Model -&gt; ( Model, Cmd Msg )
update msg model =
    case msg of
        GotLoggingIn -&gt;
            ( LoggingIn, Cmd.none )

        GotLoggedIn email -&gt;
            ( LoggedIn email, Cmd.none )

        GotLoggedOut -&gt;
            ( Anonymous, Cmd.none )


subscriptions : Model -&gt; Sub Msg
subscriptions _ =
    Sub.batch
        [ loggingIn (\_ -&gt; GotLoggingIn)
        , loggedIn GotLoggedIn
        , loggedOut (\_ -&gt; GotLoggedOut)
        ]</code></pre>
</div>
</div>
<div class="paragraph">
<p>To finish we have to update the definition of our type <code>Ports</code> in <strong>packages/elm-app/</strong>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-ts" data-lang="ts">export interface Ports {
    loggingIn?: {
        send: (nothing: null) =&gt; void;
    };
    loggedIn?: {
        send: (username: string) =&gt; void;
    };
    loggedOut?: {
        send: (nothing: null) =&gt; void;
    };
}</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_the_meteor_side_3">The Meteor side</h3>
<div class="paragraph">
<p>On the <a href="https://www.meteor.com/">Meteor</a> side, we will use the function <code>Tracker.autorun</code> to send messages to the <a href="https://elm-lang.org/">Elm</a> application each time the state of the authentication changes.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-ts" data-lang="ts">// ...
import { Tracker } from 'meteor/tracker';

// ...
Meteor.startup(() =&gt; {
    const ports = init({
        node: document.getElementById('main'),
        flags: {
            isLoggingIn: Meteor.loggingIn(),
        },
    });

    Tracker.autorun(() =&gt; {
        if (Meteor.loggingIn()) {
            console.log('Loggind in...');
            ports.loggingIn?.send(null);
        } else if (Meteor.user()) {
            console.log('Connected');
            ports.loggedIn?.send(Meteor.user()?.emails?.[0].address || '');
        } else {
            console.log('Not connected');
            ports.loggedOut?.send(null);
        }
    });
});</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now you should see the state up-to-date in the <a href="https://elm-lang.org/">Elm</a> application when you play with the authentication.</p>
</div>
<div class="videoblock">
<div class="content">
<video src="https://anthonnyquerouil.me/images/meteor-elm-accounts-ui.mp4" width="100%" controls>
Your browser does not support the video tag.
</video>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_conclusion">Conclusion</h2>
<div class="sectionbody">
<div class="paragraph">
<p>With <a href="https://www.meteor.com/">Meteor</a> it is easy to add an account management system, thanks to accounts-ui and the combination of <a href="https://elm-lang.org/">Elm</a> with the Custom Elements makes the integration simple.</p>
</div>
<div class="paragraph">
<p>But I think that one day you will have to create your own UI for your forms (login, register&#8230;&#8203;), luckily it will be the topic of my next post.</p>
</div>
<div class="paragraph">
<p>If you liked this post, do not hesitate to share it on your favorite social networks and if you are interested by this kind of content, you can follow me on twitter <a href="https://twitter.com/anthonny_q">@anthonny_q</a>.</p>
</div>
<div class="paragraph">
<p>If you have any feedbacks, comments are open 😘</p>
</div>
</div>
</div>]]></description><link>http://anthonnyquerouil.me/2020/06/24/how-to-use-accounts-ui-with-elm-and-meteor.html</link><guid isPermaLink="true">http://anthonnyquerouil.me/2020/06/24/how-to-use-accounts-ui-with-elm-and-meteor.html</guid><category><![CDATA[meteor]]></category><category><![CDATA[elm]]></category><dc:creator><![CDATA[Anthonny Quérouil]]></dc:creator><pubDate>Wed, 24 Jun 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[How I use Meteor, Elm and Tailwindcss together]]></title><description><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>I have worked with <a href="https://elm-lang.org/">Elm</a> last year and honestly it was an great experience.
I really liked it and I would like to use <a href="https://elm-lang.org/">Elm</a> in all my new projects.
But I also like <a href="https://www.meteor.com/">Meteor</a> and three weeks ago I started using it again.
I discovered <a href="https://www.meteor.com/">Meteor</a> in 2015, I gave a talk about it (<a href="https://www.youtube.com/watch?v=AxIvfQXTqVo" class="bare">https://www.youtube.com/watch?v=AxIvfQXTqVo</a>) and it still feels like "Wow!" when you start a project.
So in this post I will explain how I make the two work together and how to add <a href="https://tailwindcss.com/">Tailwindcss</a> for the UI part (I love <a href="https://tailwindcss.com/">Tailwindcss</a> too 😍), from scratch.
I will also explain how to link an existing project to <a href="https://www.meteor.com/">Meteor</a> with a Todo app.</p>
</div>
<hr>
<div id="toc" class="toc">
<div id="toctitle" class="title">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_prerequisite">Prerequisite</a></li>
<li><a href="#_meteor">Meteor</a>
<ul class="sectlevel2">
<li><a href="#_install_meteor">Install Meteor</a></li>
<li><a href="#_create_a_project">Create a project</a></li>
<li><a href="#_add_typescript">Add typescript</a></li>
<li><a href="#_create_the_file_structure">Create the file structure</a></li>
<li><a href="#_checkpoint">Checkpoint</a></li>
</ul>
</li>
<li><a href="#_elm">Elm</a>
<ul class="sectlevel2">
<li><a href="#_install_parcel">Install Parcel</a></li>
<li><a href="#_create_a_meteor_package">Create a Meteor package</a></li>
<li><a href="#_create_the_app">Create the app</a></li>
<li><a href="#_the_css_main_file">The CSS main file</a></li>
<li><a href="#_the_package_mainmodule">The Package mainModule</a></li>
<li><a href="#_build_with_parcel">Build with Parcel</a></li>
<li><a href="#_add_our_package_to_meteor">Add our package to Meteor</a></li>
<li><a href="#_post_install">Post install</a></li>
</ul>
</li>
<li><a href="#_use_the_elm_application_in_our_meteor_client">Use the Elm application in our Meteor client</a>
<ul class="sectlevel2">
<li><a href="#_the_typing_is_not_good">The typing is not good</a></li>
</ul>
</li>
<li><a href="#_live_reload">Live Reload</a></li>
<li><a href="#_tailwindcss">Tailwindcss</a></li>
<li><a href="#_the_todos_application">The Todos application</a>
<ul class="sectlevel2">
<li><a href="#_update_the_main_elm">Update the Main.elm</a></li>
<li><a href="#_define_the_todos_collection_and_methods">Define the Todos collection and methods</a></li>
<li><a href="#_add_ports_to_the_elm_application">Add ports to the Elm application</a></li>
<li><a href="#_link_ports_to_meteor_methods_and_subscriptions">Link ports to Meteor.methods and subscriptions</a></li>
</ul>
</li>
<li><a href="#_conclusion">Conclusion</a></li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_prerequisite">Prerequisite</h2>
<div class="sectionbody">
<div class="paragraph">
<p>You only need two things:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Your favourite IDE (VSCode with the <a href="https://marketplace.visualstudio.com/items?itemName=Elmtooling.elm-ls-vscode">Elm extension</a> fits very well)</p>
</li>
<li>
<p>A browser</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>You don&#8217;t need to install NPM, Node or Mongo.
All this stuff is already packaged in <a href="https://www.meteor.com/">Meteor</a> environment.</p>
</div>
<div class="paragraph">
<p>So to run a NPM command we will use <code>meteor npm xxx</code>, and for Mongo, we will use <code>meteor mongo</code>.</p>
</div>
<div class="paragraph">
<p>So let&#8217;s begin 😁</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_meteor">Meteor</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_install_meteor">Install Meteor</h3>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/84738510-53981b80-afaa-11ea-8d02-cdf6c42c26c0.png" alt="84738510 53981b80 afaa 11ea 8d02 cdf6c42c26c0">
</div>
</div>
<div class="paragraph">
<p>Here are the commands to install <a href="https://www.meteor.com/">Meteor</a> on OSX/Linux and Windows
OSX / Linux</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell"># OSX/Linux
curl https://install.meteor.com/ | sh

# Windows
choco install meteor</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
For more information about the installation, please refer to the official documentation here <a href="https://www.meteor.com/install" class="bare">https://www.meteor.com/install</a>
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_create_a_project">Create a project</h3>
<div class="paragraph">
<p>Let&#8217;s start by creating an empty project</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">meteor create meteor-elm-app --bare
cd meteor-elm-app</code></pre>
</div>
</div>
<div class="paragraph">
<p>This <code>--bare</code> option will create an empty project with static-html instead of blaze and without autopublish and insecure.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
For more information about the command create, follow the link to the <a href="https://docs.meteor.com/commandline.html#meteorcreate">documentation</a>
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_add_typescript">Add typescript</h3>
<div class="paragraph">
<p>Adding Typescript is really simple.
Just run these two commands</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell"># under meteor-elm-app
meteor add typescript
meteor npm i -D @types/meteor @types/mocha</code></pre>
</div>
</div>
<div class="paragraph">
<p>Create a tsconfig.json under the directory <strong>meteor-elm-app</strong></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">{
    "compilerOptions": {
      /* Basic Options */
      "target": "es2018",
      "module": "esNext",
      "lib": ["esnext", "dom"],
      "allowJs": true,
      "checkJs": false,
      "incremental": false,
      "noEmit": true,

      /* Strict Type-Checking Options */
      "strict": true,
      "noImplicitAny": true,
      "strictNullChecks": true,

      /* Additional Checks */
      "noUnusedLocals": true,
      "noUnusedParameters": true,
      "noImplicitReturns": false,
      "noFallthroughCasesInSwitch": false,

      /* Module Resolution Options */
      "baseUrl": ".",
      "paths": {
        /* Support absolute ~imports/* with a leading '/' */
        "/*": ["*"]
      },
      "moduleResolution": "node",
      "resolveJsonModule": true,
      "types": ["node", "mocha"],
      "esModuleInterop": true,
      "preserveSymlinks": true,
    },
    "exclude": [
      "./.meteor/**",
      "./packages/**"
    ]
  }</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
This configuration comes from the typescript template provided by <a href="https://www.meteor.com/">Meteor</a>.
I just removed the support of JSX.
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_create_the_file_structure">Create the file structure</h3>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
We will setup a simple file structure here.
For more complex projects, you should follow the <a href="https://guide.meteor.com/structure.html#javascript-structure">guideline</a> provided by <a href="https://www.meteor.com/">Meteor</a>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>To initialise the file structure, run these commands</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell"># under meteor-elm-app
mkdir client server imports/api
touch client/main.html client/main.ts client/main.css server/main.ts</code></pre>
</div>
</div>
<div class="paragraph">
<p>Your project folder should look like this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell"># under meteor-elm-app
❯ tree -I node_modules
.
├── client
│   ├── main.css
│   ├── main.html
│   └── main.ts
├── imports
│   └── api
├── package-lock.json
├── package.json
├── server
│   └── main.ts
└── tsconfig.json

4 directories, 7 files</code></pre>
</div>
</div>
<div class="paragraph">
<p>We will update the <strong>package.json</strong> file to define the main modules in our <a href="https://www.meteor.com/">Meteor</a> app:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">"meteor": {
    "mainModule": {
        "client": "client/main.ts",
        "server": "server/main.ts"
    }
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>At this point your <strong>package.json</strong> file should be like:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">{
  "name": "meteor-elm-app",
  "private": true,
  "scripts": {
    "start": "meteor run"
  },
  "meteor": {
    "mainModule": {
      "client": "client/main.ts",
      "server": "server/main.ts"
    }
  },
  "dependencies": {
    "@babel/runtime": "^7.8.3",
    "meteor-node-stubs": "^1.0.0"
  }
}</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
if you need more informations about this mainModule options, you can read the content of <a href="https://github.com/meteor/meteor/pull/9690">this pull request</a>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>We now need to add some basic content to the <strong>main.html</strong> file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-html" data-lang="html">&lt;head&gt;
  &lt;title&gt;meteor-elm-app&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;
  &lt;div id="main"&gt;Elm app will be here&lt;/div&gt;
&lt;/body&gt;</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_checkpoint">Checkpoint</h3>
<div class="paragraph">
<p>Lets check if everything is OK before starting with <a href="https://elm-lang.org/">Elm</a>.
Start your <strong>meteor</strong> server:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell"># under meteor-elm-app
meteor</code></pre>
</div>
</div>
<div class="paragraph">
<p>Open <a href="http://localhost:3000" class="bare">http://localhost:3000</a> on your favorite browser
You should see this:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/84446744-8195fc80-ac46-11ea-9da8-4fd2033898bf.png" alt="84446744 8195fc80 ac46 11ea 9da8 4fd2033898bf">
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_elm">Elm</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_install_parcel">Install Parcel</h3>
<div class="paragraph">
<p>We will use <a href="https://parceljs.org/">Parcel</a> to build our <a href="https://elm-lang.org/">Elm</a> application and we will use the result of this build in our <a href="https://www.meteor.com/">Meteor</a> application</p>
</div>
<div class="paragraph">
<p>To install <a href="https://parceljs.org/">Parcel</a>, run this command</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">meteor npm i -D parcel</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_create_a_meteor_package">Create a Meteor package</h3>
<div class="paragraph">
<p>This <a href="https://www.meteor.com/">Meteor</a> package will contain our <a href="https://elm-lang.org/">Elm</a> application and we will use this package inside the <a href="https://www.meteor.com/">Meteor</a> application.</p>
</div>
<div class="paragraph">
<p>We use a Package because it allows us to isolate our <a href="https://elm-lang.org/">Elm</a> application from the rest of the <a href="https://www.meteor.com/">Meteor</a> context.
It is also really useful if we want to remove our <a href="https://elm-lang.org/">Elm</a> application or if one we don&#8217;t want to use <a href="https://www.meteor.com/">Meteor</a> anymore.</p>
</div>
<div class="paragraph">
<p>Let&#8217;s start by creating some folders:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">mkdir -p packages/elm-app/{app,dist}</code></pre>
</div>
</div>
<div class="paragraph">
<p>The <strong>app</strong> folder will contain the sources of our <a href="https://elm-lang.org/">Elm</a> application (<a href="https://elm-lang.org/">Elm</a>, TS and CSS files).
The <strong>dist</strong> folder will contain the result of the build made by <a href="https://parceljs.org/">Parcel</a>.</p>
</div>
<div class="paragraph">
<p>Because we will build with <a href="https://parceljs.org/">Parcel</a> and not with <a href="https://www.meteor.com/">Meteor</a>, we will create a new file at the root of the <strong>meteor-elm-app</strong> called <strong>.meteorignore</strong></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app
touch .meteorignore</code></pre>
</div>
</div>
<div class="paragraph">
<p>Then add this line inside this new file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">/packages/elm-app/app/**/*</code></pre>
</div>
</div>
<div class="paragraph">
<p>Because we don&#8217;t want to push the <strong>dist</strong> and the <strong>elm-stuff</strong> folders on our repository, we will add them in the <strong>.gitignore</strong> located under the folder <strong>meteor-elm-app</strong></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">dist
elm-stuff</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now, let&#8217;s create a <strong>package.js</strong> file in our package:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app/packages/elm-app
touch package.js</code></pre>
</div>
</div>
<div class="paragraph">
<p>And add the following content in this file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">Package.describe({
    name: 'elm-app',
    version: '1.0.0',
    summary: 'elm app',
    documentation: 'add your elm app into meteor',
});

Package.onUse(function (api) {
    api.versionsFrom('1.10.2');
    api.use('modules');
    api.addFiles('dist/elm-app.css', 'client');
    api.mainModule('dist/elm-app.js', 'client');
});</code></pre>
</div>
</div>
<div class="paragraph">
<p><strong>Package.describe</strong> says that our package:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>is called <code>elm-app</code>,</p>
</li>
<li>
<p>is in version <code>1.0.0</code></p>
</li>
</ul>
</div>
<div class="paragraph">
<p><code>Package.onUse</code> says that our package:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>is implemented to be use with <a href="https://www.meteor.com/">Meteor</a> <code>1.10.2</code>,</p>
</li>
<li>
<p>uses the <code>modules</code> package, so we will be able to use <code>import {} from ''</code>,</p>
</li>
<li>
<p>will add the <code>dist/elm-app.css</code> file in the client when it will be loaded,</p>
</li>
<li>
<p>have a main js file for this package called <code>dist/elm-app.js</code>.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>If you are using <strong>elm-css</strong> and if you don&#8217;t need specific css classes in your app, you can remove <code>api.addFiles('dist/elm-app.css', 'client');</code> from the <strong>package.js</strong> file.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
For more informations about the Package.js file, see the <a href="https://docs.meteor.com/api/packagejs.html">documentation</a>
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_create_the_app">Create the app</h3>
<div class="paragraph">
<p>We will create our <a href="https://elm-lang.org/">Elm</a> application under the folder <strong>packages/elm-app/app</strong>.</p>
</div>
<div class="paragraph">
<p>We need to install <a href="https://elm-lang.org/">Elm</a>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">meteor npm i -D elm elm-format</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
elm-format is not mandatory but you should use it with your IDE to format on save and to avoid problem at compile time
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Then we will initialise our app with the following command:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app/packages/elm-app/app
meteor npx elm init</code></pre>
</div>
</div>
<div class="paragraph">
<p>Validate the creation of the <strong>elm.json</strong> file and we are good 👍.</p>
</div>
<div class="paragraph">
<p>At this step, your folder should be like this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app
❯ tree -I 'node_modules|.meteor' -a
.
├── .gitignore
├── .meteorignore
├── client
│   ├── main.css
│   ├── main.html
│   └── main.ts
├── imports
│   └── api
├── package-lock.json
├── package.json
├── packages
│   └── elm-app
│       ├── app
│       │   ├── elm.json
│       │   └── src
│       ├── dist
│       └── package.js
├── server
│   └── main.ts
└── tsconfig.json

9 directories, 11 files</code></pre>
</div>
</div>
<div class="paragraph">
<p>In a first time, we will create a simple <a href="https://elm-lang.org/">Elm</a> application.</p>
</div>
<div class="paragraph">
<p>Create a <strong>Main.elm</strong> file inside the folder <strong>packages/elm-app/app/src</strong> with this content:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">module Main exposing(main)

import Browser
import Html exposing (Html, text)

type alias Model = String

main : Program () Model msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }

init: () -&gt; (Model, Cmd msg)
init _ =
    ("Hello from Elm app", Cmd.none)

view: Model -&gt; Html msg
view model =
    text model

update: msg -&gt; Model -&gt; (Model, Cmd msg)
update _ model =
    (model, Cmd.none)

subscriptions : Model -&gt; Sub msg
subscriptions _ =
    Sub.none</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_the_css_main_file">The CSS main file</h3>
<div class="paragraph">
<p>In the folder <strong>meteor-elm-app/packages/elm-app/app</strong>, create an empty <strong>main.scss</strong> SCSS file (or CSS if you prefer) that we will use later to add some style in our <a href="https://elm-lang.org/">Elm</a> application.</p>
</div>
<div class="paragraph">
<p>NB: if you use <strong>elm-css</strong> and you don&#8217;t need a stylesheet, skip this step and remove the line <code>api.addFiles('dist/elm-app.css', 'client');</code> in the <strong>package.js</strong> file</p>
</div>
</div>
<div class="sect2">
<h3 id="_the_package_mainmodule">The Package mainModule</h3>
<div class="paragraph">
<p>In the folder <strong>meteor-elm-app/packages/elm-app/app</strong>, create a file <strong>index.ts</strong> that will <strong>mount</strong> our <a href="https://elm-lang.org/">Elm</a> application and export the <strong>ports</strong>.</p>
</div>
<div class="paragraph">
<p>A simple version could be:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">import './main.scss'
const { Elm } = require('./src/Main.elm')

export const init = (configuration: any) =&gt; {
    const app = Elm.Main.init(configuration)
    return app.ports
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>But because we want to <strong>Type</strong> things as much as possible, let&#8217;s create this <strong>index.ts</strong> like this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">import './main.scss'
const { Elm } = require('./src/Main.elm')

interface Flags {}

export interface Configuration {
    node: HTMLElement | null,
    flags: Flags
}

export interface Ports {}

export const init: (configuration: Configuration) =&gt; Ports = (configuration) =&gt; {
    const app = Elm.Main.init(configuration)
    return app.ports
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>With this definition, when we will need some flags or some ports, we will add the new stuff in our interface and the client will have to implement them.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
If you are using <strong>CSS</strong> instead of <strong>SCSS</strong> then update the file import accordingly
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_build_with_parcel">Build with Parcel</h3>
<div class="paragraph">
<p>Let&#8217;s create a build script in our <strong>package.json</strong> file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">"elm:build": "parcel build packages/elm-app/app/index.ts -d packages/elm-app/dist --out-file elm-app.js --no-cache",</code></pre>
</div>
</div>
<div class="paragraph">
<p>This script will build our application in a file <strong>elm-app.js</strong> (and <strong>elm-app.css</strong>) and put it in the folder <strong>packages/elm-app/dist</strong> (the one we added in our <strong>.gitignore</strong>)</p>
</div>
<div class="paragraph">
<p>We can test our script</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app
meteor npm run elm:build</code></pre>
</div>
</div>
<div class="paragraph">
<p>If everything is ok, you should see these lines:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/84450020-4ea43680-ac4f-11ea-9b45-ce0dfb572835.png" alt="84450020 4ea43680 ac4f 11ea 9b45 ce0dfb572835">
</div>
</div>
</div>
<div class="sect2">
<h3 id="_add_our_package_to_meteor">Add our package to Meteor</h3>
<div class="paragraph">
<p>Now that we have a package, we have to add it in our <a href="https://www.meteor.com/">Meteor</a> configuration.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
you must have run the previous build command before adding the package because without a dist folder, you will not be able to add it.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Execute this command to add the package</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app
meteor add elm-app</code></pre>
</div>
</div>
<div class="paragraph">
<p>You should see</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/84450082-7abfb780-ac4f-11ea-92f4-db936ee6f726.png" alt="84450082 7abfb780 ac4f 11ea 92f4 db936ee6f726">
</div>
</div>
</div>
<div class="sect2">
<h3 id="_post_install">Post install</h3>
<div class="paragraph">
<p>To avoid to have to compile manually each time someone clone the repository, we will add a <code>postinstall</code> script in the <strong>package.json</strong> file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">"postinstall": "meteor npm run elm:build",</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_use_the_elm_application_in_our_meteor_client">Use the Elm application in our Meteor client</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Now that we have our <a href="https://elm-lang.org/">Elm</a> application, it is time to import it in the client side of our <a href="https://www.meteor.com/">Meteor</a> application</p>
</div>
<div class="paragraph">
<p>In the <strong>client/main.ts</strong> file, add the following code:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">import { init } from "meteor/elm-app";
import { Meteor } from 'meteor/meteor';

Meteor.startup(() =&gt; {
    const ports = init({
        node: document.getElementById("main"),
        flags: {}
    })
})</code></pre>
</div>
</div>
<div class="paragraph">
<p>In this code, we import the <code>init</code> function from the package <code>meteor/elm-app</code> which is the package we have just created (you can see it in the file <strong>.meteor/packages</strong>).
Then we call it to mount our <a href="https://elm-lang.org/">Elm</a> application on the node <code>document.getElementById("main")</code> (the one we have created in the <strong>main.html</strong> file)</p>
</div>
<div class="paragraph">
<p>Now, if you start your meteor application by running the <code>meteor</code> command, on <a href="http://localhost:3000" class="bare">http://localhost:3000</a> you should see:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/84450699-4a791880-ac51-11ea-9c51-c0046cc273a0.png" alt="84450699 4a791880 ac51 11ea 9c51 c0046cc273a0">
</div>
</div>
<div class="paragraph">
<p>But&#8230;&#8203;</p>
</div>
<div class="sect2">
<h3 id="_the_typing_is_not_good">The typing is not good</h3>
<div class="paragraph">
<p>You should see that your import is underlined in red:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/84450825-9c21a300-ac51-11ea-9243-78a13ecad82d.png" alt="84450825 9c21a300 ac51 11ea 9243 78a13ecad82d">
</div>
</div>
<div class="paragraph">
<p>To fix that, we will add a declaration file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app
mkdir -p types/meteor
touch types/meteor/elm-app.d.ts</code></pre>
</div>
</div>
<div class="paragraph">
<p>And add the following content</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">declare module 'meteor/elm-app' {
    export const init: (
        configuration: import('/packages/elm-app/app').Configuration,
    ) =&gt; import('/packages/elm-app/app').Ports;
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now each time we will change the definition of the type <code>Flag</code> or the type <code>Port</code> inside our <a href="https://elm-lang.org/">Elm</a> application, we will be sure to know if we have some stuff to fix in the <a href="https://www.meteor.com/">Meteor</a> client 💪.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_live_reload">Live Reload</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Because we don&#8217;t want to build manually our <a href="https://elm-lang.org/">Elm</a> application each time we make a change, we will setup the live reload</p>
</div>
<div class="paragraph">
<p>We will install some packages to help us</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app
meteor npm i -D concurrently wait-on rimraf</code></pre>
</div>
</div>
<div class="paragraph">
<p>Then we will create an new script in our <strong>package.json</strong> file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">"elm:watch": "parcel watch packages/elm-app/app/index.ts -d packages/elm-app/dist --out-file elm-app.js",</code></pre>
</div>
</div>
<div class="paragraph">
<p>With <code>elm:watch</code>, parcel will rebuild our app each time we make a change in <a href="https://elm-lang.org/">Elm</a>, TS or SCSS files under the folder <strong>packages/elm-app/app</strong>.</p>
</div>
<div class="paragraph">
<p>And because <code>parcel watch</code> create a <strong>.cache</strong> folder, we will add it to the <strong>.gitignore</strong> file.
The content of your <strong>.gitignore</strong> should be like this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">node_modules/
dist
elm-stuff
.cache</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now to run parcel and meteor in parallel, we will update the <strong>package.json</strong> file.
We will rename the script <code>start</code> to <code>meteor:run</code>, and redefine the script <code>start</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">"meteor:run": "meteor run",
"start": "rimraf \"./packages/elm-app/dist/*\" &amp;&amp; concurrently -n \"parcel,meteor\" -c \"magenta,green\" \"meteor npm run elm:watch\" \"wait-on ./packages/elm-app/dist/elm-app.js &amp;&amp; meteor npm run meteor:run\"",</code></pre>
</div>
</div>
<div class="paragraph">
<p>The script <code>start</code> call <code>rimraf</code> to clean the <strong>dist</strong> folder, then we call <code>concurrently</code> to run two tasks:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>the <code>parcel</code> one, that will be log in <code>magenta</code> and its command is <code>meteor npm run elm:watch</code></p>
</li>
<li>
<p>the <code>meteor</code> one, that will be log in <code>green</code> and its command is <code>wait-on ./packages/elm-app/dist/elm-app.js &amp;&amp; meteor npm run meteor:run</code> (the <code>wait-on</code> command is use to wait the build from Parcel)</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Now each time we will change our content under <strong>packages/elm-app/app</strong>, <a href="https://parceljs.org/">Parcel</a> will rebuild incrementally our application and update the content under the <strong>dist</strong> folder, so <a href="https://www.meteor.com/">Meteor</a> will detect a change and refresh the main application.</p>
</div>
<div class="paragraph">
<p>You can now start your application by running:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app
meteor npm start</code></pre>
</div>
</div>
<div class="paragraph">
<p>You can make some changes in your <code>Main.elm</code> file and see that everything will automatically refresh in your browser.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_tailwindcss">Tailwindcss</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Tailwindcss is a npm package, so we will install it like this</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">meteor npm i -D tailwindcss</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
For more informations about Tailwindcss, see the <a href="https://tailwindcss.com/docs/installation">documentation</a>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>We need to initialize Tailwincss:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app/packages/elm-app/app
meteor npx tailwindcss init</code></pre>
</div>
</div>
<div class="paragraph">
<p>This command will generate a file called <strong>tailwind.config.js</strong></p>
</div>
<div class="paragraph">
<p>We can now edit the file <strong>main.scss</strong> inside our app (packages/elm-app/app/main.scss) to use <a href="https://tailwindcss.com/">Tailwindcss</a></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-css" data-lang="css">@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre>
</div>
</div>
<div class="paragraph">
<p>We will configure <strong>postcss</strong> to use <strong>autoprefixer</strong> and the <strong>tailwind.config.js</strong> file.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app/packages/elm-app/app
touch postcss.config.js</code></pre>
</div>
</div>
<div class="paragraph">
<p>And add this content to this file</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">const path = require("path");

module.exports = {
  plugins: [
    require("tailwindcss")(path.join(__dirname, "tailwind.config.js")),
    require("autoprefixer"),
  ],
};</code></pre>
</div>
</div>
<div class="paragraph">
<p>We can now edit our <strong>Main.elm</strong> to add a CSS class (<code>text-green-500</code>) from Tailwindcss:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">view: Model -&gt; Html msg
view model =
    div [class "text-green-500"] [text model]</code></pre>
</div>
</div>
<div class="paragraph">
<p>Then if you (re)start your server, you should see this:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/84566211-bfdb0b00-ad6f-11ea-86fa-927a901ae327.png" alt="84566211 bfdb0b00 ad6f 11ea 86fa 927a901ae327">
</div>
</div>
<div class="paragraph">
<p>Congratulations 🎉! You made your first application with <a href="https://elm-lang.org/">Elm</a>, <a href="https://www.meteor.com/">Meteor</a> and Tailwindcss 👏.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_the_todos_application">The Todos application</h2>
<div class="sectionbody">
<div class="paragraph">
<p>It is really awesome right? What? You don&#8217;t want to use <a href="https://www.meteor.com/">Meteor</a> just to expose static file? Hmm ok, let&#8217;s go with the Todos application</p>
</div>
<div class="paragraph">
<p>Because the goal of this post is not to learn how to code in <a href="https://elm-lang.org/">Elm</a>, we will start with an application I wrote for the occasion.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/84833215-132eb100-b02f-11ea-836b-63a14a6dbe74.png" alt="84833215 132eb100 b02f 11ea 836b 63a14a6dbe74">
</div>
</div>
<div class="paragraph">
<p>This application is not linked with <a href="https://www.meteor.com/">Meteor</a> yet, there is no ports defined.
The goal is to save each Todo in MongoDB and to be able to sync two browser.</p>
</div>
<div class="sect2">
<h3 id="_update_the_main_elm">Update the Main.elm</h3>
<div class="paragraph">
<p>Replace the content of the Main.elm file with <a href="https://gist.github.com/anthonny/1b6a73782a6ad94c611849b9a5d4cbbf">this gist</a></p>
</div>
<div class="paragraph">
<p>We will need to add <code>elm/svg</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app/packages/elm-app/app
meteor npx elm install elm/svg</code></pre>
</div>
</div>
<div class="paragraph">
<p>Then start your application</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">meteor npm start</code></pre>
</div>
</div>
<div class="paragraph">
<p>You can try the application, actually we can:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Add a Todo</p>
</li>
<li>
<p>Switch the status of a Todo</p>
</li>
<li>
<p>Filter Todos by status</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>We will keep the filtering part in the client, but we want to:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Load Todos from MongoDB</p>
</li>
<li>
<p>Save new Todos in MongoDB</p>
</li>
<li>
<p>Switch the status and save it in MongoDB</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>But let&#8217;s start with the backend</p>
</div>
</div>
<div class="sect2">
<h3 id="_define_the_todos_collection_and_methods">Define the Todos collection and methods</h3>
<div class="paragraph">
<p>Under the folder <strong>meteor-elm-app/imports/api</strong>, create a file <strong>todos.ts</strong>.</p>
</div>
<div class="paragraph">
<p>In this file we will define what is a Todo, and create the collection:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">import { Mongo } from "meteor/mongo";
import { Meteor } from "meteor/meteor";

export interface Todo {
  _id?: string;
  value: string;
  status: "checked" | "unchecked";
  createdAt: Date;
}

export const TodosCollection = new Mongo.Collection&lt;Todo&gt;("todos");</code></pre>
</div>
</div>
<div class="paragraph">
<p>Then in the same file, we will add two <a href="https://www.meteor.com/">Meteor</a> methods, one to add a Todo and another to switch the status of Todo with its ID:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">Meteor.methods({
  "todos.addTodo"(value: string) {
    if (value !== "") {
      TodosCollection.insert({
        value,
        status: "unchecked",
        createdAt: new Date(),
      });
    }
  },
  "todos.toggleStatus"(todoId: string) {
    const todo = TodosCollection.findOne({ _id: todoId });
    if (!todo) {
      throw new Meteor.Error("Todo not found");
    }

    const newStatus = todo.status === "checked" ? "unchecked" : "checked";

    TodosCollection.update({ _id: todoId }, { $set: { status: newStatus } });
  },
});</code></pre>
</div>
</div>
<div class="paragraph">
<p>And at the end of the file, we will publish our collection on the server side:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">if (Meteor.isServer) {
  Meteor.publish("todos", function todos() {
    return TodosCollection.find({}, { sort: { createdAt: -1 } });
  });
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Finally we need to import this file in the file <strong>server/main.ts</strong>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">import "/imports/api/todos";</code></pre>
</div>
</div>
<div class="paragraph">
<p>The server side in now ready.</p>
</div>
</div>
<div class="sect2">
<h3 id="_add_ports_to_the_elm_application">Add ports to the Elm application</h3>
<div class="paragraph">
<p>We will start by installing <code>elm/json</code> and <code>NoRedInk/elm-json-decode-pipeline</code> to decode our Todos:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">#under meteor-elm-app/packages/elm-app/app
meteor npx elm install elm/json
meteor npx elm install NoRedInk/elm-json-decode-pipeline</code></pre>
</div>
</div>
<div class="paragraph">
<p>So we will create 3 ports:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>addTodo: <code>port addTodo : String &#8594; Cmd msg</code></p>
</li>
<li>
<p>toggleStatus: <code>port toggleStatus : String &#8594; Cmd msg</code></p>
</li>
<li>
<p>receiveTodos: <code>port receiveTodos : (Decode.Value &#8594; msg) &#8594; Sub msg</code></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Let&#8217;s put these port at the end of our <strong>Main.elm</strong> file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">port module Main exposing(main)

import Json.Decode as Decode
import Json.Decode.Pipeline exposing (required)

...

port addTodo : String -&gt; Cmd msg

port toggleStatus : String -&gt; Cmd msg

port receiveTodos : (Decode.Value -&gt; msg) -&gt; Sub msg</code></pre>
</div>
</div>
<div class="paragraph">
<p>We have to change the type of the Todo.id to use a <code>String</code> because of the id in Mongo:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">type alias Todo =
    { id : String
    , value : String
    , status : TodoStatus
    }

type Msg
    = InputChanged String
    | AddTodo
    | ToggleStatus String -- ToggleStatus now need a String not a Int
    | FilterBy Filter</code></pre>
</div>
</div>
<div class="paragraph">
<p>We need a new variant <code>ReceiveTodos (List Todo)</code> for <code>Msg</code> to receive todos:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">type Msg
    = InputChanged String
    | AddTodo
    | ToggleStatus String
    | FilterBy Filter
    | ReceiveTodos (List Todo)</code></pre>
</div>
</div>
<div class="paragraph">
<p>We also change the <code>update</code> function because we will not update the <code>todos</code> list anymore.
We will get the one we will receive from the port <code>receiveTodos</code></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">update : Msg -&gt; Model -&gt; ( Model, Cmd Msg )
update msg model =
    case msg of
        InputChanged value -&gt;
            ( { model | todo = value }, Cmd.none )

        AddTodo -&gt;
            if String.isEmpty (String.trim model.todo) then
                ( model, Cmd.none )

            else
                ( { model | todo = "" }, addTodo model.todo )

        ToggleStatus todoId -&gt;
            ( model, toggleStatus todoId )

        FilterBy selectedFilter -&gt;
            ( { model | filter = selectedFilter }, Cmd.none )

        ReceiveTodos todos -&gt;
            ( { model | todos = todos }, Cmd.none )</code></pre>
</div>
</div>
<div class="paragraph">
<p>To finish with the <a href="https://elm-lang.org/">Elm</a> part, we need a subscription and some decoders to receive our Todos:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-elm" data-lang="elm">subscriptions : Model -&gt; Sub Msg
subscriptions _ =
    receiveTodos
        (\value -&gt;
            Decode.decodeValue decodeTodos value
                |&gt; Result.withDefault []
                |&gt; ReceiveTodos
        )


decodeTodo : Decode.Decoder Todo
decodeTodo =
    Decode.succeed Todo
        |&gt; required "id" Decode.string
        |&gt; required "value" Decode.string
        |&gt; required "status" decodeStatus


decodeStatus : Decode.Decoder TodoStatus
decodeStatus =
    Decode.string
        |&gt; Decode.andThen
            (\status -&gt;
                case status of
                    "checked" -&gt;
                        Decode.succeed Checked

                    _ -&gt;
                        Decode.succeed Unchecked
            )


decodeTodos : Decode.Decoder (List Todo)
decodeTodos =
    Decode.list decodeTodo</code></pre>
</div>
</div>
<div class="paragraph">
<p>If you remember, we have defined an interface <code>Ports</code> in the file <strong>meteor-elm-app/packages/elm-app/app/index.ts</strong>.
It is time to add some definitions:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">interface Todo {
  id: string;
  value: string;
  status: "checked" | "unchecked";
}

export interface Ports {
  addTodo?: {
    subscribe: (fn: (todo: string) =&gt; void) =&gt; void;
  };
  toggleStatus?: {
    subscribe: (fn: (todoId: string) =&gt; void) =&gt; void;
  };
  receiveTodos?: {
    send: (todos: Todo[]) =&gt; void;
  };
}</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_link_ports_to_meteor_methods_and_subscriptions">Link ports to Meteor.methods and subscriptions</h3>
<div class="paragraph">
<p>We have some piece of code in <a href="https://elm-lang.org/">Elm</a> and some piece of code in the server side.
Now we need to link them together, and we will do that in the file <strong>client/main.ts</strong></p>
</div>
<div class="paragraph">
<p>We will need to import our TodosCollection and the <a href="https://www.meteor.com/">Meteor</a> Tracker</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">import { Tracker } from "meteor/tracker";
import { TodosCollection } from "/imports/api/todos";</code></pre>
</div>
</div>
<div class="paragraph">
<p>Then we will subscribe to the output ports:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">  ports.addTodo?.subscribe((todo) =&gt; {
    Meteor.call("todos.addTodo", todo, (err: Error) =&gt; {
      if (err) {
        // Maybe we should pass this error to Elm
        console.log("error", err);
        return;
      }
    });
  });

  ports.toggleStatus?.subscribe((todoId) =&gt; {
    Meteor.call("todos.toggleStatus", todoId, (err: Error) =&gt; {
      if (err) {
        // Maybe we should pass this error to Elm
        console.log("error", err);
        return;
      }
    });
  });</code></pre>
</div>
</div>
<div class="paragraph">
<p>Here each time <code>addTodo</code> is called from <a href="https://elm-lang.org/">Elm</a>, we add a new Todo with a Meteor.call, same for the toggleStatus.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
Of course we should manage the error, maybe it could be a good exercice 😁
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Finally we need to send todos everytime the collection change.
To do that, we use <code>Tracker.autorun</code> that will run the callback when necessary.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-js" data-lang="js">  // We use the Tracker.autorun to send todos each time the fetch result
  // changes
  Tracker.autorun(() =&gt; {
    // Maybe one day we will need to manage the subscription
    const subscription = Meteor.subscribe("todos");

    const todos = TodosCollection.find({}, { sort: { createdAt: 1 } }).fetch();

    ports.receiveTodos?.send(
      todos.map((todo) =&gt; ({
        id: todo._id || "",
        value: todo.value,
        status: todo.status,
      }))
    );
  });</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now you can restart your server, open two browsers on <a href="http://localhost:3000" class="bare">http://localhost:3000</a> and see that everything is saved and sync 👏.</p>
</div>
<div class="videoblock">
<div class="content">
<video src="https://anthonnyquerouil.me/images/todo-elm-meteor.mp4" width="100%" controls>
Your browser does not support the video tag.
</video>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_conclusion">Conclusion</h2>
<div class="sectionbody">
<div class="paragraph">
<p>I hope you enjoyed this content as much as I enjoyed writing it.
Three weeks ago I was sad because I could not use <a href="https://www.meteor.com/">Meteor</a> with <a href="https://elm-lang.org/">Elm</a>, so I started using it with React and Typescript 😳.</p>
</div>
<div class="paragraph">
<p>Today, I dropped React and I use <a href="https://elm-lang.org/">Elm</a> again and it is really pleasant.</p>
</div>
<div class="paragraph">
<p>If you liked this post, do not hesitate to share it on your favorite social networks and if you are interested by this kind of content, you can follow me on twitter <a href="https://twitter.com/anthonny_q">@anthonny_q</a>.</p>
</div>
<div class="paragraph">
<p>If you have any feedbacks, comments are open and you can find the sources of the project here <a href="https://github.com/anthonny/meteor-elm-todos" class="bare">https://github.com/anthonny/meteor-elm-todos</a>.</p>
</div>
<div class="paragraph">
<p>Special thanks to <a href="https://forums.meteor.com/u/ni-ko-o-kin">ni-ko-o-kin</a>, as I was very inspired by his <a href="https://forums.meteor.com/t/meteor-elm-example/50244">post</a>.</p>
</div>
<div class="paragraph">
<p>Big thanks to <a href="https://twitter.com/yanndanthu">Yann Danthu</a> for the review of this post 😘.</p>
</div>
</div>
</div>]]></description><link>http://anthonnyquerouil.me/2020/06/17/how-i-use-meteor-elm-and-tailwindcss-together.html</link><guid isPermaLink="true">http://anthonnyquerouil.me/2020/06/17/how-i-use-meteor-elm-and-tailwindcss-together.html</guid><category><![CDATA[meteor]]></category><category><![CDATA[elm]]></category><category><![CDATA[parcel]]></category><category><![CDATA[tailwindcss]]></category><dc:creator><![CDATA[Anthonny Quérouil]]></dc:creator><pubDate>Wed, 17 Jun 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[The onboarding]]></title><description><![CDATA[<div class="paragraph">
<p>I&#8217;m currently working on two of my side projects, <a href="https://www.ohmydeck.com">ohmydeck!</a> and <strong>ohmypages!</strong> and even though they are separated projects, they are sharing the same architecture.</p>
</div>
<div class="paragraph">
<p><strong>ohmypages!</strong> is the new <a href="https://hubpress.github.io">HubPress</a> and if you used <a href="https://hubpress.github.io">HubPress</a> in the past, you already know the <code>config.json</code> that you have to configure before using your app:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">{
  "meta": {
    "username": "put your username here",
    "repositoryName": "put your repository name here",
    "branch": "master"
  },
  "theme": {
    "name": "Casper"
  }
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>This kind of stuff is ok for a developer maybe, but certainly not enough for great user experience.</p>
</div>
<div class="paragraph">
<p>My goal with my tools is to provide a UX between Jekyll and Wordpress or Ghost for <a href="https://www.ohmypages.com">ohmypages!</a> and between RevealJS with HTML and PowerPoint for <a href="https://www.ohmydeck.com">ohmydeck!</a>, so it was clear for me that I had something to do with the <strong>onboarding</strong>.</p>
</div>
<div class="paragraph">
<p>So, here is what I did</p>
</div>
<div class="paragraph">
<p>I started with a <strong>Welcome</strong>, I&#8217;m not sure about the content yet but&#8230;&#8203; we&#8217;ll see 😄</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/80398994-c9400f00-88b8-11ea-9a6e-f752bcc5b4e1.png" alt="80398994 c9400f00 88b8 11ea 9a6e f752bcc5b4e1">
</div>
</div>
<div class="paragraph">
<p>The second step can be different, it depends of if you are hosted on GitHub/GitLab or on a self hosted instance.</p>
</div>
<div class="paragraph">
<p>If you are using GitHub or GitLab, I can easily calculate the provider, the url of your repository, the name of the project, if you use <code>master</code> or <code>gh-pages</code>&#8230;&#8203;</p>
</div>
<div class="paragraph">
<p>So in this case, you will be redirect to the <strong>Authentication</strong> step. In the other case, you will see the step to define your <strong>Provider</strong>.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/80399006-ce04c300-88b8-11ea-95e4-c44d14cea1ca.png" alt="80399006 ce04c300 88b8 11ea 95e4 c44d14cea1ca">
</div>
</div>
<div class="paragraph">
<p>Then comes the Authentication step, since you will not be able to use the Basic Auth on GitHub, and because you can’t on GitLab too if you have 2FA activated, you will have to use a <strong>Personal token</strong>.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/80399009-ce9d5980-88b8-11ea-9bea-9bd34cc71216.png" alt="80399009 ce9d5980 88b8 11ea 9bea 9bd34cc71216">
</div>
</div>
<div class="paragraph">
<p>The password is not the one you set on GitLab or GitHub, it is a password you will have to define and it will be use to encrypt you personal token in your localStorage.
So you will not have to remember your personal token to login, only this password.</p>
</div>
<div class="paragraph">
<p>The last (maybe not 🤔) step is the configuration of your repository.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/80399010-cfce8680-88b8-11ea-9c14-ddfb3501848e.png" alt="80399010 cfce8680 88b8 11ea 9c14 ddfb3501848e">
</div>
</div>
<div class="paragraph">
<p>Here again, two possibilities, if you use GitLab or GitHub, all the fields are in read-only because they are calculated.
If not, you will have to fill the form by your own.</p>
</div>
<div class="paragraph">
<p>I think the onboarding is now really better than just a config.json file, maybe you agree, maybe not, feel free to give your feedback.</p>
</div>
<div class="paragraph">
<p>That&#8217;s all for today, in a future post, I&#8217;ll explain how I calculate all the stuff about GitHub and GitLab and how I validate datas.</p>
</div>]]></description><link>http://anthonnyquerouil.me/2020/04/27/the-onboarding.html</link><guid isPermaLink="true">http://anthonnyquerouil.me/2020/04/27/the-onboarding.html</guid><category><![CDATA[UX]]></category><category><![CDATA[maker]]></category><category><![CDATA[ohmypages]]></category><category><![CDATA[ohmydeck]]></category><dc:creator><![CDATA[Anthonny Quérouil]]></dc:creator><pubDate>Mon, 27 Apr 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[4 days ago, I joined a community of Makers]]></title><description><![CDATA[<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
If you see some mistakes, feel free to open a pull request <a href="https://github.com/anthonny/personal-blog/blob/gh-pages/_posts/2020-04-25-4-days-ago-i-joined-a-community-of-Makers.adoc">here</a> 👍.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Motivation during this special period comes and goes, it can be hard to be at our top level over time.</p>
</div>
<div class="paragraph">
<p>That&#8217;s why this week, I looked for joining a community of makers.</p>
</div>
<div class="paragraph">
<p>My goal is to meet people having the same mindset than me, people who want to create their own business and live on their on products.</p>
</div>
<div class="paragraph">
<p>There is a lot of community of makers, some are just forums and others are more than that:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="https://www.producthunt.com/makers">Product Hunt Makers</a>,</p>
</li>
<li>
<p><a href="https://wip.chat/">WIP Chat</a>,</p>
</li>
<li>
<p><a href="https://getmakerlog.com">Makerlog</a>,</p>
</li>
<li>
<p>&#8230;&#8203;</p>
</li>
</ul>
</div>
<div class="paragraph">
<p><a href="https://wip.chat/">WIP Chat</a> and <a href="https://getmakerlog.com">Makerlog</a> seems to be similar on some points, both are based on <strong>gamification</strong> using <strong>streaks</strong> to motivate you to ship stuff everyday.</p>
</div>
<div class="paragraph">
<p><a href="https://wip.chat/">WIP Chat</a> does not offer free-tier, you have to spend $20 to test their product.</p>
</div>
<div class="paragraph">
<p>So I decided to use <a href="https://getmakerlog.com">Makerlog</a> which is free and have a business model based on donations and a premium (<strong>gold</strong>) access with more features.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/80286284-c44d5500-872a-11ea-88f1-f07fb22d96fe.png" alt="80286284 c44d5500 872a 11ea 88f1 f07fb22d96fe">
</div>
</div>
<div class="paragraph">
<p><a href="https://getmakerlog.com">Makerlog</a> is the <strong>Home of the maker community</strong> where <strong>4,000+ indie hackers &amp; makers get things done together</strong>.</p>
</div>
<div class="paragraph">
<p>After my subscription on the site and a quick look around, I went on Telegram.
Honestly, I really like all the stuff on <a href="https://getmakerlog.com">Makerlog</a> but to me, the community is the best part of the product.</p>
</div>
<div class="paragraph">
<p>There are always makers talking kindly with positivity, it is so motivating to see them going further day after day, the community is awesome, really.</p>
</div>
<div class="paragraph">
<p>There is a lot to say about <a href="https://getmakerlog.com">Makerlog</a>, I will certainly write another post about all the features.</p>
</div>
<div class="paragraph">
<p>In the meantime, <strong>do not hesitate</strong> to register on <a href="https://getmakerlog.com">Makerlog</a> and share your maker journey.</p>
</div>]]></description><link>http://anthonnyquerouil.me/2020/04/25/4-days-ago-i-joined-a-community-of-makers.html</link><guid isPermaLink="true">http://anthonnyquerouil.me/2020/04/25/4-days-ago-i-joined-a-community-of-makers.html</guid><category><![CDATA[community]]></category><category><![CDATA[maker]]></category><category><![CDATA[makerlog]]></category><dc:creator><![CDATA[Anthonny Quérouil]]></dc:creator><pubDate>Sat, 25 Apr 2020 00:00:00 GMT</pubDate></item><item><title><![CDATA[WTF?! Tu ne veux pas de ma PR ?]]></title><description><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>Hier j&#8217;étais dans un jour de bonté, en soirée, je ne savais pas quoi faire de mon cul et je n&#8217;avais pas vraiment (du tout ?) envie de coder.
Je suis donc allé faire un tour sur le trending GitHub et je me suis dit :</p>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>Pourquoi je ne ferais pas une PR sur un des projets avec un truc super simple, une traduction de README en français par exemple ?</p>
</div>
</blockquote>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_le_choix_du_projet">Le choix du projet</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Je pars sur le <a href="http://github.com/trending">Trending GitHub</a>, je filtre sur les projets JavaScript (le langage que j&#8217;utilise le plus en ce moment) et je choisis le projet <a href="https://github.com/sindresorhus/ky">Ky</a>.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/45206024-40f49c80-b284-11e8-867c-8e12ed0b1560.png" alt="45206024 40f49c80 b284 11e8 867c 8e12ed0b1560">
</div>
</div>
<div class="paragraph">
<p>Au démarrage d&#8217;<a href="http://hubpress.github.com">HubPress</a>, j&#8217;avais adoré les PR avec tout ces README traduits.
Du coup me voilà parti dans ma traduction, 1 heure plus tard je soumets une PR, tout fier, tout content 😀.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_et_là_c_est_l_accident">Et là, c&#8217;est l&#8217;accident</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Ce matin je reçois une notification suite à un commentaire sur ma PR :</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://user-images.githubusercontent.com/2006548/45206979-d729c200-b286-11e8-8ce6-b5b59a9b94b1.png" alt="45206979 d729c200 b286 11e8 8ce6 b5b59a9b94b1">
</div>
</div>
<div class="paragraph">
<p>C&#8217;est très cordial, mais pour le coup ça m&#8217;a un peu refroidi.</p>
</div>
<div class="paragraph">
<p>Je prends un peu de recul, et finalement je me dis qu&#8217;il est tout à fait dans son droit.</p>
</div>
<div class="paragraph">
<p>Je n&#8217;ai surtout pas à me sentir frustré ou en colère.</p>
</div>
<div class="paragraph">
<p>Ce n&#8217;est pas parce que <strong>moi</strong>, je trouve cette PR intéressante, que <strong>lui</strong> doit en penser la même chose.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_ça_veut_dire_que_je_ne_dois_plus_contribuer">Ça veut dire que je ne dois plus contribuer ?</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Bah non, surtout pas, au contraire faut continuer mais différemment.</p>
</div>
<div class="paragraph">
<p>La solution est simple, avant d&#8217;ouvrir cette PR, j&#8217;aurai du ouvrir une issue pour avoir son avis sur mon objectif.</p>
</div>
<div class="paragraph">
<p>Les choses auraient été claire tout suite et aucun de nous deux n&#8217;aurait perdu son temps.</p>
</div>
<div class="paragraph">
<p>Je voulais partager ça avec vous, je pense surtout aux personnes qui font leur première contribution et qui tombe dans ce genre de situation.</p>
</div>
<div class="paragraph">
<p>Continuez, partagez, donnez de vous mais surtout&#8230;&#8203; <strong>commencez par ouvrir une issue</strong> !</p>
</div>
</div>
</div>]]></description><link>http://anthonnyquerouil.me/2018/09/07/wtf-tu-ne-veux-pas-de-ma-pr.html</link><guid isPermaLink="true">http://anthonnyquerouil.me/2018/09/07/wtf-tu-ne-veux-pas-de-ma-pr.html</guid><category><![CDATA[github]]></category><category><![CDATA[opensource]]></category><category><![CDATA[contribution]]></category><dc:creator><![CDATA[Anthonny Quérouil]]></dc:creator><pubDate>Fri, 07 Sep 2018 00:00:00 GMT</pubDate></item><item><title><![CDATA[Un Sens'it, Meteor et mon Rolling Spider décolle]]></title><description><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>Comme vous l&#8217;avez peut-être lu dans mon précédent billet
<a href="http://anthonnyquerouil.fr/2015/08/24/Sensit-mon-petit-objet-connecte.html" class="bare">http://anthonnyquerouil.fr/2015/08/24/Sensit-mon-petit-objet-connecte.html</a>, j&#8217;ai reçu récemment un <a href="https://www.sensit.io/">Sens&#8217;it</a>.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9440843/9e9e12bc-4a72-11e5-9485-cc94a6735fbf.JPG" alt="9e9e12bc 4a72 11e5 9485 cc94a6735fbf">
</div>
</div>
<div class="paragraph">
<p>J&#8217;ai aussi depuis peu un <a href="http://www.parrot.com/fr/produits/rolling-spider/">Rolling-spider</a> que je m&#8217;étais procuré dans le but de le controller avec un peu de code <a href="https://github.com/ChrisTheBaron/cylon-rolling-spider">JS</a> (c&#8217;est à cause de <a href="http://twitter.com/k33g_org">@k33g_org</a> tout ça). L&#8217;heure est venue de mixer le tout !</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://pbs.twimg.com/media/CNiIQfqWoAAhK9m.jpg" alt="CNiIQfqWoAAhK9m">
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_l_objectif">L&#8217;objectif</h2>
<div class="sectionbody">
<div class="paragraph">
<p>L&#8217;objectif est plutôt simple, double cliquer sur le <a href="https://www.sensit.io/">Sens&#8217;it</a> et faire décoller le <a href="http://www.parrot.com/fr/produits/rolling-spider/">Rolling-spider</a>.</p>
</div>
<div class="paragraph">
<p>Pour ce faire, voici ce qu&#8217;il nous faut :</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Un <a href="https://www.sensit.io/">Sens&#8217;it</a>,</p>
</li>
<li>
<p>Un Parrot Rolling Spider,</p>
</li>
<li>
<p>Un framework JS permettant de controller le spider (on utilisera <a href="https://github.com/voodootikigod/node-rolling-spider">node-rolling-spider</a> wrapper dans un package meteor <a href="https://atmospherejs.com/anthonny/rolling-spider">anthonny:rolling-spider</a>),</p>
</li>
<li>
<p>Un peu de Meteor pour lier le tout.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_l_architecture">L&#8217;architecture</h2>
<div class="sectionbody">
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9587833/f979b92c-5024-11e5-9fbf-20a14b2594b8.png" alt="f979b92c 5024 11e5 9fbf 20a14b2594b8">
</div>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Le <a href="https://www.sensit.io/">Sens&#8217;it</a> va transmettre un message sur le réseau SIGFOX,</p>
</li>
<li>
<p>Les serveurs SIGFOX vont communiquer aux serveurs AXIBLE qu&#8217;un message <strong>button</strong> a été envoyé,</p>
</li>
<li>
<p>AXIBLE va appelé l&#8217;URL de Callback que nous aurons défini dans l&#8217;interface d&#8217;administration,</p>
</li>
<li>
<p>Ce callback pointera sur une application <strong>sensit.meteor.com</strong> que nous aurons déployée au préalable,</p>
</li>
<li>
<p>Cette dernière enregistrera une trace de l&#8217;appel dans une collection Mongo,</p>
</li>
<li>
<p>Notre application cliente établira une connexion DDP avec l&#8217;application <strong>sensit.meteor.com</strong>, et observera les changements effectués sur cette collection,</p>
</li>
<li>
<p>Au premier changement détecté, on décolle.</p>
</li>
</ol>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_la_remote_app_sensit_meteor_com">La remote-app : sensit.meteor.com</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_définition_de_l_url_de_callback">Définition de l&#8217;URL de callback</h3>
<div class="paragraph">
<p>Dans l&#8217;interface <a href="https://www.sensit.io/">Sens&#8217;it</a>, nous allons spécifier une URL de callback pour les notifications de type <strong>button</strong>.</p>
</div>
<div class="paragraph">
<p>Pour rappel, ce callback est appelé via un GET et peut recevoir en query param certaines valeurs (via des variables {{my_var}}) :</p>
</div>
<div class="ulist">
<ul>
<li>
<p>device_id</p>
</li>
<li>
<p>device_serial_number</p>
</li>
<li>
<p>sensor_id</p>
</li>
<li>
<p>mode</p>
</li>
<li>
<p>notification_type</p>
</li>
<li>
<p>data</p>
</li>
<li>
<p>date</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Nous pouvons donc définir l&#8217;URL suivante :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">http://sensit.meteor.com/sensit-callback/button?device_id={{device_id}}&amp;device_serial_number={{device_serial_number}}&amp;sensor_id={{sensor_id}}&amp;mode={{mode}}&amp;notification_type={{notification_type}}&amp;data={{data}}&amp;date={{date}}</code></pre>
</div>
</div>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9629320/160ef888-5172-11e5-895a-460308bc2a5c.png" alt="160ef888 5172 11e5 895a 460308bc2a5c">
</div>
</div>
</div>
<div class="sect2">
<h3 id="_l_application_meteor">L&#8217;application Meteor</h3>
<div class="paragraph">
<p>Nous créons notre application <strong>meteor</strong> :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">meteor create remote-app
cd remote-app
meteor remove autopublish insecure
rm remote-app.*
mkdir server
touch server/main.js</code></pre>
</div>
</div>
<div class="paragraph">
<p>Pour la gestion des routes, on utilisera <a href="http://iron-meteor.github.io/iron-router/">iron-router</a> :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript">meteor add iron:router</code></pre>
</div>
</div>
<div class="paragraph">
<p>Commençons par créer une collection <code>notification</code> qui aura pour but de stocker les différentes notifications reçues.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript">Notification = new Meteor.Collection("notification");</code></pre>
</div>
</div>
<div class="paragraph">
<p>Il faut ensuite définir notre endpoint qui répondra à l&#8217;appel du callback.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript">Router.route('/sensit-callback/:type', {where: 'server'})
  .get(function () {
    if (['temperature', 'motion', 'button'].indexOf(this.params.type) &lt; 0)
      throw new Error('Invalid type');

    var notification = _.extend({type: this.params.type}, this.params.query, {data: JSON.parse(this.params.query.data)});

    Notification.insert(notification);
    this.response.end('notification ' + notification.type + ' saved\n');
  });</code></pre>
</div>
</div>
<div class="paragraph">
<p>Nous définissons une route <code>/sensit-callback/:type</code> dans laquelle <code>:type</code> peut prendre une des valeurs suivantes <code>['temperature', 'motion', 'button']</code>.</p>
</div>
<div class="paragraph">
<p>On construit ensuite un objet <code>notification</code> à partir du <code>type</code> et des queryParam (on force la conversion en JSON du paramètre <code>data</code> car c&#8217;est un objet JSON stringifié).</p>
</div>
<div class="paragraph">
<p>Enfin on insère notre <code>notification</code> en base et on répond en confirmant l&#8217;enregistrement.</p>
</div>
<div class="paragraph">
<p>Pour que les informations présentes dans notre collection soient accessibles par notre "client", il faut les publier (au passage, on va gérer les autres types) :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript">Meteor.publish("remote-button", function (argument) {
  return Notification.find({type: 'button'});
});
Meteor.publish("remote-temperature", function (argument) {
  return Notification.find({type: 'temperature'});
});
Meteor.publish("remote-motion", function (argument) {
  return Notification.find({type: 'motion'});
});</code></pre>
</div>
</div>
<div class="paragraph">
<p>Le code complet :</p>
</div>
<div class="listingblock">
<div class="title">remote-app/server/main.js</div>
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript">Notification = new Meteor.Collection("notification");

Router.route('/sensit-callback/:type', {where: 'server'})
  .get(function () {
    if (['temperature', 'motion', 'button'].indexOf(this.params.type) &lt; 0)
      throw new Error('Invalid type');

    var notification = _.extend({type: this.params.type}, this.params.query, {data: JSON.parse(this.params.query.data)});

    Notification.insert(notification);
    this.response.end('notification ' + notification.type + ' saved\n');
  });

Meteor.publish("remote-temperature", function (argument) {
  return Notification.find({type: 'temperature'});
});
Meteor.publish("remote-motion", function (argument) {
  return Notification.find({type: 'motion'});
});
Meteor.publish("remote-button", function (argument) {
  return Notification.find({type: 'button'});
});</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_un_peu_de_test">Un peu de test</h3>
<div class="paragraph">
<p>On démarre l&#8217;application :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript">meteor</code></pre>
</div>
</div>
<div class="paragraph">
<p>On requête l&#8217;url :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">http://localhost:3000/sensit-callback/button?device_serial_number=ABCDE&amp;notification_type=generic_punctual&amp;data=%7B%22first_name%22%3A%22Anthonny%22%2C%22sensit_name%22%3A%22%22%2C%22last_name%22%3A%22Querouil%22%2C%22device_id%22%3A%22ABCDE%22%7D&amp;device_id=1234&amp;sensor_id=5678&amp;date=2015-09-01T17%3A37Z&amp;mode=6</code></pre>
</div>
</div>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9629955/06bdd084-5177-11e5-8e5b-1aa1478a6413.png" alt="06bdd084 5177 11e5 8e5b 1aa1478a6413">
</div>
</div>
<div class="paragraph">
<p>Le service répond correctement, et notre <code>notification</code> est bien enregistrée :</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9630035/b0f0abee-5177-11e5-95dd-1dd622648fce.png" alt="b0f0abee 5177 11e5 95dd 1dd622648fce">
</div>
</div>
</div>
<div class="sect2">
<h3 id="_le_déploiement">Le déploiement</h3>
<div class="paragraph">
<p>L&#8217;application sera déployée sur l&#8217;URL <strong>sensit.meteor.com</strong> :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript">meteor deploy sensit.meteor.com</code></pre>
</div>
</div>
<div class="paragraph">
<p>Pour valider le bon déploiement, on peut reprendre le test effectué au préalable et le faire pointer sur notre "production" :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">http://sensit.meteor.com/sensit-callback/button?device_serial_number=ABCDE&amp;notification_type=generic_punctual&amp;data=%7B%22first_name%22%3A%22Anthonny%22%2C%22sensit_name%22%3A%22%22%2C%22last_name%22%3A%22Querouil%22%2C%22device_id%22%3A%22ABCDE%22%7D&amp;device_id=1234&amp;sensor_id=5678&amp;date=2015-09-01T17%3A37Z&amp;mode=6</code></pre>
</div>
</div>
<div class="paragraph">
<p>Enfin, on vérifie que la <code>notification</code> est bien présente en base :</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9630272/66e493a6-5179-11e5-9230-36ecf85d83e1.png" alt="66e493a6 5179 11e5 9230 36ecf85d83e1">
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_la_local_app_sensit_meteor_rs">La local-app : sensit-meteor-rs</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Nous avons désormais un <strong>backend</strong> qui prend en compte les différentes notifications, il nous faut maintenant une application qui tournera <strong>localement</strong> et qui réagira aux changements qui surviennent dans le backend.</p>
</div>
<div class="sect2">
<h3 id="_l_application">L&#8217;application</h3>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-shell" data-lang="shell">meteor create local-app
cd local-app
meteor remove autopublish insecure
rm local-app.*
mkdir server
touch server/main.js</code></pre>
</div>
</div>
<div class="paragraph">
<p>Nous allons initier une connexion <a href="https://www.meteor.com/ddp">DDP</a> avec notre <strong>backend</strong> et écouter les changements qui sont faits sur la collection <code>notification</code>.
Pour chaque notification ajoutée dans cette collection que nous appellerons <code>RemoteNotification</code>, nous ajouterons une copie dans notre collection <strong>locale</strong> <code>Notification</code> :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript">// Déclaration de la connexion
var remote = DDP.connect('http://sensit.meteor.com/');
var RemoteNotification = new Meteor.Collection('notification', { connection: remote });
remote.subscribe('remote-button');

// On écoute les changements effectués sur la collection en Remote
RemoteNotification.find().observe({
  added: function(notification) {
    console.log('-- remote item added --');
    // On upsert dans la collection de Notification locale
    Notification.upsert({notification._id}, {$set: notification});
  }
});</code></pre>
</div>
</div>
<div class="paragraph">
<p>Il ne nous reste plus qu&#8217;à faire décoller le spider lorsqu&#8217;une <code>notification</code> est ajoutée en local :</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript">var rollingSpider = new RollingSpider();

rollingSpider.connect(Meteor.bindEnvironment(function () {
  rollingSpider.setup(Meteor.bindEnvironment(function () {
    rollingSpider.flatTrim();
    rollingSpider.startPing();
    rollingSpider.flatTrim();

    // On observe la collection Notification, au premier ajout on decolle !
    Notification.find().observe({
      added: function (notification) {
        rollingSpider.takeOff();
        rollingSpider.flatTrim();
      }
    });
  }));
}));</code></pre>
</div>
</div>
<div class="paragraph">
<p>Le code complet :</p>
</div>
<div class="listingblock">
<div class="title">local-app/server/main.js</div>
<div class="content">
<pre class="highlight"><code class="language-javascript" data-lang="javascript">var Notification = new Meteor.Collection("notification");
var remote = DDP.connect('http://sensit.meteor.com/');
var RemoteNotification = new Meteor.Collection('notification', { connection: remote });
var isFlying = false;


RemoteNotification.find().observe({
  added: function(notification) {
    console.log('-- remote item --');
    console.log(notification);
    Notification.upsert({_id: notification._id}, {$set: notification});
  }
});
remote.subscribe('remote-button');

rollingSpider.connect(Meteor.bindEnvironment(function () {
  rollingSpider.setup(Meteor.bindEnvironment(function () {
    rollingSpider.flatTrim();
    rollingSpider.startPing();
    rollingSpider.flatTrim();

    Notification.find().observe({
      added: function (notification) {
        if (!isFlying) {
          isFlying = true;
          rollingSpider.takeOff();
          rollingSpider.flatTrim();
        }
      }
    });
  }));
}));</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_décollage">Décollage !</h3>
<div class="videoblock">
<div class="content">
<iframe src="https://www.youtube.com/embed/8DY4bsKOm5g?rel=0" frameborder="0" allowfullscreen></iframe>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_conclusion">Conclusion</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Ce billet est l&#8217;occasion de mettre en avant la connexion entre deux applications <a href="https://www.meteor.com">Meteor</a> via le protocole <a href="https://www.meteor.com/ddp">DDP</a> et de vous montrer qu&#8217;avec du javascript, on se marre bien (en tout cas, c&#8217;est vrai pour moi :) ).</p>
</div>
<div class="paragraph">
<p>Si vous avez des projets similaires, n&#8217;hésitez pas à m&#8217;en faire part, ce sera un plaisir d&#8217;échanger dessus.</p>
</div>
</div>
</div>]]></description><link>http://anthonnyquerouil.me/2015/08/31/Un-Sensit-Meteor-et-mon-Rolling-Spider-decolle.html</link><guid isPermaLink="true">http://anthonnyquerouil.me/2015/08/31/Un-Sensit-Meteor-et-mon-Rolling-Spider-decolle.html</guid><category><![CDATA[Sensit]]></category><category><![CDATA[IoT]]></category><category><![CDATA[Sigfox]]></category><category><![CDATA[Meteor]]></category><category><![CDATA[Parrot]]></category><category><![CDATA[Rolling Spider]]></category><dc:creator><![CDATA[Anthonny Quérouil]]></dc:creator><pubDate>Mon, 31 Aug 2015 00:00:00 GMT</pubDate></item><item><title><![CDATA[Sens'it, mon petit objet connecté]]></title><description><![CDATA[<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>Il y a 5 jours, je suis tombé sur un tweet de <a href="https://twitter.com/cgiorgi">Cédric Giorgi</a> et je me suis empressé de répondre :</p>
</div>
<div class="paragraph">
<p>
<blockquote class="twitter-tweet" lang="fr"><p lang="fr" dir="ltr"><a href="https://twitter.com/cgiorgi">@cgiorgi</a> carrément! :)</p>&mdash; Anthonny Quérouil (@anthonny_q) <a href="https://twitter.com/anthonny_q/status/633917038256369664">19 Août 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
</p>
</div>
<div class="paragraph">
<p>Ce matin, j&#8217;ai reçu mon nouveau jouet (plutôt rapide :D )</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_déballage_et_activation">Déballage et activation</h2>
<div class="sectionbody">
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9440797/3571c07c-4a72-11e5-95dd-9deff6bc8b7d.JPG" alt="3571c07c 4a72 11e5 95dd 9deff6bc8b7d">
</div>
</div>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9440843/9e9e12bc-4a72-11e5-9485-cc94a6735fbf.JPG" alt="9e9e12bc 4a72 11e5 9485 cc94a6735fbf">
</div>
</div>
<div class="paragraph">
<p>La documentation tient sur un petit carton, mais c&#8217;est suffisant pour l&#8217;initialisation et l&#8217;utilisation de l&#8217;appareil.</p>
</div>
<div class="paragraph">
<p>L&#8217;activation du produit se fait en scannant le <strong>QR code</strong> au dos du <a href="http://www.sensit.io">Sens&#8217;it</a>, on est dirigé sur la page de création de compte : un formulaire avec email et mot de passe.</p>
</div>
<div class="paragraph">
<p>Après avoir créé son compte, on peut suivre un tutoriel permettant de découvrir comment utiliser l&#8217;appareil. C&#8217;est plutôt bien fait, à chaque étape on doit manipuler le <a href="http://www.sensit.io">Sens&#8217;it</a> et la page de tutoriel réagit en fonction.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_interface_et_configuration">Interface et configuration</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Après s&#8217;être authentifié, l&#8217;accès à l&#8217;interface utilisateur du <a href="http://www.sensit.io">Sens&#8217;it</a> se fait via l&#8217;url :</p>
</div>
<div class="paragraph">
<p><a href="https://www.sensit.io/home" class="bare">https://www.sensit.io/home</a></p>
</div>
<div class="sect2">
<h3 id="_mon_sens_it">Mon Sens&#8217;it</h3>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9441142/8e47223a-4a74-11e5-963d-fdeb49cd42ef.png" alt="8e47223a 4a74 11e5 963d fdeb49cd42ef">
</div>
</div>
<div class="paragraph">
<p>Je pense que cette page présente le mode actif sur le <a href="http://www.sensit.io">Sens&#8217;it</a> (je dis je pense car au moment où j&#8217;ai pris mon screen, mon <a href="http://www.sensit.io">Sens&#8217;it</a> était en mode <strong>Mouvement</strong> et non <strong>Complet</strong>)</p>
</div>
<div class="sect3">
<h4 id="_historique">Historique</h4>
<div class="paragraph">
<p>Pour le <strong>Mode affiché</strong> et le <strong>Bouton</strong>, on peut consulter l&#8217;historique :</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9441816/8ecde280-4a78-11e5-91d9-79ce65443f4a.png" alt="8ecde280 4a78 11e5 91d9 79ce65443f4a">
</div>
</div>
<div class="paragraph">
<p><strong>NB</strong>: je pense qu&#8217;un bug est survenu dans l&#8217;affichage de l&#8217;historique de la température car je dois normalement avoir quelquechose (ci-dessous l&#8217;historique <strong>Température</strong> passant par le menu <strong>Tout les modes</strong>) :</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9441951/25193992-4a79-11e5-9c38-2942e0e4ae4a.png" alt="25193992 4a79 11e5 9c38 2942e0e4ae4a">
</div>
</div>
</div>
<div class="sect3">
<h4 id="_notifications">Notifications</h4>
<div class="paragraph">
<p>On peut aussi gérer le processus de <strong>Notification</strong>, que ce soit pour le mode ou pour le bouton :</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9442015/985534a6-4a79-11e5-9901-7a2f4f9ef935.png" alt="985534a6 4a79 11e5 9901 7a2f4f9ef935">
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_tout_les_modes">Tout les modes</h3>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9442056/e8abfc00-4a79-11e5-820e-648f1fffe669.png" alt="e8abfc00 4a79 11e5 820e 648f1fffe669">
</div>
</div>
<div class="paragraph">
<p>On y retrouve les mêmes fonctionnalités mais pour l&#8217;ensemble des modes, ce qui est plutôt pratique pour avoir une vue d&#8217;ensemble.</p>
</div>
<div class="paragraph">
<p><strong>NB</strong>: dans tout les modes présents, je n&#8217;ai pas trouvé celui concernant la détection sonore.</p>
</div>
</div>
<div class="sect2">
<h3 id="_mon_compte">Mon compte</h3>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9442314/9694a21c-4a7b-11e5-8976-93722c6f9cdf.png" alt="9694a21c 4a7b 11e5 8976 93722c6f9cdf">
</div>
</div>
<div class="paragraph">
<p>Cette page présente les informations standards d&#8217;une page de compte, avec en plus la possibilité de lier ce dernier avec Facebook ou Twitter.</p>
</div>
<div class="paragraph">
<p>C&#8217;est aussi ici qu&#8217;on active l'<strong>Accès développeur</strong> qui permet notamment d&#8217;obtenir le fameux <strong>token d&#8217;accès</strong> pour l&#8217;utilisation de l&#8217;<a href="https://api.sensit.io/">API</a></p>
</div>
</div>
<div class="sect2">
<h3 id="_reconfiguration">Reconfiguration</h3>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9442474/65ba1626-4a7c-11e5-9783-abbe0e47e1f3.png" alt="65ba1626 4a7c 11e5 9783 abbe0e47e1f3">
</div>
</div>
<div class="paragraph">
<p>Pour la température, on peut changer l&#8217;intervalle de temps qui doit s&#8217;écouler entre deux remontées d&#8217;informations.</p>
</div>
<div class="paragraph">
<p>Pour la détection de mouvements, on peut jouer sur la sensibilité jusqu&#8217;à rendre le <a href="http://www.sensit.io">Sens&#8217;it</a> très sensible.</p>
</div>
<div class="paragraph">
<p><strong>NB</strong>: on ne trouve pas d&#8217;entrée pour jouer sur la sensibilité de la détection sonore (l&#8217;<a href="https://api.sensit.io/">API</a> fournit pourtant un service de configuration qui prend en compte cette donnée).</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_l_api_sens_it">L&#8217;API Sens&#8217;it</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Pour les développeurs, <a href="http://www.sensit.io">Sens&#8217;it</a> propose une <a href="https://api.sensit.io/">API</a> REST.</p>
</div>
<div class="paragraph">
<p>Cette dernière est assez simple et permet les actions suivantes :</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Récupération du token d&#8217;accès via login/password</p>
</li>
<li>
<p>Lister les devices</p>
</li>
<li>
<p>Consulter les informations d&#8217;un device</p>
</li>
<li>
<p>Changer la configuration d&#8217;un device</p>
</li>
<li>
<p>Récupérer les informations d&#8217;un capteur.</p>
</li>
</ul>
</div>
<div class="sect2">
<h3 id="_petit_test_rapide">Petit test rapide</h3>
<div class="imageblock">
<div class="content">
<img src="https://cloud.githubusercontent.com/assets/2006548/9443116/a668bdc8-4a7f-11e5-914d-23189e0a2f92.png" alt="a668bdc8 4a7f 11e5 914d 23189e0a2f92">
</div>
</div>
<div class="paragraph">
<p>Je n&#8217;ai eu aucun problème pour faire mon test, j&#8217;ai activé le mode développeur, récupéré mon token et j&#8217;ai bêtement suivi la documentation fournie pour consommer l&#8217;<a href="https://api.sensit.io/">API</a>.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_conclusions">Conclusions</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Je trouve le produit <strong>super sympa</strong> et le fait de me l&#8217;avoir envoyé pour un test <strong>hyper cool</strong>.</p>
</div>
<div class="paragraph">
<p>Il me reste certains points à éclaircir :</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Est-ce que la détection sonore est gérée sur le modèle ? Ou est-ce prévu pour une version future ?</p>
</li>
<li>
<p>L&#8217;<a href="https://api.sensit.io/">API</a> présente une gestion multi devices mais l&#8217;interface utilisateur semble faite pour gérer un unique <a href="http://www.sensit.io">Sens&#8217;it</a>. Comment on fait si on vu jouer avec deux devices ?</p>
</li>
<li>
<p>Existe-t-il un bugs tracker (Github, Bitbucket &#8230;&#8203;) ? Peut-on contribuer ?</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>C&#8217;est une présentation rapide du <a href="http://www.sensit.io">Sens&#8217;it</a> et je vais continuer à approfondir.</p>
</div>
<div class="paragraph">
<p>Qu&#8217;en pensez-vous, est-ce que le <a href="http://www.sensit.io">Sens&#8217;it</a> vous plait ? Peut-être en avez-vous également un, faites le moi savoir ;)</p>
</div>
</div>
</div>]]></description><link>http://anthonnyquerouil.me/2015/08/24/Sensit-mon-petit-objet-connecte.html</link><guid isPermaLink="true">http://anthonnyquerouil.me/2015/08/24/Sensit-mon-petit-objet-connecte.html</guid><category><![CDATA[Sensit]]></category><category><![CDATA[IoT]]></category><category><![CDATA[Sigfox]]></category><dc:creator><![CDATA[Anthonny Quérouil]]></dc:creator><pubDate>Mon, 24 Aug 2015 00:00:00 GMT</pubDate></item></channel></rss>