laravel set up ckeditor/ckeditor5-export-pdf - laravel

I try to set up plugin ckeditor/ckeditor5-export-pdf on my Laravel App But I cant do this. I still get issues like: Uncaught TypeError: Failed to resolve module specifier "#ckeditor/ckeditor5-export-pdf/src/exportpdf". Relative references must start with either "/", "./", or "../".
I did all steps as in docs: https://ckeditor.com/docs/ckeditor5/latest/features/export-pdf.html#configuration But when I try use import ExportPdf from '#ckeditor/ckeditor5-export-pdf/src/exportpdf'; I get the error like above. Please help. Maybe some have stuck on this issue before
import ExportPdf from '#ckeditor/ckeditor5-export-pdf/src/exportpdf';
console.log(ExportPdf);
$(document).ready(function () {
/*function ExportPdf(editor) {
editor.execute('exportPdf');
}*/
function SimpleUploadAdapter(editor) {
editor.plugins.get('FileRepository').createUploadAdapter = function(loader) {
return {
upload: function() {
return loader.file
.then(function (file) {
return new Promise(function(resolve, reject) {
// Init request
var xhr = new XMLHttpRequest();
xhr.open('POST', '/admin/instructions/ckmedia', true);
xhr.setRequestHeader('x-csrf-token', window._token);
xhr.setRequestHeader('Accept', 'application/json');
xhr.responseType = 'json';
// Init listeners
var genericErrorText = `Couldn't upload file: ${ file.name }.`;
xhr.addEventListener('error', function() { reject(genericErrorText) });
xhr.addEventListener('abort', function() { reject() });
xhr.addEventListener('load', function() {
var response = xhr.response;
if (!response || xhr.status !== 201) {
return reject(response && response.message ? `${genericErrorText}\n${xhr.status} ${response.message}` : `${genericErrorText}\n ${xhr.status} ${xhr.statusText}`);
}
$('form').append('<input type="hidden" name="ck-media[]" value="' + response.id + '">');
resolve({ default: response.url });
});
if (xhr.upload) {
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
loader.uploadTotal = e.total;
loader.uploaded = e.loaded;
}
});
}
// Send request
var data = new FormData();
data.append('upload', file);
data.append('crud_id', {{ $instruction->id ?? 0 }});
xhr.send(data);
});
})
}
};
}
}
var allEditors = document.querySelectorAll('.ckeditor');
for (var i = 0; i < allEditors.length; ++i) {
ClassicEditor.create(
allEditors[i], {
extraPlugins: [SimpleUploadAdapter, /*ExportPdf*/],
/*toolbar: [
'exportPdf', '|',
],
exportPdf: {
stylesheets: [
'./path/to/fonts.css',
'EDITOR_STYLES',
'./path/to/style.css'
],
fileName: 'my-file.pdf',
converterOptions: {
format: 'A4',
margin_top: '20mm',
margin_bottom: '20mm',
margin_right: '12mm',
margin_left: '12mm',
page_orientation: 'portrait'
}
}*/
}
);
}
});
</script>```

I solved my problem with https://ckeditor.com/ckeditor-5/online-builder/ Builded what I want and setup it on my App

Related

Service Worker "notificationclick" not firing

The notification is showing fine, but when I click on it, or any of the actions, nothing happens. I see no logging, no error messages, but the notification does close (although it closes even when I comment out the event.notification.close()).
I've tried using the Chrome debugger, and I can set a break point in the code that shows the notification, but all breakpoints within the notificationclick handler fail to pause execution.
I've spent days trying to get this to work and I'm at my wits' end.
const auth = firebase.auth();
const functions = firebase.functions();
const done = functions.httpsCallable("done");
const snooze = functions.httpsCallable("snooze");
self.addEventListener("notificationclick", event => {
console.log("notificationclick", event);
const uid = auth.currentUser.uid;
const { id, url } = event.notification.data;
event.notification.close();
event.waitUntil(() => {
switch (event.action) {
case "done":
console.log("Done");
return done({ uid, id });
case "snooze1":
console.log("Snooze 1 Hour");
return snooze({ uid, id, hours: 1 });
case "snooze24":
console.log("Snooze 1 Day");
return snooze({ uid, id, hours: 24 });
default:
console.log("Open App");
return clients
.matchAll({
includeUncontrolled: true,
type: "window"
})
.then(clientList => {
for (let i = 0; i < clientList.length; i++) {
let client = clientList[i];
if (url[0] === "#") {
if (client.url.endsWith(url) && "focus" in client) {
return client.focus();
}
} else {
if (
client.url.replace(/#.*$/, "") === url &&
"focus" in client
) {
return client.focus();
}
}
}
if (clients.openWindow) {
return clients.openWindow(location.origin + url);
}
});
}
});
});
firebase
.messaging()
.setBackgroundMessageHandler(({ data: { title, options } }) => {
options = JSON.parse(options);
options.actions = [
{ action: "done", title: "Done" },
{ action: "snooze1", title: "Snooze 1 Hour" },
{ action: "snooze24", title: "Snooze 1 Day" }
];
return self.registration.showNotification(title, options);
});
Hi Could you try below code and see if this is getting called-
self.addEventListener('notificationclick', function (event) {
event.notification.close();
var redirectUrl = null;
var tag = event.notification.tag;
if (event.action) {
redirectUrl = event.action
}
if (redirectUrl) {
event.waitUntil(async function () {
var allClients = await clients.matchAll({
includeUncontrolled: !0
});
var chatClient;
for (const client of allClients) {
if (redirectUrl != '/' && client.url.indexOf(redirectUrl) >= 0) {
client.focus();
chatClient = client;
break
}
}
if (chatClient == null || chatClient == 'undefined') {
chatClient = clients.openWindow(redirectUrl);
return chatClient
}
}().then(result => {
if (tag) {
//PostAction(tag, "click")
}
}))
}
});
Edited-
Attaching both js files. it is working at my end.
firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js');
var config = {
apiKey: "your api key",
authDomain: "you firebase domain",
databaseURL: "your firbase db url",
projectId: "your project id",
storageBucket: "",
messagingSenderId: "sender id"
};
firebase.initializeApp(config);
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload.data);
var notificationTitle = payload.data.Title;
var notificationOptions = {
body: payload.data.Body,
icon: payload.data.Icon,
image: payload.data.Image,
action: payload.data.ClickAction
};
console.log("strated sending msg" + notificationOptions);
return self.registration.showNotification(notificationTitle,notificationOptions);
});
self.addEventListener('notificationclick', function (event) {
console.log('On notification click: ', event.notification);
event.notification.close();
var redirectUrl = null;
if (event.notification.data) {
if (event.notification.data.FCM_MSG) {
redirectUrl = event.notification.data.FCM_MSG.data ? event.notification.data.FCM_MSG.data.click_action : null
} else {
redirectUrl = event.notification.data ? event.notification.data.click_action : null
}
}
console.log("redirect url is : " + redirectUrl);
if (redirectUrl) {
event.waitUntil(async function () {
var allClients = await clients.matchAll({
includeUncontrolled: true
});
var chatClient;
for (var i = 0; i < allClients.length; i++) {
var client = allClients[i];
if (client['url'].indexOf(redirectUrl) >= 0) {
client.focus();
chatClient = client;
break;
}
}
if (chatClient == null || chatClient == 'undefined') {
chatClient = clients.openWindow(redirectUrl);
return chatClient;
}
}());
}
});
self.addEventListener("notificationclose", function (event) {
event.notification.close();
console.log('user has clicked notification close');
});
application.js file :
/// <reference path="scripts/jquery-3.3.1.js" />
try {
var config = {
apiKey: "your api key",
authDomain: "you firebase domain",
databaseURL: "your firbase db url",
projectId: "your project id",
storageBucket: "",
messagingSenderId: "sender id"
};
firebase.initializeApp(config);
if ('serviceWorker' in navigator && 'PushManager' in window) {
console.log('Service Worker and Push is supported');
navigator.serviceWorker
.register('/firebase-messaging-sw.js')
.then((swReg) => {
firebase.messaging().useServiceWorker(swReg);
askForPermissioToReceiveNotifications();
})
.catch(function (error) {
console.error('Service Worker Error', error);
window.alert("Service Worker Error" + error);
})
} else {
console.warn('Push messaging is not supported');
window.alert("Push messaging is not supported " + (navigator.serviceWorker));
}
const askForPermissioToReceiveNotifications = async () => {
try {
const messaging = firebase.messaging();
console.log(messaging);
await messaging.requestPermission();
const token = await messaging.getToken();
if (token !== null || token !== 'undefined') {
await sendDeviceTokenToServerSide(token);
}
console.log('Got token : ' + token);
messaging.onMessage(function (payload) {
console.log('onMessage: ', payload);
setTimeout(() => {
navigator.serviceWorker.ready.then(function (registration) {
var notificationTitle = payload.notification.title;
var notificationOptions = {
body: payload.notification.body,
data: payload.data,
icon: payload.notification.icon,
image: payload.data.Image,
requireInteraction: payload.notification.requireInteraction,
tag: payload.notification.tag,
click_action: payload.data.click_action,
requireInteraction: true
};
registration.showNotification(notificationTitle, notificationOptions);
},50)
});
});
}
catch (e) { console.log('error in getting token: ' + e); window.alert("error in getting token: " + e); }
}
function sendDeviceTokenToServerSide(token) {
$.ajax({
type: 'POST',
url: '/Home/StoreToken',
timeout: 5000000,
data: { token: token },
success: function (success) {
console.log("device token is sent to server");
},
error: function (error) {
console.log("device error sending token to server : " + error);
window.alert("device error sending token to server : " + error);
}
});
}
} catch (e) {
window.alert("error: " + e);
}
function GetFcmUserToken(messaging) {
messaging.onTokenRefresh(function () {
messaging.getToken()
.then(function (refreshedToken) {
console.log('Token refreshed.');
return refreshedToken;
})
.catch(function (err) {
console.log('Unable to retrieve refreshed token ', err);
showToken('Unable to retrieve refreshed token ', err);
});
});
}
self.addEventListener('notificationclick', function (event) {
const clickedNotification = event.notification;
// Do something as the result of the notification click
const promiseChain = clients.openWindow(clickedNotification.data.Url);
event.waitUntil(promiseChain);
});
This code inside service worker js worked fine for me on chrome Desktop and Android.

Upload multiple file with VueJs and Laravel

I try to do a single post request to upload multiple files. Now I have a functionally method that works for multiple files. But I want a single request.
submitFile(){
this.contract_file.forEach((file) =>{
let formData = new FormData();
formData.append('file', file.file);
axios.post('contracts/uploadfile/' + this.id_contract,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
}
}
).then(function(){
//
})
.catch(function(){
//
});
})
},
public function uploadFile(Request $request, Contract $contract)
{
$filename = $request->file('file')->getClientOriginalName();
$path = $request->file('file')->store($contract->id,'uploads');
$contractFile = new ContractFile();
$contractFile->fill([
'contract_id' => $contract->id,
'name' => $filename,
'path' => $path,
])->save();
}
Update:
This is what I changed,but..
let formData = []
this.contract_file.forEach((file,index) =>{
formData[index] = new FormData();
formData[index].append('file', file.file);
})
foreach($request->file('file') as $file){
//same code but I use $fille
}
Message:
Missing boundary in multipart/form-data POST data in Unknown
Update2:
<file-upload
class="btn btn-primary"
:multiple="true"
:drop="true"
:drop-directory="true"
v-model="files"
#input-filter="inputFilter"
#input-file="inputFile"
ref="upload">
<i class="fa fa-plus"></i>
Select files
</file-upload>
My answer is not properly tested since I had to adapt my code. Let me know if it doesn't work or if I'm missing something.
Basically, I built my own FormData to be more flexible and easier to reuse.
Form.vue
<template>
<div>
<input #change="upload($event)"
type="file"
name="picture"
id="new-file"
class="custom-file-input"
aria-label="picture"
multiple
>
<label class="custom-file-label" for="new-file">
<span>File...</span>
<span class="btn-primary">Browse</span>
</label>
<button #click="submit" type="button" >Submit</button>
</div>
<template>
<script>
import MyFormData from "./MyFormData";
export default {
data() {
return {
form: new MyFormData({contract_id: 5, files: []})
}
},
methods: {
upload(event) {
for (let file of event.target.files) {
try {
let reader = new FileReader();
reader.readAsDataURL(file); // Not sure if this will work in this context.
this.form.files.push(file);
} catch {}
}
},
submit(){
this.form.post('/my-url')
.catch(errors => {
throw errors;
})
.then((response) => location = response.data.redirect);
}
}
}
</script>
MyFormData.js
export default class MyFormData {
constructor(data, headers) {
// Assign the keys with the current object MyFormData so we can access directly to the data:
// (new FormData({myvalue: "hello"})).myvalue; // returns 'hello'
Object.assign(this, data);
// Preserve the originalData to know which keys we have and/or reset the form
this.originalData = JSON.parse(JSON.stringify(data));
this.form = null;
this.errors = {};
this.submitted = false;
this.headers = headers || {}
}
// https://stackoverflow.com/a/42483509/8068675
// It will build a multi-dimensional Formdata
buildFormData(data, parentKey) {
if (data && typeof data === 'object' && !(data instanceof Date) && !(data instanceof File) && !(data instanceof Blob)) {
Object.keys(data).forEach(key => {
this.buildFormData(data[key], parentKey ? `${parentKey}[${key}]` : key);
});
} else {
const value = data == null ? '' : data;
this.form.append(parentKey, value);
}
}
// Returns all the new / modified data from MyFormData
data() {
return Object.keys(this.originalData).reduce((data, attribute) => {
data[attribute] = this[attribute];
return data;
}, {});
}
post(endpoint) {
return this.submit(endpoint);
}
patch(endpoint) {
return this.submit(endpoint, 'patch');
}
delete(endpoint) {
return axios.delete(endpoint, {}, this.headers)
.catch(this.onFail.bind(this))
.then(this.onSuccess.bind(this));
}
submit(endpoint, requestType = 'post') {
this.form = new FormData();
this.form.append('_method', requestType);
this.buildFormData(this.data());
return axios.post(endpoint, this.form, {
headers: {
'Content-Type': `multipart/form-data; boundary=${this.form._boundary}`,
}
})
.catch(this.onFail.bind(this))
.then(this.onSuccess.bind(this));
}
onSuccess(response) {
this.submitted = true;
this.errors = {};
return response;
}
onFail(error) {
console.log(error);
this.errors = error.response.data.errors;
this.submitted = false;
throw error;
}
reset() {
Object.assign(this, this.originalData);
}
}
Edit Based on your note specifying you're using vue-upload-component
Your submit method should look like this
submitFile(){
let files = this.contract_file.map((obj) => obj.file));
let form = new MyFormData({files: files});
form.post('contracts/uploadfile/' + this.id_contract)
.then(function(){
//
})
.catch(function(){
//
});
},
In your controller
public function uploadFile(Request $request, Contract $contract) {
if($request->hasFile('files')){
$files = $request->file('files');
foreach ($files as $file) {
$filename = $file->getClientOriginalName();
$path = $file->store($contract->id,'uploads');
$contractFile = new ContractFile();
$contractFile->fill([
'contract_id' => $contract->id,
'name' => $filename,
'path' => $path,
])->save();
}
}
}
Adding the boundary to the Content-Type header fixed my problem. You can do it like below. Just change only submitFile() function.
submitFile(){
this.contract_file.forEach((file) =>{
let formData = new FormData();
formData.append('file', file.file);
axios.post('contracts/uploadfile/' + this.id_contract,
formData,
{
headers: {
'Content-Type': 'multipart/form-data;boundary=' + Math.random().toString().substr(2),
}
}
).then(function(){
//
})
.catch(function(){
//
});
})
},

WebSocket connection from node module to chrome extension closing automatically in Windows 10

I use https://www.npmjs.com/package/ws npm package to communicate between my npm module and my chrome extension.
Connection is getting opened, but closes in few seconds.
I researched the topic and I found that I should send messages back and forth between webSocket server and Client to keep it alive.
I am sanding this messages every 2 sec but my connection is still closing.
Interesting stuff is that close event fires on node module side only, while chrome plugin does not fire the event. Here is a code from my chrome extension:
function myPlugin() {
myPlugin.prototype.socket = false;
myPlugin.prototype.isConnected = false;
myPlugin.prototype.port = false;
myPlugin.prototype.pluginName = "Tab Reloader";
myPlugin.prototype.init();
};
myPlugin.prototype = {
initListeners: function () {
chrome.browserAction.onClicked.addListener(function(tab) {
if (!this.isConnected) {
this.connectToServer();
} else {
this.disconnectFromServer();
}
}.bind(this));
},
setPort: function () {
chrome.storage.sync.get(['port'], function(items) {
this.port = items.port || '8001';
}.bind(this));
},
getTabsToReload: function (callback) {
var tabsToReload = [];
chrome.storage.sync.get(['hostName'], function(items) {
if (!items.hostName) {
chrome.tabs.query({active: true}, function(tabs) {
tabs.forEach(function (tab) {
tabsToReload.push(tab);
});
callback(tabsToReload);
});
} else {
chrome.tabs.query({}, function(tabs) {
tabs.forEach(function (tab) {
if (tab.url.indexOf(items.hostName) != -1) {
tabsToReload.push(tab);
}
});
callback(tabsToReload);
});
}
}.bind(this));
},
initSocketListeners: function () {
var fileExtIndex,
fileExt,
file;
this.socket.onmessage = function (ev) {
file = ev.data.toString();
fileExtIndex = file.lastIndexOf('.') + 1;
fileExt = file.slice(fileExtIndex),
fileNameStandardize = file.replace(/\\/g, '\/'),
indexOfLastSeparator = fileNameStandardize.lastIndexOf('/') + 1,
fileName = file.slice(indexOfLastSeparator);
if (file != 'pong' && file.indexOf('connected to server!!!') == -1) {
//do stuff
} else {
if (file == 'pong') {
this.isAlive = true;
}
}
}.bind(this);
this.socket.addEventListener('close', function (ev) {
console.log('connection Closed')
clearInterval(this.aliveInterval);
chrome.browserAction.setIcon({
path: {
"16": "img/icon_disabled_16.png",
"24": "img/icon_disabled_24.png",
"32": "img/icon_disabled_32.png"
}
});
this.isConnected = false;
}.bind(this));
},
connectToServer: function () {
this.socket = new WebSocket("ws://localhost:" + this.port);
this.socket.addEventListener('error', function (ev) {
this.isConnected = false;
alert('Error connecting to websocket server, make sure it\'s running and port ' + this.port + ' is not occupied by other process');
}.bind(this));
this.socket.addEventListener('open', function (ev) {
this.isConnected = true;
this.socket.send(this.pluginName + ' connected to server!!!');
this.initSocketListeners();
this.stayConnected();
this.isAlive = true;
this.aliveInterval = setInterval(function () {
this.checkIfAlive();
}.bind(this), 2500);
this.getTabsToReload(function (tabsToReload) {
tabsToReload.forEach(function (tab) {
chrome.tabs.update(tab.id, {url: tab.url});
});
});
chrome.browserAction.setIcon({
path: {
"16": "img/icon_active_16.png",
"24": "img/icon_active_24.png",
"32": "img/icon_active_32.png"
}
});
}.bind(this));
},
disconnectFromServer: function () {
this.socket.close();
},
stayConnected: function () {
setTimeout(function () {
this.socket.send('ping');
if (this.isConnected) {
this.stayConnected();
}
}.bind(this), 1000);
},
checkIfAlive: function () {
this.isAlive = false;
setTimeout(function () {
console.log(this.isAlive)
if (!this.isAlive) {
console.log(this.isAlive)
this.disconnectFromServer();
}
}.bind(this), 2000);
},
init: function () {
this.setPort();
this.initListeners();
}
}
window.onload = new myPlugin();
And here is my node module code:
"use strict";
//var WebSocketServer = require('websocketserver');
//var WebSocketServer = require("nodejs-websocket")
var WebSocket = require('ws');
class MyModule {
constructor(options) {
this.options = options;
this.server = false;
this.pluginName = 'Some';
this.isConnected = false;
this.connection = false;
this.init();
return this.refreshTab.bind(this);
}
init() {
var port = this.options ? this.options.port : false;
this.server = new WebSocket.Server({port: port || 8001});
this.server.on('connection', (websocket) => {
console.log('Connection open');
this.websocket = websocket;
this.isConnected= true;
this.initListeners();
});
}
refreshTab(uploadedFiles) {
if (this.isConnected) {
if (Array.isArray(uploadedFiles)) {
uploadedFiles.forEach(function (el) {
this.websocket.send(el.toString());
}.bind(this));
} else {
this.websocket.send(uploadedFiles ? uploadedFiles.toString() : 'reload');
}
} else {
console.log('You are not connected to server yet.');
}
}
initListeners() {
this.websocket.on('message', (message) => {
if (message == 'ping') {
this.websocket.send('pong');
}
});
this.websocket.on('close', () => {
this.isConnected = false;
console.log(this.pluginName + ' connection is closed');
});
}
};
module.exports = MyModule;
Appreciate any help,
Thanks in advance.
So my problem was actually in manifest.json
"background": {
"scripts": ["js/tab_reloader.js"],
"persistent": false
}
"persistent" was set to false, it transformed my Background page into Event Page. And event page is unloaded from memory after a period of time, which was killing the connection. After I set "persistent" to true the problem got fixed.

MutationObserver not observing in ie11 after using its disconnect function

My code is as below
var originalTitle = document.title.split("#")[0];
var testtar = document.getElementsByTagName('title')[0];
try{
document.attachEvent('onpropertychange', function (evt) {
console.log('inside attachEvent');
if(evt.propertyName === 'title' && document.title !== originalTitle) {
setTimeout(function () {
document.title = originalTitle;
}, 0);
}
});
}
catch(e){
function disconnect(){
observer.disconnect();
setTimeout(function(){
observer.observe(testtar, config);
console.log(observer)
},1000);
}
var observer = new MutationObserver(function(mutations) {
testtar.innerHTML = originalTitle;
disconnect();
});
var config = { attributes: true, childList: true, characterData: true, characterDataOldValue: true };
observer.observe(testtar, config);
};
I am trying to check for title change using MutationObserver. but once i call observer.disconnect() and again call its observe() method it doesn't work.
the title changes for the second time but still testtar.innerHTML is not set to originalTitle. please help

AngularJS - Strange behaviour of promises in connection with notify()

As I want to implement a chat in AngularJS, I want to use the promise/deferred principle. My ChatService looks like the following:
factory('ChatService', ['$q', '$resource', function($q, $resource) {
var Service = {};
var connected = false;
var connection;
var chatResource = $resource('/guitars/chat/:action', {action: '#action'}, {
requestChatroomId: {
params: {
action: 'requestChatroomId'
},
method: 'GET'
},
sendMessage: {
params: {
action: 'sendMessage'
},
method: 'POST'
}
});
Service.connect = function(cb) {
var deferred = $q.defer();
chatResource.requestChatroomId(function(data) {
connection = new WebSocket('ws://127.0.0.1:8888/realtime/' + data.chatroomId);
connection.onerror = function (error) {
deferred.reject('Error: ' + error);
};
connection.onmessage = function (e) {
cb.call(this, e.data);
deferred.notify(e.data);
};
connected = true;
});
return deferred.promise;
};
Service.sendMessage = function(msg) {
if(!connected) {
return;
}
chatResource.sendMessage({message: msg});
}
return Service;
}])
My controller using the ChatService is:
app.controller('ChatCtrl', ['$scope', 'ChatService', function($scope, ChatService) {
$scope.chat = {};
$scope.chat.conversation = [];
var $messages = ChatService.connect(function(message) {
$scope.$apply(function() {
// #1 THIS FIRES EVERY TIME
$scope.chat.conversation.push(message);
});
});
$messages.then(function(message) {
console.log('Finishes - should never occur!')
}, function(error) {
console.log('An error occurred!')
}, function(message) {
// #2 THIS FIRES ONLY IF THERE IS AN INTERACTION WITH THE ANGULAR MODEL
console.log(message);
});
$scope.sendMessage = function(event) {
ChatService.sendMessage($scope.chat.message);
$scope.chat.message = '';
};
}]);
If something is pushed from the server, callback #1 is called, but callback #2 wont be called until there is some interaction with the angular-model, i.e. start writing something in the input-Box. What is the reason for that behaviour?
Okay the reason was, that AngularJS was not aware of a change. So I injected the $rootScope to my ChatService:
factory('ChatService', ['$q', '$resource', '$rootScope', function($q, $resource, $rootScope) {
and in connection.onmessage I called $apply() on $rootScope:
connection.onmessage = function (e) {
deferred.notify(e.data);
$rootScope.$apply();
};

Resources