#TernopilJS

MeteorJS

Лекція 11. Going to production

Лектор - Михайло

Код - github.com/ternopiljs

Презентації - slides.com/ternopiljs

Новини - vk.com/ternopiljs

  • Безпека

  • Рефакторинг

  • Продуктивність

Безпека

Приховання секретних ключів

S3.config = {
    key: '12345678901234567890',
    secret: '098765432109876543210987654321',
    bucket: 'top-secret-bucket'
};
S3.config = {
    key: Meteor.settings.aws.key,
    secret: Meteor.settings.aws.secret,
    bucket: Meteor.settings.aws.bucket
};

Безпека

Приховання секретних ключів

{
  "public": {
    "aws": {
      "bucketUrl": "https://my-meteor-example.s3.amazonaws.com",
    }
  },
  "aws": {
    "accessKey": "12345678901234567890",
    "secret": "098765432109876543210987654321",
    "bucket": "top-secret-bucket"
  }   
}
config/production/settings.json

Безпека

Приховання секретних ключів

$ meteor run --settings config/development/settings.json
$ METEOR_SETTINGS={"public": {"key": "123"}} meteor run
> Meteor.settings.aws
undefined
> Meteor.settings.aws
{
  accessKey: "12345678901234567890",
  secret: "098765432109876543210987654321",
  bucket: "top-secret-bucket"
} 
client
server

Клієнт має доступ тільки до властивості public!

> Meteor.settings
{
  public: {
    key: "123"
  }
}

Безпека

Приховання серверного коду

if (Meteor.isClient) {
  // do some stuff
}

if (Meteor.isServer) {
  ServiceConfiguration.configurations.insert({
    service: "instagram",
    clientId: Meteor.settings.instagram.clientId,
    secret: Meteor.settings.instagram.secret,
    scope: 'basic+likes+comments'
  });
}
both/accounts.js

Безпека

Приховання серверного коду

ServiceConfiguration.configurations.insert({
  service: "instagram",
  clientId: Meteor.settings.instagram.clientId,
  secret: Meteor.settings.instagram.secret,
  scope: 'basic+likes+comments'
});
server/accounts.js
// do some stuff
client/accounts.js

Безпека

Обмеження доступу до БД

$ meteor remove autopublish & meteor remove insecure

Безпека

Приховання полів при публікації

Meteor.publish('users', function(limit) {
  return Meteor.users.find({}, {limit: limit}); 
);
Meteor.publish('users', function(limit) {
  var projection = { 
    'username': 1,
    'createdAt': 1,
    'avatar': 1
  };
  return Meteor.users.find({}, {fields: projection, limit: limit}); 
);

Безпека

Приховання полів при публікації

Meteor.publish(null, function(limit) {
  if (this.userId) {
    var projection = { 
      'settings': 1,
      'roles': 1
    };
    return Meteor.users.find({}, {fields: projection, limit: limit}); 
  } else {
    this.ready();
  }
});

Розширення документа поточного користувача

Meteor.publish(null, function() {
  // some stuff
});

Публікація, ім'я якої встановлене як null, автоматично відправляє дані до всіх під'єднаних клієнтів.

Безпека

Валідація даних

Meteor.methods({
  'messages/new': function (message) {
    message.createdAt = new Date;
    return Messages.insert(message);
  }
});

Template.Messages.events({
  'submit form': function (e) {
    var message;
    // ... формуєм повідомлення
    Meteor.call('messages/new', message);
  }
});
server
client

Безпека

Валідація даних

// server
Meteor.methods({
  'messages/new': function (message) {
    message.createdAt = new Date;
    return Messages.insert(message);
  }
});

// client
Template.Messages.events({
  'submit form': function (e) {
    var message;
    // ... формуєм повідомлення
    check(message, { 
      userId: this.userId,
      conversationId: String,
      addresseeId : Match.Optional(String),
      text: Match.Optional(String),
      attachments: Match.Optional({
        images: Match.Optional([String]),
        docs: Match.Optional([String])
      }),
    });
    Meteor.call('messages/new', message);
  }
});
server
client

Безпека

Валідація даних на сервері

//server
Meteor.methods({
  'messages/new': function (message) {
    check(message, { 
      userId: this.userId,
      conversationId: String,
      addresseeId : Match.Optional(String),
      text: Match.Optional(String),
      attachments: Match.Optional({
        images: Match.Optional([String]),
        docs: Match.Optional([String])
      }),
    });
    message.createdAt = new Date;
    return Messages.insert(message);
  }
});
// client
Template.Messages.events({
  'submit form': function (e) {
    var message;
    // ... формуєм повідомлення
    Meteor.call('messages/new', message);
  }
});
server
client

Безпека

User profile services

Meteor.users.deny({
  update: function () {
    return true;
  }
});

Meteor.services - editable by default

Sollution:

Безпека

Захист від DDoS

$ meteor add meteorhacks:sikka

Безпека

Content Security Policy/X-Frame-Options

$ meteor add browser-policy
Meteor.startup(function() {
  BrowserPolicy.content.allowImageOrigin("https://*.googleusercontent.com");
  BrowserPolicy.content.allowImageOrigin("http://profile.ak.fbcdn.net");
  BrowserPolicy.content.allowImageOrigin("http://graph.facebook.com");
  BrowserPolicy.content.disallowInlineScripts(); // забороняє виконання inline javascript
});
server

Рефакторинг

Events

Template.Comment.events({
  'click .like': function (e) {
    var text = $(e.target).closest('.comment-wrapper')
      .find('.comment-text')
      .text();
  }
});
Template.Comment.events({
  'click .like': function (e) {
    var instance = Template.instance();
    var text = instance.$('.comment-text').text();
  }
});

Рефакторинг

Scoped reactivity

// тільки в крайній необхідності
var limit = new ReactiveVar;

Template.Comment.helpers({
  limit: function () {
    return limit.get();
  }
});

Template.Comment.helpers({
  limit: function () {
    return Template.instance().limit.get();
  }
});

Template.Comment.onCreated(function () {
  this.limit = new ReactiveVar;
});

Рефакторинг

Scoped autorun/subscribe

Template.Comment.onRendered(function () {
  Tracker.autorun(function () { /*...*/ });
  Meteor.subscribe(/*...*/);
});
Template.Comment.onRendered(function () {
  this.autorun(function () { /*...*/ });
  this.subscribe(/*...*/);
});

Рефакторинг

Templates

<template name="Comments">
  {{#each comments}}
    <div>{{text}}</div>
    {{#with likes}}
      <div>{{count}}</div>
    {{/with}}
  {{/each}}
</template>
<template name="Comments">
  {{#each comments}}
    {{> Comment}}
  {{/each}}
</template>

Рефакторинг

Templates

<template name="Comments">
  {{#each comments}}
    <div>{{text}}</div>
    {{#with likes}}
      <div>{{count}}</div>
    {{/with}}
  {{/each}}
</template>
<template name="Comments">
  {{#each comments}}
    {{> Comment}}
  {{/each}}
</template>

Рефакторинг

Meteor style guide

Рефакторинг

JSHint

$ npm install jshint -g
$ jshint .

Продуктивність

Відключення реактивності

Comments.find({ postId: this.postId }, { reactive: false });

Продуктивність

Kadira

Продуктивність

this.unblock()

Meteor.methods({
  getLocation: function() {
    Meteor.inServer && Meteor._sleepForMs(2000);
  }
});
Meteor.methods({
  getLocation: function() {
    this.unblock();
    Meteor.inServer && Meteor._sleepForMs(2000);
  }
});
if (Meteor.isClient) {
  Meteor.call('trackLocation');
  Meteor.subscribe('posts');
}

Продуктивність

Send less data

Meteor.publish('getRecentPosts', function () {
  return Posts.find({}, {limit: 50});
});
Meteor.publish('getRecentPosts', function () {
  var options = {
    sort: {date: -1},
    limit: 50,
    fields: {topic:1, content: 1}
  };

  return Posts.find({}, options);
});

Продуктивність

Cached subscriptions

$ meteor add meteorhacks:subs-manager
var subscriptions = new SubsManager();

Router.config({
  waitOn: function () {
    return Meteor.subscribe('notifications');
  }
});

Router.route('/', {
  name: 'home',
  waitOn: function () {
    return subscriptions.subscribe('posts');
  }
});
  • DDP.
  • Deploy.

У наступній лекції:

Д/З

?

Lecture #11 - Going to production

By ternopiljs

Lecture #11 - Going to production

  • 1,287