Printing from a BrowserView in electron 10 - electron

In my electron app, a user can select one or more images for printing. To do this, I create a BrowserView which is not visible, load html into it and print it before destroying the view. This worked ok up to Electron 5. But after upgrading my app to electron 10, the printing still occurs but the image is broken (not visible). Any ideas?
Here is a sample of the HTML used to initialize my browser view:
<!DOCTYPE html>
<html>
<head>
<style>#media print { #page { margin: 0; padding: 0;} html, body { margin: 0; padding: 0; height: 100%; width: 100%; } html body *:not(.tempPrinterPaper) {display: none; visibility: hidden; z-index: -5000; } .tempPrinterPaper { display: flex; align-items: center; justify-content: center; position: relative; page-break-after: always; page-break-inside: avoid; overflow: hidden; height: 100%; width: 100%; border: none; margin: 0; padding: 0; background-color: #FFFFFF; text-align: center; align-self: stretch; } .tempPrinterPaper img.tempPrinterPaperImage { display: flex; position: relative; height: auto; max-height: 100%; max-width: 100%; margin: 0; padding: 0; z-index: 500000000000; visibility: visible; } }</style>
</head>
<body>
<div class="tempPrinterPaper"><img class="tempPrinterPaperImage" src="C:\Users\igweo\OneDrive\Pictures\rts9nzl-e1526310385107.jpg" /></div>
</body>
</html>
Here is the code for printing:
// now initialize browser window
printerWindow = new remote.BrowserView( {webPreferences: {webSecurity: false}} );
// load url
printerWindow.webContents.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(printerHTML));
// after contents have been loaded
printerWindow.webContents.on('did-finish-load', function() {
printerWindow.webContents.print({silent: false, printBackground: false}, function(res) {
// if printing is successful, show a notification
if (res) {
//let printNotification = new Notification('Image' + ((currentPrintList.length > 1) ? 's' : '') + ' successfully sent to printer', {icon: 'static/icons/logoFilledBlue.png'});
let printNotification = new Notification('Image' + ((currentPrintList.length > 1) ? 's' : '') + ' successfully sent to printer', {body: app_name});
}
// clean up print list
//currentPrintList.length = 0;
// after printing is done, destroy browser window
printerWindow.destroy();
// just to be sure :-)
printerWindow = null;
});
});

Unfortunately in electron 10, the old ways no longer seem to work. What I have now done is to create a file called print.html, then write my html contents to the file and print it from the main process.
In app.js
function showPrintDialog() {
// build html string to be printed
let printHTML = '<!DOCTYPE html><html><head><style>#media print { #page { margin: 0; padding: 0;} }</style></head><body>HELLO WORLD</body></html>';
// save file in the userdata directory
let printFilePath = app.getPath('userData') + "\\print.html";
// write to file using powershell (THIS IS ONLY FOR WINDOWS)
let cmd = ['"' + printerHTML + '" | Set-Content -Path "' + printFilePath + '"'];
let cx = cp.spawn('powershell.exe', cmd);
// wait for completion
cx.stdout.on('close', function(data) {
(async function() {
// invoke ipc function to display a dialog box and send the required parameters
let printResult = await ipcRenderer.invoke('show-print-dialog', { printFilePath: printFilePath, successMessage: 'Successfully sent to printer'});
}) ();
});
}
Also, in app.js, we need a function to show notifications when printing is completed:
ipcRenderer.on('show-notification', function(event, args) {
// show a notification
showNotification(args);
});
Now, in our main.js, we will create a browserview with the written file and call the print command. When it is done, request a notification be shown.
// show printing dialog and handle printing
ipcMain.handle('show-print-dialog', async(event, args) => {
// create a browser view
try {
// now initialize browser window
let printerWindow = new BrowserView( {webPreferences: {worldSafeExecuteJavaScript: true, contextIsolation: true}});
// At this point, there should be a file called print.html in the local app path for the user
// This should be to C:\Users\username\AppData\Roaming\Photo Viewer Classic\print.html
printerWindow.webContents.loadURL(args.printFilePath);
// after contents have been loaded
printerWindow.webContents.on('did-finish-load', async function() {
// show print dialog
printerWindow.webContents.print({silent: false, printBackground: false}, function(res) {
// if printing is successful, res will return true
if (res) {
// show notification
event.sender.send('show-notification', args.successMessage);
}
// after printing is done, destroy browser window
printerWindow.destroy();
// just to be sure :-)
printerWindow = null;
});
});
} catch (e) { console.log(e); }
// If failed, simply return false
});

Related

Twilio Video Call issue on iOS Safari, After call start video freeze nodejs issue

I have created a video call application using Nodejs & Twilio CLI. And using this in my both mobile app Android & iOS. On Android is working perfectly. But on iOS, there is an issue, when users reach the video call page, it's showing preview but as the user clicks on the Join Room button, then his/her video stops and just showing a black screen. While he can talk with other users and can see the video of them. And the Second user also can see his/her video perfectly. Only the issue he/she can't see his/her video on that call.
My html code
<!DOCTYPE html>
<html>
<head>
<style>
.joinbtn {
border: none;
padding: 10px 10px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
margin: 4px 2px;
cursor: pointer;
background-color: #2b96cc;
color: #fff;
}
.stvbtn {
border: none;
padding: 10px 10px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
margin: 4px 2px;
cursor: pointer;
background-color: #2b96cc;
color: #fff;
}
.endbtn {
float:right;
border: none;
padding: 10px 10px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
margin: 4px 2px;
cursor: pointer;
background-color: #dc3545;
color: #fff;
}
#media screen and (max-width: 820px) {
video {
object-fit: cover;
width: 100%;
height: 47vh;
}
}
#media screen and (min-width: 821px){
video {
object-fit: contain;
}
}
.connect_btn{
display: flex;
justify-content: center;
align-content: space-around;
margin-top: -50px;
opacity: 0.8;
padding-bottom:8px;
}
button.endbtn:disabled, button.joinbtn:disabled {
background-color: #607d8b;
color: #ffffff;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/6.4.0/adapter.js" type="text/javascript"></script>
<script src="webrtc.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Clifix Video Chat</title>
</head>
<body>
<div id="room-controls">
<video id="video" autoplay muted playsinline loop width="100%"></video>
<div class="connect_btn">
<label for="passcode"></label>
<input id="passcode" type="hidden" value="8514"/>
<!--button class="stvbtn" id="start-video" onclick="viplay()">On/Off</button-->
<button class="joinbtn" id="button-join">Join Room</button>
<button class="endbtn" id="button-leave" disabled="disabled">End Call</button>
</div>
</div>
<!-- EDIT_CODE -->
<script src="//media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script>
<script src="index.js"></script>
</body>
</html>
My nodejs code:
'use strict';
(() => {
//const ROOM_NAME = 'demo';
var urltemp = location.search;
var array = urltemp.split('?');
var array1 = array[1];
var array2 = array1.split('=');
var id = array2[1];
const ROOM_NAME = id;
const Video = Twilio.Video;
let videoRoom, localStream;
const video = document.getElementById('video');
// preview screen
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((vid) => {
video.srcObject = vid;
localStream = vid;
});
// buttons
const joinRoomButton = document.getElementById('button-join');
const leaveRoomButton = document.getElementById('button-leave');
joinRoomButton.onclick = () => {
//video.play();
// get access token
fetch(`video-token?passcode=${getPasscode()}&room=${ROOM_NAME}`)
.then((resp) => {
if (resp.ok) {
var url=window.location.href,
separator = (url.indexOf("?")===-1)?"?":"&",
newParam=separator + "join=true";
var newUrl=url.replace(newParam,"");
newUrl+=newParam;
window.history.replaceState(null,null,newUrl);
return resp.json();
} else {
console.error(resp);
if (resp.status === 401) {
throw new Error('Go Back & Join Again');
} else {
throw new Error('Unexpected error. Open dev tools for logs');
}
}
})
.then((body) => {
const token = body.token;
//console.log(token);
//connect to room
return Video.connect(token, { name: ROOM_NAME });
})
.then((room) => {
//console.log(`Connected to Room ${room.name}`);
videoRoom = room;
room.participants.forEach(participantConnected);
room.on('participantConnected', participantConnected);
room.on('participantDisconnected', participantDisconnected);
room.once('disconnected', (error) =>
room.participants.forEach(participantDisconnected)
);
joinRoomButton.disabled = true;
leaveRoomButton.disabled = false;
})
.catch((err) => {
alert(err.message);
});
};
// leave room
leaveRoomButton.onclick = () => {
var url=window.location.href,
separator = (url.indexOf("?")===-1)?"?":"&",
newParam=separator + "end=true";
var newUrl=url.replace(newParam,"");
newUrl+=newParam;
window.history.replaceState(null,null,newUrl);
videoRoom.disconnect();
//console.log(`Disconnected from Room ${videoRoom.name}`);
joinRoomButton.disabled = false;
leaveRoomButton.disabled = true;
};
})();
const getPasscode = () => {
const passcodeInput = document.getElementById('passcode') || {};
const passcode = passcodeInput.value;
passcodeInput.value = '';
return passcode;
};
// connect participant
const participantConnected = (participant) => {
//console.log(`Participant ${participant.identity} connected'`);
const div = document.createElement('div'); //create div for new participant
div.id = participant.sid;
participant.on('trackSubscribed', (track) => trackSubscribed(div, track));
participant.on('trackUnsubscribed', trackUnsubscribed);
participant.tracks.forEach((publication) => {
if (publication.isSubscribed) {
trackSubscribed(div, publication.track);
}
});
document.body.appendChild(div);
};
const participantDisconnected = (participant) => {
//console.log(`Participant ${participant.identity} disconnected.`);
document.getElementById(participant.sid).remove();
};
const trackSubscribed = (div, track) => {
div.appendChild(track.attach());
};
const trackUnsubscribed = (track) => {
track.detach().forEach((element) => element.remove());
};
As per my understanding, before this my video was not working on iOS safari then I have done modifications in my HTML video code.
From this:
<video id="video" autoplay muted width="100%"></video>
To:
<video id="video" autoplay muted playsinline loop width="100%"></video>
Then it starts working as having video freezing at the iOS User side when he/she start calling.
Twilio developer evangelist here.
When you call Video.connect the Video SDK will ask for permission to use your microphone and camera. Safari does not like giving access to the microphone and camera more than once at a time and since you also ask for media access to show the preview, it drops the preview tracks and creates new tracks for the video call. This is why the preview goes dark, but other participants can see and hear the video/audio.
Instead, you should reuse the tracks that you got for the preview by storing a reference to them and then passing them to Video.connect as the tracks property in the ConnectOptions. You already store a reference to the localStream so you can use that when you get to connect, like this:
return Video.connect(token, {
name: ROOM_NAME,
tracks: localStream.getTracks()
});
That way the tracks for the preview will be re-used for the video call and nothing should go dark.

How to take screenshot using electronjs?

I'm building a cross-platform desktop application. I'm using electronjs framework for my desktop app development.And I want to add a functionality of taking screenshot every 5 minutes when my app starts!
help will be appreciated
my main.js
// Modules to control application life and create native browser window
const {app, BrowserWindow,Tray,Menu} = require('electron')
const path = require('path')
const iconz = path.join(__dirname,'/img/download.png')
const fs = require('fs')
var config = require('./login.json');
const shell = require('electron').shell
let tray = null
function createWindow () {
tray = new Tray(iconz)
const contextMenu = Menu.buildFromTemplate([
{ label: 'User:'+ config.username, type: 'radio',enabled:false},
{type:'separator'},
{ label: 'Show DeskTime', type: 'radio',
click() {
shell.openExternal('http://coinmarketcap.com')
}
},
{type:'separator'},
{ label: 'Private Time', type: 'radio',
click() {
checked:true
}
},
{type:'separator'},
{ label: 'LogOut', type: 'radio' },
{type:'separator'},
{ label: 'Quit', type: 'radio',
click() {
app.quit()
}
}
])
tray.setToolTip('This is my application.')
tray.setContextMenu(contextMenu)
console.log(config.username + ' ' + config.password);
if(config.username == ""){
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// and load the index.html of the app.
mainWindow.loadFile('index.html')
}
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
my index.html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {font-family: Arial, Helvetica, sans-serif;}
form {border: 3px solid #f1f1f1;}
input[type=text], input[type=password] {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
cursor: pointer;
width: 100%;
}
button:hover {
opacity: 0.8;
}
.cancelbtn {
width: auto;
padding: 10px 18px;
background-color: #f44336;
}
.imgcontainer {
text-align: center;
margin: 12px 0 6px 0;
}
img.avatar {
width: 20%;
border-radius: 40%;
}
.container {
padding: 16px;
}
span.psw {
float: right;
padding-top: 16px;
}
/* Change styles for span and cancel button on extra small screens */
#media screen and (max-width: 300px) {
span.psw {
display: block;
float: none;
}
.cancelbtn {
width: 100%;
}
}
</style>
</head>
<body>
<h2>Login Form</h2>
<form action="/action_page.php" method="post">
<div class="imgcontainer">
<img src="login_logo.png" alt="Avatar" class="avatar">
</div>
<div class="container">
<label for="uname"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="uname" required>
<label for="psw"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="psw" required>
<button type="submit">Login</button>
<label>
<input type="checkbox" checked="checked" name="remember"> Remember me
</label>
</div>
<div class="container" style="background-color:#f1f1f1">
<span class="psw">Forgot password?</span>
</div>
</form>
<label id="screenshot-path">Path:</label>
<button id="screen-shot" type="button" class="cancelbtn">Singup</button>
</body>
<script src="./renderer.js"></script>
</html>
My codes are given above, please go through this and help me to take screenshot on a certain interval.And also how to save in screen shots in predefined folder.
You can use contents.capturePage([rect]) on your main process. if you omits rect args it will capture the whole window. This will return a promise with native image .
To capture it on every 5 minute, you can set a setInterval(<function>,<time in millis>)
To save on specific folder you can use path module
ex:
const path = require('path')
const myFolderPath = path.join(__dirname, "myfolderinsideProject")
fs.writeFile(path.join(myFolderPath,`test${count}.png`), ....)
example code to save Captured window on current project folder:
// Modules to control application life and create native browser window
const {app, BrowserWindow } = require('electron')
const path = require('path')
const fs = require('fs')
let mainWindow, count=0;
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
//starting when the app is ready
app.on('ready', () => {
//setting the time interval for 3 second (3000 in millis)
setInterval(()=>{
console.log(`Capturing Count: ${count}`)
//start capturing the window
mainWindow.webContents.capturePage().then(image =>
{
//writing image to the disk
fs.writeFile(`test${count}.png`, image.toPNG(), (err) => {
if (err) throw err
console.log('Image Saved')
count++
})
})
}, 3000); //tome in millis
});
or you can use this npm package on renderer process

How to check if a selected string contains a substring of an highlight in epubjs

As the title above.
Assume, I have a paragraph:
It will be seen that this mere painstaking burrower and grub-worm of a poor devil of a Sub-Sub appears to have gone through the long Vaticans and street-stalls of the earth..
The bold string is a highlight. When I drag my mouse to select string
grub-worm of a poor devil of a Sub-Sub
Then I want to check if my selected text contains the highlight(or the part of the highlight) or not. How could I do that?
The code below is the example to add a highlight when I select a text.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EPUB.js Highlights Example</title>
<script src="../dist/epub.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script>
<link rel="stylesheet" type="text/css" href="examples.css">
<style type="text/css">
::selection {
background: yellow;
}
#extras {
width: 600px;
margin: 40px auto;
}
#highlights {
list-style: none;
margin-left: 0;
padding: 0;
}
#highlights li {
list-style: none;
margin-bottom: 20px;
border-top: 1px solid #E2E2E2;
padding: 10px;
}
#highlights a {
display: block;
}
#viewer.spreads {
top: 0;
margin-top: 50px;
}
[ref="epubjs-mk"] {
background: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycgeG1sbnM6eGxpbms9J2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsnIHg9JzBweCcgeT0nMHB4JyB2aWV3Qm94PScwIDAgNzUgNzUnPjxnIGZpbGw9JyNCREJEQkQnIGlkPSdidWJibGUnPjxwYXRoIGNsYXNzPSdzdDAnIGQ9J00zNy41LDkuNEMxOS42LDkuNCw1LDIwLjUsNSwzNC4zYzAsNS45LDIuNywxMS4zLDcuMSwxNS42TDkuNiw2NS42bDE5LTcuM2MyLjgsMC42LDUuOCwwLjksOC45LDAuOSBDNTUuNSw1OS4yLDcwLDQ4LjEsNzAsMzQuM0M3MCwyMC41LDU1LjQsOS40LDM3LjUsOS40eicvPjwvZz48L3N2Zz4=") no-repeat;
width: 20px;
height: 20px;
cursor: pointer;
margin-left: 0;
}
</style>
</head>
<body>
<div id="frame">
<div id="viewer" class="spreads"></div>
<a id="prev" href="#prev" class="arrow">‹</a>
<a id="next" href="#next" class="arrow">›</a>
</div>
<div id="extras">
<ul id="highlights"></ul>
</div>
<script>
// Load the opf
var book = ePub("https://s3.amazonaws.com/moby-dick/OPS/package.opf");
var rendition = book.renderTo("viewer", {
width: "100%",
height: 600,
ignoreClass: 'annotator-hl',
manager: "continuous"
});
var displayed = rendition.display(6);
// Navigation loaded
book.loaded.navigation.then(function(toc){
// console.log(toc);
});
var next = document.getElementById("next");
next.addEventListener("click", function(){
rendition.next();
}, false);
var prev = document.getElementById("prev");
prev.addEventListener("click", function(){
rendition.prev();
}, false);
var keyListener = function(e){
// Left Key
if ((e.keyCode || e.which) == 37) {
rendition.prev();
}
// Right Key
if ((e.keyCode || e.which) == 39) {
rendition.next();
}
};
rendition.on("keyup", keyListener);
document.addEventListener("keyup", keyListener, false);
rendition.on("relocated", function(location){
// console.log(location);
});
// Apply a class to selected text
rendition.on("selected", function(cfiRange, contents) {
rendition.annotations.highlight(cfiRange, {}, (e) => {
console.log("highlight clicked", e.target);
});
contents.window.getSelection().removeAllRanges();
});
this.rendition.themes.default({
'::selection': {
'background': 'rgba(255,255,0, 0.3)'
},
'.epubjs-hl' : {
'fill': 'yellow', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'
}
});
// Illustration of how to get text from a saved cfiRange
var highlights = document.getElementById('highlights');
rendition.on("selected", function(cfiRange) {
book.getRange(cfiRange).then(function (range) {
var text;
var li = document.createElement('li');
var a = document.createElement('a');
var remove = document.createElement('a');
var textNode;
if (range) {
text = range.toString();
textNode = document.createTextNode(text);
a.textContent = cfiRange;
a.href = "#" + cfiRange;
a.onclick = function () {
rendition.display(cfiRange);
};
remove.textContent = "remove";
remove.href = "#" + cfiRange;
remove.onclick = function () {
rendition.annotations.remove(cfiRange);
return false;
};
li.appendChild(a);
li.appendChild(textNode);
li.appendChild(remove);
highlights.appendChild(li);
}
})
});
</script>
</body>
</html>
I assume you only know the functionality of epubjs you listed above. From rendition.on(selected,...), we can get output: cfiRange. From book.getRange(cfiRange).then(function (range)..., we can get output: range.
That means whenever we select a word or sentence, we get cfiRange and range.
cfiRange is epubcfi(/6/10[id139]!/4/2[filepos12266]/6,/3:1,/3:4, which based on position of the selected word. I don't know how it calculates/works but if you do then you can check if the cfiRange contains a existing highlight word's cfiRange.
range.toString() can give you the text. if your application is only storing a word. then you can check if the new selected word == or contain your existing highlight word.

Building a chat app that uses a node.js server in IOS [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I am trying to build an iPhone(native) chat app that uses node.js on socket.io.
What is the best way to create chat application on IOS
Is there is any way to create chat application with the node.js server in IOS
Could anyone give me suggestion?
Thanks for you suggestion
Of cuz you could create chat application using Socket.io with iOS/Android and HTML!
There are 2 ways for you to do it!
i) Implement your own socket communication with Socket.io, (this is diffucult, because you need to write most of the network implementation by yourself!)
Socket.io will interface as stream, that you need to connect from your iOS!
You could refer the iOS dev guide on how to implement the stream!
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/UsingSocketsandSocketStreams.html
There is an active thread that discuss about this too!
iPhone Objective-C socket communication with Socket.IO
ii) Use existing library or wrapper that people make for iOS, you could find the link below!
This library mainly done the networking part, and you just need to implement your app logics!
iOS
https://github.com/pkyeck/socket.IO-objc
Android
https://github.com/nkzawa/socket.io-client.java
It would be good for you to start with the library first and then try to make your own implementation! :))
I suggest following:
Develop HTML5 application with node.js and NOSQL database (CouchDb in my example) on the server
Do not use socket.io for your first playground, because its complex. You must first understand the websockets very well.
Do not burden yourself with pre-prepared frameworks and tons of code. You need a clear, simple code in order to make modifications you need later. You need full control and understanding of the code you have.
I attached working sample (thanks to Ales Smodis). For the sample bellow to work, you need to install 2 node.js packets:
npm install websocket
npm install nano
And you need to create databse and insert at least 1 user into CouchDb database:
curl -X PUT http://localhost:5984/chatroom
curl -X PUT -H 'Content-Type: application/json' --data '{"username":"john","password":"john"}' http://localhost:5984/chatroom/john
$(document).ready(function () {
var connection,
username = 'Tester',
password = 'Tester',
historyCounter = 0,
members = {},
// displays
jqLogin = $('#login'),
jqChatroom = $('#chatroom'),
// login display components
jqWaiting = $('#waiting'),
jqNickname = $('#nickname'),
jqPassword = $('#password'),
// chat display components
jqHistory = $('#history'),
jqMembers = $('#members'),
jqLine = $('#line');
function addLine(nick, line) {
var jq = $('<p><span class="nick"></span><span class="line"></span></p>'),
jqNick = jq.find('.nick'),
jqLine = jq.find('.line'),
i, lines;
jqNick.text(nick ? nick + ': ' : '*** ');
jqLine.text(line);
jqHistory.append(jq);
historyCounter++;
for (lines = jqHistory.children(), i = 0; historyCounter > 100; i++, historyCounter--) {
$(lines[i]).remove();
}
}
function addMsg(msgObj) {
var msgHandler = states[activeState].messageHandlers[msgObj.type] || function () { addLine(null, 'Neveljaven paket tipa ' + msgObj.type); };
msgHandler(msgObj);
}
function clearMembers() {
var nickname;
for (nickname in members) {
members[nickname].remove();
delete members[nickname];
}
jqMembers.empty(); // just in case
}
function addMember(nickname) {
var jq = $('<li></li>');
jq.text(nickname);
jqMembers.append(jq);
members[nickname] = jq;
}
function removeMember(nickname) {
if (nickname in members) {
members[nickname].remove();
delete members[nickname];
}
}
function connect () {
connection = new WebSocket('ws://127.0.0.1:8080');
connection.onopen = function () {
states[activeState].onopen();
};
connection.onmessage = function (message) {
try {
addMsg(JSON.parse(message.data));
}
catch (e) {
addLine(null, 'Exception while handling a server message: ' + e.toString());
}
};
connection.onclose = function () {
states[activeState].onclose();
};
}
function loginKeypress(event) {
if (13 !== event.keyCode) return;
username = jqNickname.val();
password = jqPassword.val();
if (!username) jqNickname[0].focus();
else if (!password) jqPassword[0].focus();
else {
jqWaiting.css('display', '');
jqNickname.unbind('keydown', loginKeypress);
jqPassword.unbind('keydown', loginKeypress);
connect();
}
}
function inputKeypress(event) {
var line;
if (13 === event.keyCode) {
line = jqLine.val();
if (line.length === 0) return;
jqLine.val('');
connection.send(JSON.stringify({ 'type': 'line', 'line': line }));
}
}
var states = {
'login': {
'start': function () {
jqChatroom.css('display', 'none');
jqWaiting.css('display', 'none');
jqLogin.css('display', '');
jqNickname.val('');
jqPassword.val('');
jqNickname[0].focus();
activeState = 'login';
jqNickname.bind('keydown', loginKeypress);
jqPassword.bind('keydown', loginKeypress);
},
'onopen': function () {
connection.send(JSON.stringify({ 'type': 'login', 'username': username, 'password': password }));
},
'messageHandlers': {
'state': function (msgObj) {
var i, history, users;
states.chat.start();
history = msgObj.history;
jqHistory.empty();
historyCounter = 0;
for (i = 0; i < history.length; i++) addMsg(history[i]);
users = msgObj.users;
clearMembers();
for (i = 0; i < users.length; i++) addMember(users[i]);
}
},
'unhandledMessage': function (msgObj) {
connection.close(4000, 'Unhandled message type');
},
'onclose': function () {
states.login.start();
}
},
'chat': {
'start': function () {
jqLogin.css('display', 'none');
jqWaiting.css('display', 'none');
jqChatroom.css('display', '');
jqHistory.empty();
historyCounter = 0;
activeState = 'chat';
jqLine.bind('keydown', inputKeypress);
},
'onopen': function () {
connection.close(4001, 'Connection opened while chatting');
},
'messageHandlers': {
'line': function (msgObj) {
addLine(msgObj.nick, msgObj.line);
},
'join': function (msgObj) {
addLine(null, 'Priklopil: ' + msgObj.nick);
addMember(msgObj.nick);
},
'leave': function (msgObj) {
addLine(null, 'Odklopil: ' + msgObj.nick);
removeMember(msgObj.nick);
}
},
'unhandledMessage': function (msgObj) {
connection.close(4000, 'Unhandled message type');
},
'onclose': function () {
addLine(null, 'Connection closed');
jqLine.unbind('keydown', inputKeypress);
}
}
},
activeState = 'login';
states.login.start();
});
// node.js code
var http = require('http'),
url = require('url'),
path = require('path'),
fs = require('fs'),
nano = require('nano')('http://localhost:5984'),
chatroomDb = nano.use('chatroom'),
websocket = require('websocket'),
chatHistory = [],
activeUsers = {};
var filesDir = path.join(process.cwd(), 'web');
var mimeTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript'
};
var getContentType = function (extension) {
var mimeType = mimeTypes[extension];
return mimeType ? mimeType : 'application/octet-stream';
};
var server = http.createServer(function (request, response) {
var relativePath = url.parse(request.url).pathname,
absolutePath = path.join(filesDir, relativePath);
var handler = function (err, stats) {
if (stats) {
if (stats.isDirectory()) {
absolutePath = path.join(absolutePath, 'index.html');
fs.stat(absolutePath, handler);
return;
}
if (stats.isFile()) {
response.writeHead(200, getContentType(path.extname(absolutePath)));
var stream = fs.createReadStream(absolutePath);
stream.pipe(response);
return;
}
}
response.writeHead(404, {'Content-Type': 'text/plain'});
response.write('Not found\r\n');
response.end();
};
console.log('HTTP request for ' + relativePath);
fs.stat(absolutePath, handler);
});
server.listen(8080, function () {});
wsServer = new websocket.server({
'httpServer': server
});
function addLine (type, nick, line) {
var msg = { 'type': type, 'nick': nick, 'line': line },
jsonMsg = JSON.stringify(msg),
username;
chatHistory.push(msg);
while (chatHistory.length > 100) chatHistory.shift();
for (username in activeUsers) {
activeUsers[username].sendMessage(jsonMsg);
}
}
wsServer.on('request', function (request) {
console.log('New websocket connection from ' + request.origin);
// TODO: verify that request.origin is our web site
var connection = request.accept(null, request.origin);
var username = null;
connection.on('message', function (message) {
if (message.type !== 'utf8') {
console.log('Refusing a non-utf8 message');
return;
}
console.log('Processing message: ' + message.utf8Data);
try {
var m = JSON.parse(message.utf8Data);
switch (m.type) {
case 'login':
chatroomDb.get(m.username, function (err, body) {
if (err || (body.password !== m.password)) {
connection.close();
return;
}
username = m.username;
addLine('join', username, null);
activeUsers[username] = {
'sendMessage': function (jsonMsg) {
connection.sendUTF(jsonMsg);
}
};
var users = [], u;
for (u in activeUsers) users.push(u);
connection.sendUTF(JSON.stringify({ 'type': 'state', 'history': chatHistory, 'users': users }));
});
break;
case 'line':
if (!username) {
connection.close();
break;
}
addLine('line', username, m.line);
break;
}
}
catch (e) {
console.log(e);
}
});
connection.on('close', function (connection) {
console.log('Connection closed');
if (username) {
delete activeUsers[username];
addLine('leave', username, null);
}
});
});
console.log('Server running');
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Chatroom</title>
<style>
html, body {
width: 100%;
height: 100%;
padding: 0;
border: none;
margin: 0;
}
#heading {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 30px;
margin: 0;
padding: 0;
line-height: 30px;
text-align: center;
font-size: 20px;
background-color: green;
}
#outer {
position: absolute;
top: 30px;
bottom: 0;
left: 0;
right: 0;
margin: 20px;
min-height: 400px;
min-width: 400px;
background-color: lime;
}
#inner {
height: 100%;
background-color: #ffc0cb;
}
#chat {
position: absolute;
top: 0;
left: 0;
right: 200px;
bottom: 0;
background-color: #ffd700;
}
#members {
position: absolute;
top: 0;
right: 10px;
width: 180px;
bottom: 0;
background-color: #ff00ff;
list-style-type: none;
padding: 10px;
padding: 0;
border: none;
}
#history {
position: absolute;
top: 0;
left: 0;
bottom: 2em;
right: 0;
background-color: #00ffff;
padding: 10px;
}
#input {
position: absolute;
height: 2em;
left: 0;
right: 0;
bottom: 0;
background-color: #90ee90;
line-height: 2em;
}
#line {
width: 100%;
margin: 0;
border: none;
padding: 0;
}
#history > p {
margin: 2px;
}
.nick {
white-space: pre;
display: table-cell;
}
.line {
display: table-cell;
}
#login {
height: 100%;
display: table;
margin: 0 auto;
}
#login > .svg {
vertical-align: middle;
display: table-cell;
}
#login-wrapper1 {
display: table;
height: 100%;
width: 100%;
}
#login-wrapper2 {
display: table-cell;
vertical-align: middle;
}
#login-table {
margin: 0 auto;
}
#login-table .label {
text-align: right;
}
#waiting {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
opacity: 0.3;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="client.js"></script>
</head>
<body>
<div id="login">
<div class="svg">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 400 400"
width="400"
height="400">
<defs>
<path id="curved-text" d="M0,0 c 50 -50 150 -50 200 0" />
</defs>
<g>
<text
transform="translate(-100,40)"
font-weight="bold"
font-variant="small-caps"
font-family="Arial sans-serif"
font-size="30"
fill="none"
stroke="orange"
text-anchor="middle">
<textPath xlink:href="#curved-text" startOffset="50%">Chatroom</textPath>
</text>
<animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0" to="360" dur="5s" fill="remove" additive="sum" repeatCount="indefinite" />
<animateMotion dur="10s" repeatCount="indefinite" path="M100,200 a100,100 0 1 1 200,0 a100,100 0 1 1 -200,0" />
</g>
<foreignObject
x="0"
y="0"
width="400"
height="400"
style="height:400px;">
<div xmlns="http://www.w3.org/1999/xhtml" id="login-wrapper1">
<div id="login-wrapper2">
<table id="login-table">
<tbody>
<tr>
<td class="label">Vzdevek:</td><td><input type="text" id="nickname" /></td>
</tr>
<tr>
<td class="label">Geslo:</td><td><input type="password" id="password" /></td>
</tr>
</tbody>
</table>
</div>
</div>
</foreignObject>
</svg>
</div>
</div>
<div id="chatroom" style="display:none;">
<h1 id="heading">Chatroom</h1>
<div id="outer">
<div id="inner">
<div id="chat">
<div id="history">
<p><span class="nick">Matej: </span><span class="line">Hi</span></p>
<p><span class="nick">Borut: </span><span class="line">How are you?</span></p>
<p><span class="nick">Martina: </span><span class="line">Ok, thanks!</span></p>
</div>
<div id="input"><input id="line" type="text"></div>
</div>
<ul id="members">
<li>Matej</li>
<li>Borut</li>
<li>Martina</li>
</ul>
</div>
</div>
</div>
<div id="waiting" style="display:none;"></div>
</body>
</html>
If you are evaluating options, check out IP Messaging from Twilio:
https://www.twilio.com/docs/tutorials/walkthrough/ip-chat/ios/swift#0
The tutorial above in Swift for iOS (also available for Objective-C) allows you to work with a native SDK while the server side app (yours in Node.js) generates user access tokens to connect to the API.
The code walks you through joining a channel, creating a channel and sending messages.
Note: I do work for Twilio. This quick sample app should at least help you determine what functionality will work best for you.

Issue with hidden div-jquery

i'm using jquery hashchange technique for dynamically and smoothly(fadein) loading the contents in a website.Though it's dynamic the url gets changed in the addressbar which looks as follows..as you can see a pound symbol appears before filename.
www.whatever.com/#about.html
www.whatever.com/#contact.html and so on.
In my index page a div named 'folder1' is hidden by default and it's made visible 5seconds after the page is loaded and there is div folder2(check the code).
When you type url 'www.whatever.com' everything works as it should. But when you hit 'home' link it appends # to index.html so url will be whatever.com/#index.html.
And this time div 'folder2' show up right after page is loaded which should be hidden as per the code. I noticed css of those divs gets messed up this time.
I don't understand what's happpening there. Any help?
(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);
$(function() {
var newHash = "",
$mainContent = $("#main-content"),
$pageWrap = $("#page-wrap"),
baseHeight = 0,
$el;
$pageWrap.height($pageWrap.height());
baseHeight = $pageWrap.height() - $mainContent.height();
$("nav").delegate("a", "click", function() {
window.location.hash = $(this).attr("href");
return false;
});
$(window).bind('hashchange', function(){
newHash = window.location.hash.substring(1);
if (newHash) {
$mainContent
.find("#guts")
.fadeOut(200, function() {
$mainContent.hide().load(newHash + " #guts", function() {
$mainContent.fadeIn(200, function() {
$pageWrap.animate({
height: baseHeight + $mainContent.height() + "px"
});
});
$("nav a").removeClass("current");
$("nav a[href="+newHash+"]").addClass("current");
});
});
};
});
$(window).trigger('hashchange');
});
above makes the entire jquery code.
this is the css.infact there are two divs.
#folder1{
float: left;
height: 100px;
width: 100px;
position: absolute;
top: 15px;
right: 100px;
display: none;
}
#folder2{
float: left;
height: 100px;
width: 100px;
position: absolute;
top: 15px;
right: 100px;
display: show;
}
below is the code usedto hide and show divs.
$(document).ready(function() {
$("#folder2").hide();
setTimeout(function(){
$("#folder1").show();
}, 5000);
setTimeout(function(){
$("#folder1").hide();
}, 10000);
setTimeout(function(){
$("#folder2").show();
}, 15000);
});

Resources