Posted on 2 Comments

SpeechKit: A Javascript Package For The Web Speech API (Speech Synthesis & Speech Recognition)

Speech Recognition & Speech Synthesis In The Browser With Web Speech API

Voice apps are now first-class citizens on the web thanks to the Speech Recognition and the Speech Synthesis interfaces which are a part of the bigger Web Speech API. Taken from the MDN docs

The Web Speech API makes web apps able to handle voice data. There are two components to this API:

  • Speech recognition is accessed via the SpeechRecognition interface, which provides the ability to recognize voice context from an audio input (normally via the device’s default speech recognition service) and respond appropriately. Generally you’ll use the interface’s constructor to create a new SpeechRecognition object, which has a number of event handlers available for detecting when speech is input through the device’s microphone. The SpeechGrammar interface represents a container for a particular set of grammar that your app should recognize. Grammar is defined using JSpeech Grammar Format (JSGF.)
  • Speech synthesis is accessed via the SpeechSynthesis interface, a text-to-speech component that allows programs to read out their text content (normally via the device’s default speech synthesizer.) Different voice types are represented by SpeechSynthesisVoice objects, and different parts of text that you want to be spoken are represented by SpeechSynthesisUtterance objects. You can get these spoken by passing them to the SpeechSynthesis.speak() method.
Brief on Web Speech API from MDN

So basically with the Web Speech API you can work with voice data. You can make your apps speak to its users and you can run commands based on what your user speaks. This opens up a host of opportunities for voice-activated CLIENT-SIDE apps. I love building open-source software, so I decided to create an NPM package to work with the Web Speech API called SpeechKit and I couldn’t wait to share it with you! I suppose this is a continuation of Creating A Voice Powered Note App Using Web Speech

Simplifying The Process With SpeechKit

I decided starting this year I would contribute more to the open-source community and provide packages (primarily Javascript, PHP, and Rust) to the world to use. I use the Web Speech API a lot in my personal projects and so why not make it an NPM package? You can find the source code here.

Listen To Some Hacker Music While You Code

Follow me on Spotify I make Tech Trap music

Features

  • Speak Commands
  • Listen for voice commands
  • Add your own grammar
  • Transcribe words and output as file.
  • Generate SSML from text
npm install @mastashake08/speech-kit

Import

import SpeechKit from '@mastashake08/speech-kit'

Instantiate A New Instance

new SpeechKit(options)

listen()

Start listening for speech recognition.

stopListen()

Stop listening for speech recognition.

speak(text)

Use Speech Synthesis to speak text.

Param Type Description
text string Text to be spoken

getResultList() ⇒ SpeechRecognitionResultList

Get current SpeechRecognition resultsList.

Returns: SpeechRecognitionResultList – – List of Speech Recognition results

getText() ⇒ string

Return text

Returns: string – resultList as text string

getTextAsFile() ⇒ Blob

Return text file with results.

Returns: Blob – transcript

getTextAsJson() ⇒ object

Return text as JSON.

Returns: object – transcript

addGrammarFromUri()

Add grammar to the SpeechGrammarList from a URI.

Params: string uri – URI that contains grammar

addGrammarFromString()

Add grammar to the SpeechGrammarList from a Grammar String.

Params: string grammar – String containing grammar

getGrammarList() ⇒ SpeechGrammarList

Return current SpeechGrammarList.

Returns: SpeechGrammarList – current SpeechGrammarList object

getRecognition() ⇒ SpeechRecognition

Return the urrent SpeechRecognition object.

Returns: SpeechRecognition – current SpeechRecognition object

getSynth() ⇒ SpeechSynthesis

Return the current Speech Synthesis object.

Returns: SpeechSynthesis – current instance of Speech Synthesis object

getVoices() ⇒ Array<SpeechSynthesisVoice>

Return the current voices available to the user.

Returns: Array<SpeechSynthesisVoice> – Array of available Speech Synthesis Voices

setSpeechText()

Set the SpeechSynthesisUtterance object with the text that is meant to be spoken.

Params: string text – Text to be spoken

setSpeechVoice()

Set the SpeechSynthesisVoice object with the desired voice.

Params: SpeechSynthesisVoice voice – Voice to be spoken

getCurrentVoice() ⇒ SpeechSynthesisVoice

Return the current voice being used in the utterance.

Returns: SpeechSynthesisVoice – current voice

Example Application

In this example vue.js application there will be a text box with three buttons underneath, when the user clicks the listen button, SpeechKit will start listening to the user. As speech is detected, the text will appear in the text box. The first button under the textbox will tell the browser to share the page, the second button will speak the text in the textbox while the third button will control recording.

Home page from the github.io page

I created this in Vue.js and (for sake of time and laziness) I reused all of the defaul components and rewrote the HelloWorld component. So let’s get started by creating a new Vue application.

Creating The Application

Open up your terminal and input the following command to create a new vue application:

vue create speech-kit-demo

It doesn’t really matter what settings you choose, after you get that squared away, now it is time to add our dependecy.

Installing SpeechKit

Still inside your terminal we will add the SpeechKit dependency to our package.json file with the following command:

npm install @mastashake08/speech-kit

Now with that out of the way we can begin creating our component functionality.

Editing HelloWorld.vue

Open up your HelloWorld.vue file in your components/ folder and change it to look like this:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>
      Simple demo to demonstrate the Web Speech API using the
      <a href="https://github.com/@mastashake08/speech-kit" target="_blank" rel="noopener">SpeechKit npm package</a>!
    </p>
    <textarea v-model="voiceText"/>
    <ul>
      <button @click="share" >Share</button>
      <button @click="speak">Speak</button>
      <button @click="listen" v-if="!isListen">Listen</button>
      <button @click="stopListen" v-else>Stop Listen</button>
    </ul>
  </div>
</template>

<script>
import SpeechKit from '@mastashake08/speech-kit'
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  mounted () {
    this.sk = new SpeechKit({rate: 0.85})
    document.addEventListener('onspeechkitresult', (e) => this.getText(e))
  },
  data () {
    return {
      voiceText: 'SPEAK ME',
      sk: {},
      isListen: false
    }
  },
  methods: {
    share () {
      const text = `Check out the SpeechKit Demo and speak this text! ${this.voiceText} ${document.URL}`
      try {
        if (!navigator.canShare) {
          this.clipBoard(text)
        } else {
          navigator.share({
            text: text,
            url: document.URL
          })
        }
      } catch (e) {
        this.clipBoard(text)
      }
    },
    async clipBoard (text) {
      const type = "text/plain";
      const blob = new Blob([text], { type });

      const data = [new window.ClipboardItem({ [type]: blob })];
      await navigator.clipboard.write(data)
      alert ('Text copied to clipboard')
    },
    speak () {
      this.sk.speak(this.voiceText)
    },
    listen () {
      this.sk.listen()
      this.isListen = !this.isListen
    },
    stopListen () {
      this.sk.stopListen()
      this.isListen = !this.isListen
    },
    getText (evt) {
      this.voiceText = evt.detail.transcript
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

As you can see the almost all of the functionality is being offloaded to the SpeechKit library. You can see a live version of this at https://mastashake08.github.io/speech-kit-demo/ . In the mount() method we initialize our SpeechKit instance and add an event listener on the document to listen for the onspeechkitresult event emitted from the SpeechKit class which dispatches everytime there is an availble transcript from speech recognition. The listen() and stopListen() functions simply call the SpeechKit functions and toggle a boolean indicating recording is in process. Finally the share() function uses the Web Share API to share the URL if available, otherwise it defaults to using the Clipboard API and copying the text to the user’s clipboard for manual sharing.

Want To See More Tutorials?

Join my newsletter and get weekly updates from my blog delivered straight to your inbox.

Check The Shop!

Consider purchasing an item from the #CodeLife shop, all proceeds go towards our coding initiatives.

Follow Me On Social Media

Follow Me On Youtube!

Follow my YouTube account

Get Your Next Domain Cheap & Support The Channel

I use Namecheap for all of my domains! Whenever I need a cheap solution for a proof-of-concept project I grab a domain name for as little as $1! When you sign up and buy your first domain with Namecheap I get a commission, it’s a great way to get a quality service and support this platform!

Get Your Next Domain Cheap
CLICK HERE

Become A Sponsor

Open-source work is free to use but it is not free to develop. If you enjoy my content and would like to see more please consider becoming a sponsor on Github or Patreon! Not only do you support me but you are funding tech programs for at risk youth in Louisville, Kentucky.

Join The Newsletter

By joining the newsletter, you get first access to all of my blogs, events, and other brand-related content delivered directly to your inbox. It’s 100% free and you can opt out at any time!

Check The Shop

You can also consider visiting the official #CodeLife shop! I have my own clothing/accessory line for techies as well as courses designed by me covering a range of software engineering topics.

Posted on Leave a comment

Creating A Screen Recorder and Email Microservice With Vue.js + MediaRecorder API and Laravel PHP Framework

Recording Your Screen With Vue.js and MediaRecorder API

Last year I wrote a screen recording progressive web app with Vue.js and the MediaRecorder API. This was a simple app that allowed you to record your current screen and after screen sharing, a file would be created with the File API and downloaded to your system. Well I decided to update it this week and add email functionality. The reason? I needed to send a screen recording to a client and figured might as well add the functionality in the app and save time; as opposed to downloading the file then opening Gmail, then sending the email. Here is a video for the first part.

Screen recorder part 1

Adding The Email Service

Obviously, you all know I love Laravel! I decided to create a Laravel 8 API microservice with a single post route that takes the video file and email address and sends a notification to said email address. I then had to edit the Vue application to make a network call to the microservice when the user wants to email the file.

Screen recorder part 2

Getting To The Code!

Let’s start off with the Vue.js application. Create a new application in your terminal

vue create screen-recorder

The first thing we are going to do is add our dependencies, which in this case is vue-tailwind for ease of working with TailwindCSS, gtag for working with Google Analytics ( I like to know where my users are coming from), Google Adsense ( a brother gotta eat) and vue-script2.

cd screen-recorder; npm install --save vue-tailwind vue-script2 vue-gtag vue-google-adsense

After installing the dependencies, head over to main.js and let’s setup the application

import Vue from 'vue'
import App from './App.vue'
import VueTailwind from 'vue-tailwind'
import Ads from 'vue-google-adsense'
import VueGtag from "vue-gtag";
import "tailwindcss/tailwind.css"
Vue.use(VueGtag, {
  config: { id: "your google analytics id" }
});

Vue.use(require('vue-script2'))

Vue.use(Ads.Adsense)
const settings = {
  TInput: {
    classes: 'form-input border-2 text-gray-700',
    variants: {
      error: 'form-input border-2 border-red-300 bg-red-100',
      // ... Infinite variants
    }
  },
TButton: {
    classes: 'rounded-lg border block inline-flex items-center justify-center block px-4 py-2 transition duration-100 ease-in-out focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none focus:ring-opacity-50 disabled:opacity-50 disabled:cursor-not-allowed',
    variants: {
      secondary: 'rounded-lg border block inline-flex items-center justify-center bg-purple-500 border-purple-500 hover:bg-purple-600 hover:border-purple-600',
    }
  },
  TAlert: {
    classes: {
      wrapper: 'rounded bg-blue-100 p-4 flex text-sm border-l-4 border-blue-500',
      body: 'flex-grow text-blue-700',
      close: 'text-blue-700 hover:text-blue-500 hover:bg-blue-200 ml-4 rounded',
      closeIcon: 'h-5 w-5 fill-current'
    },
    variants: {
      danger: {
        wrapper: 'rounded bg-red-100 p-4 flex text-sm border-l-4 border-red-500',
        body: 'flex-grow text-red-700',
        close: 'text-red-700 hover:text-red-500 hover:bg-red-200 ml-4 rounded'
      },
      // ... Infinite variants
    }
  },
  // ... The rest of the components
}

Vue.use(VueTailwind, settings)
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

This file basically bootstraps the application with all the Google stuff and the Tailwind CSS packaging. Now let’s open up the App.vue and replace with the following:

<template>
  <div id="app">
    <img alt="J Computer Solutions Logo" src="./assets/logo.png" class="object-contain h-48 w-full">
    <p>
    Record your screen and save the file as a video.
    Perfect for screen recording for clients. Completely client side app and is installable as a PWA!
    </p>
    <p>
    Currently full system audio is only available in Windows and Chrome OS.
    In Linux and MacOS only chrome tabs are shared.
    </p>
    <t-modal
      header="Email Recording"
      ref="modal"
    >
  <t-input v-model="sendEmail" placeholder="Email Address" name="send-email" />
  <template v-slot:footer>
    <div class="flex justify-between">
      <t-button type="button" @click="$refs.modal.hide()">
        Cancel
      </t-button>
      <t-button type="button" @click="emailFile">
        Send File
      </t-button>
    </div>
  </template>
</t-modal>
<div class="mt-5">
    <t-button v-on:click="getStream" v-if="!isRecording"> Start Recording 🎥</t-button>
    <t-button v-on:click="stopStream" v-else> Stop Screen Recording ❌ </t-button>
    <t-button v-on:click="download" v-if="fileReady" class="ml-10"> Download Recording 🎬</t-button>
    <t-button  v-on:click="$refs.modal.show()" v-if="fileReady" class="ml-10"> Email Recording 📧</t-button>
</div>
    <br>
    <Adsense
      data-ad-client="ca-pub-xxxxxxxxxx"
      data-ad-slot="xxxxxxx">
    </Adsense>
  </div>
</template>

<script>

export default {
  name: 'App',
  data() {
    return {
      isRecording: false,
      options: {
        audioBitsPerSecond: 128000,
        videoBitsPerSecond: 2500000,
        mimeType: 'video/webm'
      },
      displayOptions: {
      video: {
        cursor: "always"
      },
      audio: {
          echoCancellation: true,
          noiseSuppression: true,
          sampleRate: 44100
        }
      },
      mediaRecorder: {},
      stream: {},
      recordedChunks: [],
      file: null,
      fileReady: false,
      sendEmail: '',
      url: 'https://screen-recorder-micro.jcompsolu.com'
    }
  },
  methods: {
    async emailFile () {
      try {
        const fd = new FormData();
        fd.append('video', this.file)
        fd.append('email', this.sendEmail)
        await fetch(`${this.url}/api/email-file`, {
          method: 'post',
          body: fd
        })
      this.$refs.modal.hide()
      this.showNotification()
      } catch (err) {
        alert(err.message)
      }
    },
    setFile (){
      this.file = new Blob(this.recordedChunks, {
        type: "video/webm"
      });
      this.fileReady = true
    },
    download: function(){
      this.$gtag.event('download-stream', {})


    var url = URL.createObjectURL(this.file);
    var a = document.createElement("a");
    document.body.appendChild(a);
    a.style = "display: none";
    a.href = url;
    var d = new Date();
    var n = d.toUTCString();
    a.download = n+".webm";
    a.click();
    window.URL.revokeObjectURL(url);
    this.recordedChunks = []
    this.showNotification()
    },
    showNotification: function() {
      var img = '/logo.png';
      var text = 'If you enjoyed this product consider donating!';
      navigator.serviceWorker.getRegistration().then(function(reg) {
        reg.showNotification('Screen Recorder', { body: text, icon: img, requireInteraction: true,
        actions: [
            {action: 'donate', title: 'Donate',icon: 'logo.png'},
            {action: 'close', title: 'Close',icon: 'logo.png'}
            ]
              });
      });
    },
    handleDataAvailable: function(event) {
      if (event.data.size > 0) {
        this.recordedChunks.push(event.data);
        this.isRecording = false
        this.setFile()
      } else {
        // ...
      }
    },
    stopStream: function() {
      this.$gtag.event('stream-stop', {})
      this.mediaRecorder.stop()
      this.mediaRecorder = null
      this.stream.getTracks()
      .forEach(track => track.stop())

    },
    getStream: async function() {
    try {
        this.stream =  await navigator.mediaDevices.getDisplayMedia(this.displayOptions);
        this.mediaRecorder = new MediaRecorder(this.stream, this.options);
        this.mediaRecorder.ondataavailable = this.handleDataAvailable;
        this.mediaRecorder.start();
        this.isRecording = true
        this.$gtag.event('stream-start', {})
      } catch(err) {
        this.isRecording = false
        this.$gtag.event('stream-stop', {})
        alert(err);
      }
    }
  },
  mounted() {

    let that = this
    Notification.requestPermission().then(function(result) {
      that.$gtag.event('accepted-notifications', { result: result })
    });
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Laravel API

Start off by creating a new Laravel application. My setup uses Docker and MacOS

curl -s "https://laravel.build/screen-recorder-api" | bash

The first thing we want to do is create our File model and migration. The File model will hold the name, mime_type and size of the file along with the email where the file is to be sent. Note! We are NOT storing the file, simply passing it through to the email.

cd screen-recorder-api; ./vendor/bin/sail up -d; ./vendor/bin/sail artisan make:model -m File

Open up the app/Models/File.php file and replace the contents with the following:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class File extends Model
{
    use HasFactory, Notifiable;
    public $guarded = [];
}

Now open up the migration file and edit it to be the following:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateFilesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('files', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email');
            $table->string('size');
            $table->string('mime_type');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('files');
    }
}

Now let’s create a new notification called SendFile. This notification will send an email with the file attached to it to the user. Let’s create the notification and fill out the contents!

./vendor/bin/sail artisan make:migration SendFile
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class SendFile extends Notification
{
    use Queueable;
    public $file;
    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($file)
    {
        //
        $this->file = $file;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
                    ->line('Your Screen Recording')
                    ->line('Thank you for using our application!')
                    ->attach($this->file, ['as' => 'jcompsolu-screen-record.webm', 'mime' => 'video/webm']);
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

You will notice we set the file in the constructor then attach it using the attach() method on the MailMessage object. Now that is done let’s create the API route, and send our notifications! Open up routes/api.php and edit it to be so:

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Models\File;
use App\Notifications\SendFile;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Route::post('/email-file', function (Request $request) {
  $uploadedFile = $request->video;
  $file = File::Create([
    'name' => $uploadedFile->getClientOriginalName(),
    'mime_type' => $uploadedFile->getClientMimeType(),
    'size' => $uploadedFile->getSize(),
    'email' => $request->email
  ]);
  $file->notify(new SendFile($uploadedFile));
  return response()->json($file);
});

When you upload a file in Laravel it is an instance of UploadedFile class and has several file related methods associated with it! Using these methods we can get the name, size and mimetype of the uploaded file! After setting the model and saving in the database we send a notification with the uploaded file! Test it yourself here!

Conclusion

The vast majority of the apps I create and monetize, start off as an app that I use myself to make my life or work easier! This is the basis of #CodeLife and is the reason I was able to retire early for a few years. If this tutorial helped you please consider subscribing to my Youtube channel and subscribing to the blog and leave a comment if you want me to add new functionality!

Posted on 1 Comment

WebTransport Is A Game Changer For Gaming

If You Haven’t Heard Of WebTransport

It is a new standard that provides bidirectional data transport over HTTP/3. In many cases, it can replace WebSocket and WebRTC with less overhead. It has two APIs that come with it, one for sending data unreliably with Datagram API and one reliably with the Stream API. In this article I will explain what WebTransport is, and how it will affect web gaming! If you want access to my member’s only article where I build a HTTP/3 server in Go and WebTransport client become a patron today.

Why Is WebTransport Such A Big Deal?

WebTransport is built on top of HTTP/3 which means it runs over QUIC. Without going into too much technical detail this equates to lower overhead and faster more reliable connections. It is also bidirectional meaning you can read and write data to the server. The cool thing to me though is how you can send data reliably (with streams similar to websockets) AND unreliably via datagrams!

Imagine you are making a multiplayer shooting game with 64 players. In coding terms you need this data to come as fast as possible right? Well at first glance you might think that a reliable data stream would be the best right? After all if all the players are shooting one character you want the damage to come in order right? WRONG! You do that and you are at the behest of the slowest network in the game. By sending datagrams you get best effort delivery (let’s be honest only ~1-5% of traffic is lost in these types of connections) so you won’t block the other connections.

Since WebTransport is Client-Server this is a no brainer for real-time multiplayer web games! Low latency is a must!

What’s Next?

WebTransport is still in it’s early stages but I will be following up with a YouTube video and a follow up blog where I will build a WebTransport server and client application! If you aren’t following me on Twitter do so @mastashake08

Posted on Leave a comment

Create An Encryption Based Anonymous Messenger

Secure Your Messages With Laravel Encryption

Encryption is the best tool in this fight for your right to privacy. Now more than anytime in history privacy is of the essence. In today’s tutorial I will show you how to create a simple messenger service in Laravel and Vue.js; however they will be password protected and encrypted therefore the receiver must know the password beforehand to read the message. All code can be found on my Github repo.

Setting Up The Backend

The application itself has only one model and that is the Message model. It has 3 properties: content, email and passphrase. The content stores the encrypted message. The email is the email address of who is receiving the message. Finally the passphrase is the password that protects the message from being opened by anybody.
php artisan make:model -m Message
Open the Message.php file and make it Notifiable and change the $fillable


<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Model;
class Message extends Model
{
use Notifiable;
//
public $fillable = ['content','passphrase','email'];
}

 
Next open up the migration file that was created with the model and add the following:


<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateMessagesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('messages', function (Blueprint $table) {
$table->increments('id');
$table->longText('content');
$table->string('passphrase');
$table->string('email');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('messages');
}
}

Next create the controller. This controller will be RESTful with an extra method for decrypting the messages.


php artisan make:controller --resource MessageController

Open the file and fill in the methods store(), show() and decrypt()


<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Message;
use App\Notifications\MessageCreated;
use Illuminate\Contracts\Encryption\DecryptException;
class MessageController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
$message = Message::Create([
'content' => encrypt($request->content),
'passphrase' => encrypt($request->password),
'email' => $request->email
]);
$message->notify(new MessageCreated($message));
return response()->json($message);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
$message = Message::findOrFail($id);
return view('message')->with([
'message' => $message
]);
}
public function decrypt(Request $request, $id){
try{
$message = Message::findOrFail($id);
if($request->password == decrypt($message->passphrase)){
$message = decrypt($message->content);
$with = [
'message' => $message
];
return response()->json($with);
}
}
catch (DecryptException $e){
return response()->json($e);
}
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}

 
As you can see there is a call to a notification that has not been created yet so create it


php artisan make:notification MessageCreated

 
Open that up and replace it with the following which will call the toMail method and alert the recipient they have a new message to view


<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class MessageCreated extends Notification
{
use Queueable;
public $message;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(\App\Message $message)
{
//
$this->message = $message;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('You have a new encrypted message.')
->line('You should have been given the passphrase')
->action('Decrypt and Read Now!', url('/message/'.$this->message->id))
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

Lastly the api and web routes need to be updated
In web.php


<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('home');
});
Route::get('/home', 'HomeController@index')->name('home');
Route::get('/message/{id}','MessageController@show');

In api.php


<?php
use Illuminate\Http\Request;
/* |-------------------------------------------------------------------------- | API Routes Fr */
Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); });
Route::resource('/message','MessageController');
Route::post('/decrypt-message/{id}','MessageController@decrypt');

Lastly let’s create the view files. As a shortcut I scaffold authentication even though we aren’t using it to get the bootstrap layouts and the home.blade.php file. Copy the home.blade.php file to another file called message.blade.php.
In home.blade.php


@extends('layouts.app')
@section('content')
<send-message></send-message>
@endsection

 
In message.blade.php


@extends('layouts.app')
@section('content')
<read-message v-bind:message="{{$message}}"></read-message>
@endsection

That’s it, the back end is complete!
 

Front End

The Vue.js side of things is pretty simple there are two components a MessageSend and a MessageRead. Create a file in your resources/assets/js/components folder called MessageSend.vue and MessageRead.vue.
 
In MessageSend.vue


<template>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card card-default">
<div class="card-header">Send Encrypted Message</div>
<div class="card-body">
<div class="form-group">
<input type="email" placeholder="Enter email address" class="form-control" v-model="message.email">
</div>
<div class="form-group">
<input type="password" placeholder="Enter passphrase" class="form-control" v-model="message.password">
</div>
<div class="form-group">
<textarea class="form-control" placeholder="Enter Message" v-model="message.content"></textarea>
</div>
<div class="form-group">
<button class="btn btn-sm btn-primary" v-on:click="send()">Send Message</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
},
data() {
return {
message: {}
}
},
methods: {
send: function(){
axios.post('/api/message',this.message).then(data =>{
console.log(data);
alert('Message Sent!');
}).catch(err => {
console.log(err);
})
}
}
}
</script>

 
In MessageRead.vue


<template>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card card-default">
<div class="card-header">Read Encrypted Message</div>
<div class="card-body">
<div class="form-group">
<input type="password" placeholder="Enter passphrase" class="form-control" v-model="password">
</div>
<div class="form-group">
<textarea class="form-control" placeholder="Enter Message" v-model="msg.content"></textarea>
</div>
<div class="form-group">
<button class="btn btn-sm btn-primary" v-on:click="decrypt()">Decrypt Message</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
},
data() {
return {
password: '',
msg : this.message
}
},
props: ['message'],
created(){
},
methods: {
decrypt: function(){
var that = this;
axios.post('/api/decrypt-message/'+this.message.id,{password:this.password}).then(data =>{
that.message.content = data.data.message;
}).catch(err => {
console.log(err);
})
}
}
}
</script>

In your resources/assets/js/app.js file don’t forget to add your components


/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Vue = require('vue');
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
Vue.component('send-message', require('./components/MessageSend.vue'));
Vue.component('read-message', require('./components/MessageRead.vue'));
const app = new Vue({
el: '#app'
});

 

Posted on Leave a comment

Create A Point Of Sales System With Laravel, Vue and Stripe – Part 4

Stripe Subscriptions

In part 4 we add recurring billing to our point of sales system by utilizing Stipe Subscriptions. Subscriptions in conjunction with our customers created in part 3 allows for easy tracking of expenses and opens the way for residual income. Next up we add inventory management to the system! For the source code click here and don’t forget to subscribe to the YouTube channel!

Posted on 1 Comment

Create A Point Of Sales System With Laravel, Vue and Stripe – Part 3

Stripe Customers

In the previous segment we set up the Stripe webhooks so that Stripe can interact with our system asynchronously. Today we implement Stripe customers in our platform. For all source code check here. In the next post we introduce recurring billing.

 

Posted on 3 Comments

Create A Point Of Sales System With Laravel, Vue and Stripe – Part 1

What Is A Point Of Sales System?

Creating web apps for the fun of it is cool, but we all need to get paid right?! This new series explores how to create a point of sales system with Laravel, Vue.js and the Stripe API. A point of sales system (or POS) is software solution that allows you to process money. In this case we will use Stripe to process credit/debit cards as well as manage inventory. Using a fullstack Laravel and Vue.js application we can collect money, send invoices and get paid all in one location. By the time this series is over you will have used:

  • Laravel Passport
  • Laravel Socialite
  • Stripe Charge API
  • Stripe Product API
  • Stripe Subscription API


All of the source code is available at https://github.com/mastashake08/laravel-vue-pos. In part one I explain what it is we are building today as well as setting up the ground work for the future. Watch the video below and don’t forget to subscribe to the channel and share the video! Lastly don’t forget to check out the live version right here! Now on to part 2.

Posted on 3 Comments

Create A Point Of Sales System With Vue/Laravel + Stripe

Point Of Sales In The Palm Of Your Hand

In today’s tutorial I will be creating a point of sales system utilizing Vue and Laravel with Stripe being our payment processor. The program will allow a stripe account holder to take payments and if on mobile will allow them to scan the card via the device’s camera. It will utilize Laravel Passport for secure API calls and Stripe to handle the payments.

Installing The Dependencies

The app uses two dependencies as of now and those are Stipe and Laravel Passport install them using composer
 

composer require laravel/passportstripe/stripe-php

Now run the migrations (I’m using Laravel 5.5 so the packages are auto-discovered)

php artisan migrate

Now open your app/User.php model and edit the following

<?php
namespace App;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

Next register the Passport routes in your AuthServiceProvider

<?php
namespace App\Providers;
use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];
    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();
        Passport::routes();
    }
}

Register the api driver in config/auth.php

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],

Lastly set the web middleware group

'web' => [
    // Other middleware...
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

Controller

This application only needs one external controller

php artisan make:controller StripeController

This controller will only contain 2 methods __construct() and charge(). The __construct method will set the StripeApiKey and the charge method actually makes the charge

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class StripeController extends Controller
{
 //
 public function __construct(){
 \Stripe\Stripe::setApiKey(env('STRIPE_SECRET'));
 }
public function charge(Request $request){
 try {
 // Use Stripe's library to make requests...
 $token = \Stripe\Token::create(array(
 "card" => array(
 "number" => $request->card['card_number'],
 "exp_month" => $request->card['expiry_month'],
 "exp_year" => $request->card['expiry_year'],
 "cvc" => $request->card['cvv']
 )
 ));
 \Stripe\Charge::create(array(
 "amount" => $request->amount * 100,
 "currency" => "usd",
 "source" => $token, // obtained with Stripe.js
 "description" => $request->description,
 "receipt_email" => $request->email
 ));
 return response()->json([
 'success' => true
 ]);
 } catch(\Stripe\Error\Card $e) {
 // Since it's a decline, \Stripe\Error\Card will be caught
 return response()->json($e->getJsonBody());
 } catch (\Stripe\Error\RateLimit $e) {
 // Too many requests made to the API too quickly
 return response()->json($e->getJsonBody());
 } catch (\Stripe\Error\InvalidRequest $e) {
 // Invalid parameters were supplied to Stripe's API
 return response()->json($e->getJsonBody());
 } catch (\Stripe\Error\Authentication $e) {
 // Authentication with Stripe's API failed
 // (maybe you changed API keys recently)
 return response()->json($e->getJsonBody());
 } catch (\Stripe\Error\ApiConnection $e) {
 // Network communication with Stripe failed
 return response()->json($e->getJsonBody());
 } catch (\Stripe\Error\Base $e) {
 // Display a very generic error to the user, and maybe send
 // yourself an email
 return response()->json($e->getJsonBody());
 } catch (Exception $e) {
 // Something else happened, completely unrelated to Stripe
 return response()->json($e->getJsonBody());
 }
 }
}

The controller is now finished let’s create the API routes

API Routes

Open the routes/api.php file and add the following routes

<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::middleware('auth:api')->get('/user', function (Request $request) {
 return $request->user();
});
Route::middleware('auth:api')->post('/charge','StripeController@charge');

The backend is now complete now for the front end.

Vue Component

Get rid of the example component and create a new one called ChargeComponent and add the following content

<template>
 <div class="container">
 <div class="row">
 <div class="col-md-8 col-md-offset-2">
 <div class="panel panel-default">
 <div class="panel-heading">Make A Charge</div>
<div class="panel-body">
 <fieldset>
 <div class="form-group">
 <label class="col-sm-3 control-label" for="amount">Amount</label>
 <div class="col-sm-9">
 <input type="number" class="form-control" id="amount" placeholder="Amount To Charge" v-model="amount">
 </div>
 </div>
 <div class="form-group">
 <label class="col-sm-3 control-label" for="email">Email</label>
 <div class="col-sm-9">
 <input type="email" class="form-control" id="email" placeholder="Email Receipt" v-model="email">
 </div>
 </div>
 <div class="form-group">
 <label class="col-sm-3 control-label" for="description">Description</label>
 <div class="col-sm-9">
 <input type="text" class="form-control" id="description" placeholder="Credit Card Description" v-model="description">
 </div>
 </div>
 <div class="form-group">
 <label class="col-sm-3 control-label" for="card-number">Card Number</label>
 <div class="col-sm-9">
 <input type="text" class="form-control" name="card-number" id="card-number" placeholder="Debit/Credit Card Number" autocomplete="cc-number" v-model="card.card_number">
 </div>
 </div>
 <div class="form-group">
 <label class="col-sm-3 control-label" for="expiry-month">Expiration Date</label>
 <div class="col-sm-9">
 <div class="row">
 <div class="col-xs-3">
 <select class="form-control col-sm-2" name="expiry-month" id="expiry-month" autocomplete="cc-exp-month" v-model="card.expiry_month">
 <option>Month</option>
 <option value="01">Jan (01)</option>
 <option value="02">Feb (02)</option>
 <option value="03">Mar (03)</option>
 <option value="04">Apr (04)</option>
 <option value="05">May (05)</option>
 <option value="06">June (06)</option>
 <option value="07">July (07)</option>
 <option value="08">Aug (08)</option>
 <option value="09">Sep (09)</option>
 <option value="10">Oct (10)</option>
 <option value="11">Nov (11)</option>
 <option value="12">Dec (12)</option>
 </select>
 </div>
 <div class="col-xs-3">
 <select class="form-control" name="expiry-year" autocomplete="cc-exp-year" v-model="card.expiry_year">
 <option value="17">2017</option>
 <option value="18">2018</option>
 <option value="19">2019</option>
 <option value="20">2020</option>
 <option value="21">2021</option>
 <option value="22">2022</option>
 <option value="23">2023</option>
 </select>
 </div>
 </div>
 </div>
 </div>
 <div class="form-group">
 <label class="col-sm-3 control-label" for="cvv">Card CVV</label>
 <div class="col-sm-3">
 <input type="text" class="form-control" name="cvv" id="cvv" placeholder="Security Code" autocomplete="cvc" v-model="card.cvv">
 </div>
 </div>
 <div class="form-group">
 <div class="col-sm-offset-3 col-sm-9">
 <button type="button" class="btn btn-success" v-on:click="createCharge">Pay Now</button>
 </div>
 </div>
 </fieldset>
 </div>
 </div>
 </div>
 </div>
 </div>
</template>
<script>
 export default {
 mounted() {
 console.log('Component mounted.')
 },
 data(){
 return{
 card: {
 card_number: null,
 expiry_year: null,
 expiry_month: null,
 cvv: null
 },
 amount: 0,
 email: null,
 description: null
 }
 },
 methods: {
 createCharge: function(){
 axios.post('/api/charge',{card: this.card, amount: this.amount, description: this.description})
 .then(function(data){
 alert('Success!')
 }).catch(function(error){
 alert(error.message);
 });
 }
 }
 }
</script>

Edit the app.js file to match the following

/**
 * First we will load all of this project's JavaScript dependencies which
 * includes Vue and other libraries. It is a great starting point when
 * building robust, powerful web applications using Vue and Laravel.
 */
require('./bootstrap');
window.Vue = require('vue');
/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */
Vue.component('charge-component', require('./components/ChargeComponent.vue'));
const app = new Vue({
 el: '#app'
 });

now install the npm dependencies and run mix

npm install && npm run dev

Enjoy your new app! You can fork the source code here! Be sure to like/subscribe/share and if you want to show your support please check out the shop!

Posted on 1 Comment

Check Out My New Web Application Anon Video Chat

Anon Video Chat

Anon Video Chat is my latest web application, the premise is simple, anonymous video chat using webRTC for secure low latency browser to browser connections. This app assigns you a random channel ID everytime you load the page (you have the option on saving the current ID to your device to use permanently) you can call other channel IDs and if they answer a webRTC connection is set up between your two browsers and the live feeds start. In the future I plan on adding features like file and location sharing and possibly Bitcoin sending/receiving. No data is ever stored on my server making all actions anonymous. I decided to write this for a few reasons

  • I wanted to sharpen my Vue.js skills
  • I wanted to better understand webRTC
  • I need to implement P2P chat in other applications
  • I didn’t want to pay Twilio to do something I could do myself

Why Did I Choose To Release It?

The reason I chose to release Anon Video Chat to the public is because I believe in the philosophy of an open and free internet. Other video applications like Skype collect tons of metadata on you and in turn sell that data on you for a profit. In cases like these you are not the consumer but the product. Not only is this creepy but it affects the performance of the application by providing overhead with no benefit to the end user resulting in laggy performance.

anon video chat screenshot
Screenshot of me and my friend in France using Anon Video Chat

Current Limitations Of Anon Video Chat

Until iOS11 is released later in the fall, Anon Video Chat will not work on iOS devices (blame them not me they decided not to implement webRTC until now!). Other than that it should work on Chrome, Firefox and Opera. If you notice that it doesn’t please email me and let me know jyrone.parker@gmail.com.
Needless to say I wrote this app in a few hours, you can tell by it’s current lack of features but as I stated above I will update it periodically. If the demand calls for it I will add whatever features you guys leave below in the comments. Also if you are interested in contributing to the project, drop your GitHub username in the comment section below and I will add you to the private repo!