Firebase + Phonegap plugin push: How to send silent messages but update app? - django

My current stack is:
Django using FCM to send push notifications to an Ionic app. The app uses the phonegap-plugin-push.
I have the problem, that the on notification handler doesn't get called.
Here is the data that I'm sending:
'message': {
'token': '<my-device-token>',
'data': {
'yup': 'okay'
},
'apns': {
'payload': {
'aps': {
'data': 'here is my data',
'badge': 1,
'content-available': 1
},
'notId': 2
}
}
}
The app gets the data, but somehow the on notificatoin handler doesn't get called.
Also here is my code in the app:
import { Injectable } from '#angular/core';
import { Push, PushObject, PushOptions } from '#ionic-native/push';
import { AlertController, Platform } from 'ionic-angular';
import { FcmDataProvider } from './fcm.data';
#Injectable()
export class FcmProvider {
/*
* FIREBASE CLOUD MESSAGING
*/
constructor(private push: Push,
private alertCtrl: AlertController,
private platform: Platform,
private fcmDataProv: FcmDataProvider) {
}
getPermission(): Promise<{isEnabled: boolean}> {
// Listen for res.isEnabled.
return this.push.hasPermission();
}
initPush() {
console.log("Init push!");
const options: PushOptions = this.initPushOptions();
const pushObject: PushObject = this.push.init(options);
pushObject.on('notification').subscribe((notification: any) => {
console.log('Received a notification', notification);
if(this.platform.is('ios')) {
this.handleIOSNotification(notification, pushObject);
} else if(this.platform.is('android')) {
this.handleAndroidNotification(notification);
}
this.presentSuccessAlert(notification.message);
});
pushObject.on('registration').subscribe(
(registration: any) => {
console.log('Device registered', registration);
// TODO: Send registration.registrationId to server and update it.
}
);
pushObject.on('error').subscribe(
error => console.error('Error with Push plugin', error)
);
}
private initPushOptions(): PushOptions {
return {
android: {
sound: true,
vibrate: true,
clearBadge: true
},
ios: {
alert: true,
badge: true,
sound: true,
clearBadge: true
},
windows: {}, // Lol
browser: {
pushServiceURL: 'http://push.api.phonegap.com/v1/push'
}
};
}
private handleIOSNotification(data, push: PushObject) {
push.finish().then(
() => console.log("Finished processing push data")
).catch(() => console.error(
"Something went wrong with push.finish for ID=", data.additionalData.notId
));
}
private handleAndroidNotification(data) {
console.log(data.data);
}
private presentSuccessAlert(message: string): void {
let alert = this.alertCtrl.create({
title: "Neue Benachrichtigung",
message: message,
buttons: ["Ok"]
});
alert.present();
}
}
I'm testing on iOS but I would love to know how to handle it on android as well.
Edit:
Here is the console.log I receive from XCode:
Push Plugin notId 1
Warning: Application delegate received call to -application:didReceiveRemoteNotification:fetchCompletionHandler: but the completion handler was never called.
Notification received
Push Plugin key: content-available
Push Plugin key: data
Push Plugin key: badge

Wow this issue is super silly.
But here is what was wrong: You got to put notId first!
Like this:
"notId": 1, # notId HAS TO BE FIRST!!!
'aps': {
'data': 'here is my data',
'content-available': 1,
}

Related

expo-task-manager with expo-location error

Please provide the following:
SDK Version: "expo": "^45.0.0",
IOS Emulator (atm) but all normally.
expo-task-manager & expo-location
Hello, We are setting up background location tracking in our App...
I am defining my background location task before is loaded
BackgroundLocation.ts
export const LOCATION_TASK_NAME = 'background-location-task'
function init() {
TaskManager.defineTask(LOCATION_TASK_NAME, _execute)
setTimeout(() => {
TaskManager.getRegisteredTasksAsync().then((tasks) =>
console.log('registered tasks', tasks) // logs empty array
)
}, 5000)
}
export default { init }
App.tsx
import BackgroundLocation from '../../......'
BackgroundLocation.init()
export default App() { ... }
Now in the App component hiararchy i have a Component called <LocationTracker />
import { LOCATION_TASK_NAME } from '../../BackgroundLocation'
...
React.useEffect(() => {
async function handleLocation() {
try {
if (backgroundLocationServicesEnabled) {
await Location.startLocationUpdatesAsync(
LOCATION_TASK_NAME,
{
accuracy: Location.Accuracy.Balanced,
distanceInterval: 20,
}
)
} else {
await Location.stopLocationUpdatesAsync(
LOCATION_TASK_NAME
).catch((e) => {
console.error(
'error calling stopLocationUpdatesAsync',
e
)
})
}
} catch (e) {
console.error('error in handleLocation', e)
}
}
handleLocation().then(() => {})
}, [backgroundServicesEnabled])
Background location is not enabled in my case so the error being fired is
error calling stopLocationUpdatesAsync, [Error: Task 'background-location-task' not found for app ID 'mainApplication'.]
at components/LocationTracker.tsx:70:37 in Location.stopLocationUpdatesAsync._catch$argument_0
its this part
[Error: Task 'background-location-task' not found for app ID 'mainApplication'.]
that is causing me confusion because i'm registering that task ID right at the begining of the app.
OP over here https://forums.expo.dev/t/expo-task-manager-with-expo-location-error/68515

express-session crashes server on setting cookie?

Basically this other post Express-session does not set cookie? where I'm following Ben Awad's Fullstack Tutorial. The cookie gets created but the server crashes and this is the error
node:internal/errors:464
ErrorCaptureStackTrace(err);
^
TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Array
at new NodeError (node:internal/errors:371:5)
at _write (node:internal/streams/writable:312:13)
at Socket.Writable.write (node:internal/streams/writable:334:10)
at RedisSocket.writeCommand (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/socket.js:57:130)
at Commander._RedisClient_tick (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/index.js:415:64)
at Commander._RedisClient_sendCommand (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/index.js:396:82)
at Commander.commandsExecutor (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/index.js:160:154)
at Commander.BaseClass.<computed> [as set] (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/commander.js:8:29)
at RedisStore.set (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/connect-redis/lib/connect-redis.js:65:21)
at Session.save (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/session/session.js:72:25)
at Session.save (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/index.js:406:15)
at ServerResponse.end (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/index.js:335:21)
at ServerResponse.send (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express/lib/response.js:221:10)
at /home/kuratar/github/milestone-4-Kuratar/server/node_modules/apollo-server-express/dist/ApolloServer.js:89:25 {
code: 'ERR_INVALID_ARG_TYPE'
}
I noticed that this specific line of code in user.ts:
req.session.userId = user.id
when it's commented out, the error doesn't occur but the cookie is not set. There isn't a set-cookie option in the response-header.
My files are pretty much the same as this other person in the post I linked.
index.ts
import "reflect-metadata";
import { MikroORM } from "#mikro-orm/core";
import { __prod__ } from "./constants";
import microConfig from "./mikro-orm.config";
import express from "express";
import { ApolloServer } from "apollo-server-express";
import { ApolloServerPluginLandingPageGraphQLPlayground } from "apollo-server-core";
import { buildSchema } from "type-graphql";
import { HelloResolver } from "./resolvers/hello";
import { PostResolver } from "./resolvers/post";
import { UserResolver } from "./resolvers/user";
import * as redis from "redis";
import session from "express-session";
import connectRedis from "connect-redis";
import { MyContext } from "./types";
// start postgresql server on wsl - sudo service postgresql start
// stop - sudo service postgresql stop
// start redis server on wsl - redis-server
// sudo /etc/init.d/redis-server restart
// stop, start
// watch ts changes - npm run watch
// run server - npm run dev
const main = async () => {
const orm = await MikroORM.init(microConfig); // initialize database
await orm.getMigrator().up(); // run migrations before anything else
const app = express();
app.set("trust proxy", 1); // trust first proxy
// this comes before applyMiddleware since use session middleware inside apollo
const RedisStore = connectRedis(session);
const redisClient = redis.createClient(); // TODO: TypeError: Cannot read properties of undefined (reading 'createClient')
redisClient.on("error", (err) => console.log("Redis Client Error", err));
await redisClient.connect();
app.use(
session({
name: "qid",
// touch - make request to redis to reset the user's session
// if user does something, it means they are active and should reset the timer of automatically logging them out
// after 24 hours for example
// disableTouch: true - keep session forever, can change this later to timed sessions
store: new RedisStore({ client: redisClient, disableTouch: true }), // tell express session using redis
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
httpOnly: true,
sameSite: "lax", // csrf
secure: __prod__, // only works in https
},
saveUninitialized: false,
secret: "askljdhfjkalshdjlf", // want to keep this secret separately
resave: true,
rolling: true,
})
);
// app.use(function (req, res, next) {
// res.header(
// "Access-Control-Allow-Origin",
// "https://studio.apollographql.com"
// );
// res.header("Access-Control-Allow-Credentials", "true");
// next();
// });
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [HelloResolver, PostResolver, UserResolver],
validate: false,
}),
// object that is accessible by resolvers, basically pass the database itself
context: ({ req, res }): MyContext => ({ em: orm.em, req, res }),
plugins: [
ApolloServerPluginLandingPageGraphQLPlayground({
settings: { "request.credentials": "include" },
}),
],
});
await apolloServer.start();
const corsOptions = {
origin: new RegExp("/*/"),
credentials: true,
};
apolloServer.applyMiddleware({ app, cors: corsOptions }); // create graphql endpoint on express
app.listen(4000, () => {
console.log("Server started on localhost:4000");
});
};
main().catch((error) => {
console.log("----------MAIN CATCHED ERROR----------");
console.error(error);
console.log("-----------------END------------------");
});
user.ts
import {
Resolver,
Arg,
Mutation,
InputType,
Field,
Ctx,
ObjectType,
} from "type-graphql";
import { User } from "../entities/User";
import { MyContext } from "../types";
import argon2 from "argon2";
// another way to implementing arguments for methods instead of #Arg()
#InputType()
class UsernamePasswordInput {
#Field()
username: string;
#Field()
password: string;
}
#ObjectType()
class FieldError {
#Field()
field: string;
#Field()
message: string;
}
#ObjectType()
class UserResponse {
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[];
#Field(() => User, { nullable: true })
user?: User;
}
#Resolver()
export class UserResolver {
#Mutation(() => UserResponse)
async register(
#Arg("options") options: UsernamePasswordInput,
#Ctx() { em }: MyContext
): Promise<UserResponse> {
if (options.username.length <= 2) {
return {
errors: [
{ field: "username", message: "length must be greater than 2" },
],
};
}
if (options.password.length <= 2) {
return {
errors: [
{ field: "password", message: "length must be greater than 2" },
],
};
}
// argon2 is a password hasher package
const hashedPassword = await argon2.hash(options.password);
const user = em.create(User, {
username: options.username,
password: hashedPassword,
});
try {
await em.persistAndFlush(user);
} catch (error) {
// duplicate username error
if (error.code === "23505") {
// || error.detail.includes("already exists")
return {
errors: [{ field: "username", message: "Username already taken" }],
};
}
}
// return user in an object since response is now a response object - UserResponse
return { user };
}
#Mutation(() => UserResponse)
async login(
#Arg("options") options: UsernamePasswordInput,
#Ctx() { em, req }: MyContext
): Promise<UserResponse> {
// argon2 is a password hasher package
const user = await em.findOne(User, {
username: options.username,
});
// can give same field error message like invalid login
if (!user) {
return {
errors: [{ field: "username", message: "That username doesn't exist" }],
};
}
const valid = await argon2.verify(user.password, options.password);
if (!valid) {
return {
errors: [{ field: "password", message: "Incorrect password" }],
};
}
// mutation {
// login(options: {username: "eric", password: "eric"}) {
// errors {
// field
// message
// }
// user {
// id
// username
// }
// }
// }
console.log(req.session)
console.log(user.id)
req.session.userId = user.id
console.log(req.session)
console.log(req.session.id)
// console.log(req.session.userId)
// return user in an object since response is now a response object - UserResponse
return { user };
}
}
types.ts
import { EntityManager, IDatabaseDriver, Connection } from "#mikro-orm/core";
import { Request, Response } from "express";
import { Session, SessionData } from "express-session";
// this is the type of orm.em from index.ts
// extracted to make code look cleaner in post.ts
export type MyContext = {
em: EntityManager<any> & EntityManager<IDatabaseDriver<Connection>>;
req: Request & {
session: Session & Partial<SessionData> & { userId?: number };
};
res: Response;
};
I've had the same error. In my situation I was able to fix it by changing the redis client to ioredis(I was using redis).
To be more specific on Bernardo, Ben also changes it to ioredis in the github repo. So you need to install ioredis and add these lines
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
and delete/comment out the old redisClient lines of code.

Vue Test Utils / Jest - How to test if class method was called within a component method

I have an interesting problem with a unit test of mine. My unit test is written to click on a button inside a component. This button calls a component method which contains an instance of a class Service (a wrapper class for axios). The only thing this component method does is call Service.requestPasswordReset(). My unit test needs to verify that Service.requestPasswordReset was called.
I know I'm mocking my Service class correctly, because this passes in my unit test:
await Service.requestPasswordReset()
expect(Service.requestPasswordReset).toHaveBeenCalled()
And I know that I'm calling the method correctly on click because this passes in my unit test:
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
I just can't get my test to register that the Service method gets called. Any ideas?
Component
<template lang="pug">
Layout
section
header( class="text-center py-4 pb-12")
h1( class="text-grey-boulder font-light mb-4") Recovery Email
p( class="text-orange-yellow") A recovery email has been sent to your email address
div( class="text-center")
div( class="mb-6")
button(
type="button"
#click.stop="resend()"
class="bg-orange-coral font-bold text-white py-3 px-8 rounded-full w-48"
) Resend Email
</template>
<script>
import Layout from '#/layouts/MyLayout'
import Service from '#/someDir/Service'
export default {
name: 'RecoveryEmailSent',
page: {
title: 'Recovery Email Sent',
},
components: {
Layout,
},
data() {
return {
errorMessage: null
}
},
computed: {
userEmail() {
const reg = this.$store.getters['registration']
return reg ? reg.email : null
},
},
methods: {
async resend() {
try {
await Service.requestPasswordReset({
email: this.userEmail,
})
} catch (error) {
this.errorMessage = error
}
},
},
}
</script>
Service.js
import client from '#/clientDir/BaseClient'
class Service {
constructor() {
this.client = client(baseUrl)
}
requestPasswordReset(request) {
return this.client.post('/account_management/request_password_reset', request)
}
}
export { Service }
export default new Service()
Service.js in __mock__
export default {
requestPasswordReset: jest.fn(request => {
return new Promise((resolve, reject) =>
resolve({
data: {
statusCode: 'Success',
},
})
)
})
}
Unit Test
jest.mock('#/someDir/Service')
import { shallowMount, mount, createLocalVue } from '#vue/test-utils'
import RecoveryEmailSent from './AccountManagement.RecoveryEmailSent'
import Service from '#/someDir/Service'
const localVue = createLocalVue()
// localVue.use(Service) // <-- Tried this, didn't work
describe('Recovery Email Sent', () => {
it('should resend recovery email', async () => {
const mockMethods = {
resend: jest.fn()
}
const email = 'testemail#test.com'
const wrapper = mount(RecoveryEmailSent, {
localVue,
computed: {
userEmail() {
return email
},
},
methods: mockMethods
})
// await Service.requestPasswordReset()
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
expect(Service.requestPasswordReset).toHaveBeenCalled()
})
})
I figured it out. Apparently, Jest's .toHaveBeenCalled() doesn't return true if the method in question was called with parameters. You MUST use .toHaveBeenCalledWith(). I don't see anything about this caveat in their docs, but it does seem to be the case.
Here is my passing test code
it('should resend email hash', async () => {
const email = 'testemail#test.com'
const wrapper = mount(AccountManagementForgottenPasswordSubmitted, {
localVue,
computed: {
userEmail() {
return email
},
},
})
await wrapper.find('button').trigger('click')
expect(Service.requestPasswordReset).toHaveBeenCalledWith({
email: email
})
})
You can use inject-loader to mock your Service
Basic idea:
const RecoveryEmailSentInjector = require('!!vue-loader?inject!./AccountManagement.RecoveryEmailSent')
import Service from '#/someDir/Service'
const mockedServices = {
'#/someDir/Service': Service
}
describe('Recovery Email Sent', () => {
it('should resend recovery email', async () => {
const RecoveryEmailSentWithMocks = RecoveryEmailSentInjector(mockedServices)
const wrapper = mount(RecoveryEmailSentWithMocks, {
...
})
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
expect(mockedServices.requestPasswordReset).toHaveBeenCalled()
})
})

How to image upload in ionic 2 using REST API

Without Ani transfer ionic 2 plugin
How to image upload in ionic 2 using REST API
public options: CameraOptions = {
enter code herequality: 100,
sourceType: this.camera.PictureSourceType.PHOTOLIBRARY,
destinationType: this.camera.DestinationType.FILE_URI,
encodingType: this.camera.EncodingType.JPEG,
mediaType: this.camera.MediaType.PICTURE,
saveToPhotoAlbum: true,
allowEdit:true,
correctOrientation:true,
targetWidth:300,
targetHeight:150
}
takePicture(options){
this.camera.getPicture(options).then((imageData) => {
console.log("imageData",imageData);
this.base64Image = imageData;
}, (err) => {
console.log(err);
});
}
add(){
var formData={
"title" : this.title,
"image" : this.base64Image,
"description" : this.description
}
if(this.formgroup.valid == true){
this.presentLoadingDefault();
this.service.add(formData).subscribe(
(data)=>{
console.log('data',data);
if(data.Code == 200){
this.presentToast(data.Message);
}else{`enter code here`
this.presentToast(data.Message);
}
this.loading.dismiss();
this.navCtrl.pop();
},
function (error){
console.log("error"+error)
});
}
}
Install the Cordova and Ionic Native plugins:
ionic cordova plugin add cordova-plugin-camera
npm install --save #ionic-native/camera
import {Camera, File, CameraOptions} from "ionic-native";
import { Myservice } from '../../providers/myservice';
export class HomePage {
captureDataUrl:any;
constructor(private Service:Myservice){}
openCamera() {
const cameraOptions: CameraOptions = {
targetHeight:150,
targetWidth:150,
allowEdit:true,
quality: 50,
destinationType: Camera.DestinationType.DATA_URL,
encodingType: Camera.EncodingType.JPEG,
mediaType: Camera.MediaType.PICTURE,
};
Camera.getPicture(cameraOptions).then((imageData) => {
this.captureDataUrl = 'data:image/jpeg;base64,' + imageData;
console.log("CaptureDataUrl:" + this.captureDataUrl);
this.upload();
}, (err) => {
});
}
this.upload(){
var profildata={
"name" :'HR PATEL',
"image" : this.base64Image
}
this.Service.getSaveImage(profildata).subscribe(
response => {
console.log("user add sucessfully");
},
err => {
console.log("err...."+err );
}
);
}
}
// call the myservice.ts
import { Http, Headers } from '#angular/http';
import {Observable} from 'rxjs/Observable';
#Injectable()
export class Myservice {
constructor(public http: Http){}
getSaveImage(profiledata): Observable<any>{
let headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded'});
return this.http.post("this api url",{data:profiledata},{headers:headers})
.map(data=> data.json());
}
}
}

Delay in retrieving data from Ionic 2 storage

Before I launches the app I will check with local storage if any user data available, If yes I will navigation to Home page else Login page.
Here I'm unable to retrieve stored data, Any inputs please...
Currently using Ionic 2 SQlite plugin.
Note: In browser it's working fine but on Android device it's not working.
app.component.ts : checking user data
loadUser() {
this.userSettings.getUser().then(user => {
this.userObj = JSON.stringify(user);
if (user) {
console.log('App : ', this.userObj);
this.nav.setRoot(HomePage,this.userObj);
} else {
console.log('App : No user data');
this.rootPage = LoginPage;
}
});
}
login.ts : Saving user data
this.userSettings.addUser(
userData.employeeCode,
userData.password,
userData.role
);
user-settings.ts : Storage file in providers
getUser() {
if (this.sql) {
return this.sql.get('user').then(value => value);
} else {
return new Promise(resolve => resolve(this.storage.get('user').then(value => value)));
}
}
addUser(employeeCode, password, role) {
let item = { employeeCode: employeeCode, password: password, role: role };
if (this.sql) {
this.sql.set('user', JSON.stringify(item)).then(data => {
this.events.publish('userObj:changed');
});
} else {
return new Promise(resolve => {
this.storage.set('user', JSON.stringify(item)).then(() => {
this.events.publish('userObj:changed');
resolve();
});
});
}
}
app.module.ts:
providers: [
{ provide: ErrorHandler, useClass: IonicErrorHandler },
AuthService,
SqlStorage,
UserSettings,
Storage
]
Thanks in advance.
Problem solved
After calling the sqlite operation in ngAfterViewInit it's working fine.
ngAfterViewInit() {
this.storage.get('user').then((user: any) => {
if (user) {
this.userCredentials = JSON.parse(user);
this.nav.setRoot(HomePage, this.userCredentials);
}
else {
this.rootPage = LoginPage;
}
});
}
[Source] (https://github.com/driftyco/ionic-conference-app/blob/master/src/pages/account/account.ts)
Cheers :)
As you point out that your code is working in Chrome, but not on your device, you might be calling sqlite before cordova's device.ready() has fired.
In app.component.ts ensure you call this.loadUser() in the following manner: (platform.ready() should already be in the constructor)
platform.ready().then(() => {
this.loadUser();
});