I'm trying to update the NativeScript plugin nativescript-local-notifications to display images in the notifications.
That's already working on Android here, but I'm having some issues when trying to implement the same thing for iOS.
I have done some changes to the method schedulePendingNotificationsNew in local-notifications.ios.ts:
private static async schedulePendingNotificationsNew(pending: ScheduleOptions[]): Promise<void> {
...
content.userInfo = userInfoDict; // Not changed
if (options.image) {
const image: ImageSource = await imageSource.fromUrl(options.image);
const imageName: string = options.image.split('/').slice(-1)[0];
const imageExtension: "png" | "jpeg" | "jpg" = <"png" | "jpeg" | "jpg">imageName.split('.')[1]
const folderDest = fileSystemModule.knownFolders.temp();
const pathDest = fileSystemModule.path.join(folderDest.path, imageName);
console.log(`Image will be saved to = ${ pathDest }`);
const saved = image.saveToFile(pathDest, imageExtension);
console.log(`Image ${ saved ? '' : 'not' } saved. `);
console.log(`Image does ${ fileSystemModule.File.exists(pathDest) ? '' : 'not' } exist. `);
if (saved || fileSystemModule.File.exists(pathDest)) {
console.log('Attaching image...');
try {
const attachment = UNNotificationAttachment
.attachmentWithIdentifierURLOptionsError('attachment', NSURL.fileURLWithPath('file://' + pathDest), null);
// .attachmentWithIdentifierURLOptionsError('attachment', NSURL.fileURLWithPath(pathDest), null);
content.attachments = NSArray.arrayWithObject<UNNotificationAttachment>(attachement);
} catch(err) {
console.log('Attachment error ; ', err);
}
console.log('Image attached!');
}
}
const repeats = options.interval !== undefined; // Not changed
...
}
You can see I have created the attachment in two different ways:
const attachment = UNNotificationAttachment
.attachmentWithIdentifierURLOptionsError('attachment',
NSURL.fileURLWithPath('file://' + pathDest), null);
And:
const attachment = UNNotificationAttachment
.attachmentWithIdentifierURLOptionsError('attachment',
NSURL.fileURLWithPath(pathDest), null);
But none of them work, in both cases I get a text-only notification and these logs:
Image will be saved to = /var/mobile/Containers/Data/Application/.../Library/Caches/1974-lancia-stratos-hf-stradale-for-sale.jpg
Image not saved.
Image does not exist.
I'm testing it on an iPhone 7 and an iPhone 8 and the image I'm trying to save is this one: https://icdn-0.motor1.com/images/mgl/7WjgW/s3/1974-lancia-stratos-hf-stradale-for-sale.jpg
I fixed it by forcing the image to be saved as png:
export class LocalNotificationsImpl extends LocalNotificationsCommon implements LocalNotificationsApi {
...
private static guid() {
// Not the best, but will it will do. See https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
return `${ s4() }${ s4() }-${ s4() }-${ s4() }-${ s4() }-${ s4() }${ s4() }${ s4() }`;
}
private static getImageName(imageURL: string = "", extension: "png" | "jpeg" | "jpg" = "png"): [string, string] {
const name: string = imageURL.split(/[\/\.]/).slice(-2, -1)[0] || LocalNotificationsImpl.guid();
return [name, `${ name }.${ extension }`];
}
...
private static async schedulePendingNotificationsNew(pending: ScheduleOptions[]): Promise<void> {
...
const imageURL: string = options.image;
if (imageURL) {
const image: ImageSource = await imageSource.fromUrl(imageURL);
const [imageName, imageNameWithExtension] = LocalNotificationsImpl.getImageName(imageURL, "png");
const path: string = fileSystemModule.path.join(
fileSystemModule.knownFolders.temp().path,
LocalNotificationsImpl.getImageName(imageURL, "png"),
);
const saved = image.saveToFile(path, "png");
if (saved || fileSystemModule.File.exists(path)) {
try {
const attachment = UNNotificationAttachment
.attachmentWithIdentifierURLOptionsError('attachment', NSURL.fileURLWithPath(path), null);
content.attachments = NSArray.arrayWithObject<UNNotificationAttachment>(attachment);
} catch(err) {}
}
}
...
}
}
If you are interested, these changes have been merged to my fork of the plugin and there's a PR open in the official one.
Related
I've got an ionic react app which fetches data from a rest API. When I bundle the ab with ionic server or on Android the app works fine.
When I run the same code on iOS I got a blank page. Some Versions before it worked on iOS as well. When I rebase my code the version 5 days ago now it doesn't work at all.
the code looks like this:
export default function PostsContainer() {
let {categoryid} = useParams<any>();
const [posts, setPosts] = useState<any[]>([]);
const [page, setPage] = useState<number>(1);
const [totPages, setTotPages] = useState<number>(1);
const [title, setTitle] = useState<string>("Recent posts");
const baseUrl = BASE_URL + "wp-json/wp/v2";
const [loadingPosts, setLoadingPosts] = useState<boolean>(false);
useEffect(() => {
async function loadPosts() {
setLoadingPosts(false)
let url = baseUrl + "/posts?status=publish&page=" + page;
if (categoryid !== undefined) {
url = url + "&categories=" + categoryid;
getCategoryName(categoryid);
}
const response = await fetch(url);
if (!response.ok) {
return;
}
const totalPages = await response.headers.get("x-wp-totalpages");
const postsTemp = await response.json();
setPosts(postsTemp);
setTotPages(Number(totalPages));
setLoadingPosts(true);
console.log(posts)
}
loadPosts();
}, [page, categoryid]);
function handleClickNextPage() {
setPage(page + 1);
}
async function getCategoryName(id: number) {
let url = baseUrl + "/categories/" + id;
const response = await fetch(url);
if (!response.ok) {
return;
}
const category = await response.json();
setTitle(category.name);
}
if (loadingPosts) {
return (
<IonPage>
<IonHeader translucent={true}>
</IonHeader>
<IonContent>
{page === 1 ? <Slider listOfPosts={posts}/> : <div/>}
<Posts
listOfPosts={posts}
totPages={totPages}
handleClickNextPage={handleClickNextPage}
pageNumber={page}
/>
</IonContent>
</IonPage>
);
} else {
return <IonLoading
isOpen={!loadingPosts}
message={ION_LOADING_DIALOG}
duration={ION_LOADING_DURATION}
/>
}
}
are there any settings belonging to the rest api? The call is to a URL with https. When I remove the useefect and only render a static page without content, it works fine.
At the End it was a CORS error on Endpoint. I had to allow cordova://localhost on Endpoint.
I am attempting to implement file storage in an S3-compatible blob store when a user selects a photo in an iOS mobile app. I am using the Just library on the mobile side for an authenticated POST request, and koa libraries on top of a nodejs server on the backend. Everything appears to be working perfectly when I watch the logs in xcode and on our server (ie: the photo's name, path, and type are exposed/provided in the POST request), but I receive an error in my server logs that the file/path does not exist when the S3 function is attempting to execute. I know this is a layered issue but any help greatly appreciated.
Swift Code (which seems to work perfectly):
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let editedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
selectPhotoImageView.image = editedImage
let imgUrl = info[UIImagePickerController.InfoKey.imageURL] as! URL
let imgName = imgUrl.lastPathComponent
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
let localPath = documentDirectory?.appending(imgName)
let data = editedImage.jpegData(compressionQuality: 0.5)! as NSData
data.write(toFile: localPath!, atomically: true)
let photoURL = URL.init(fileURLWithPath: localPath!)
let partFilename = photoURL.lastPathComponent
if let URLContent = try? Data(contentsOf: photoURL) {
let partContent = URLContent
}
let resp = SessionManager.shared.just()?.post(
"https://app.myta.io/api/1/file-upload",
data: ["path": photoURL,
"filename": "testimage"],
files: ["file": .url(photoURL, "image/jpeg")]
)
if (resp != nil && resp!.ok) {
print("Made successful request")
} else {
print("Request failed :(((")
}
}
}
Backend Code:
import { authenticateJWT, checkAuthenticated } from 'server/auth';
import { Context, DefaultState } from 'koa';
import Koa from 'koa';
import mount from 'koa-mount';
import Router from 'koa-router';
import { addUploadedFile } from 'server/external/file_upload';
import koaBody from 'koa-body';
const multer = require('#koa/multer');
const upload = multer();
const fs = require('fs');
/**
* setup router for authenticated API routes
*/
const apiRouter = new Router<DefaultState, Context>();
apiRouter.use(authenticateJWT);
apiRouter.get(
'/authenticated',
async (ctx) => (ctx.body = 'Hi, authenticated user!'),
);
/**
* upload file with koa/multer
*/
apiRouter.post('/file-upload', upload.any('file'), async (ctx) => {
console.log('ctx.request.body', ctx.request.body);
console.log('ctx.request.files', ctx.request.files); // displays properties in the server logs, but I can not set properties to ctx.request.files.filename, for example
const type = ctx.request.type;
const user = ctx.state.user;
try {
const fileName = ctx.request.body.filename;
const fileContents = ctx.request.body.path; // The S3 function below tells me this path doesn't exist, even though I see it in the logs and it is accurate
const fileType = 'image/jpeg';
console.log(`name: ${fileName}`);
console.log(`contents: ${fileContents}`);
console.log(`type: ${fileType}`);
try {
const fileId = addUploadedFile(fileName, fileContents, fileType, user);
if (fileId) {
ctx.status = 200;
ctx.body = {
status: 'success',
};
} else ctx.status = 400;
} catch {
ctx.status = 400;
}
} catch(err) {
console.log(`error ${err.message}`)
}
});
const app = new Koa();
app.use(apiRouter.routes()).use(apiRouter.allowedMethods());
export const apiApp = mount('/api/1', app);
I have tried countless iterations of the code above. A primary issue is that ctx.request.files will return data when the POST executes, but in writing the code an error of the object being "potentially undefined" pops up when I try to set variables to properties such as ctx.request.files.testimage, .file, etc...
A version the came close to success:
apiRouter.post('/file-upload', upload.single('file'), async (ctx) => {
console.log('ctx.file', ctx.file);
const type = ctx.request.type;
const user = ctx.state.user;
console.log('user: ', JSON.stringify(user, null, 2));
try {
const fileName = ctx.file.fieldname;
const fileContents = fs.createReadStream(new Uint8Array(ctx.file.buffer)); // The S3 function does not like this buffer, neither as-is or when explicitly converted to Uint8Array as shown here
const fileType = ctx.file.mimetype;
try {
const fileId = addUploadedFile(fileName, fileContents, fileType, user);
if (fileId) {
ctx.status = 200;
ctx.body = {
status: 'success',
};
} else ctx.status = 400;
} catch {
ctx.status = 400;
}
} catch(err) {
console.log(`error ${err.message}`)
}
});
Using multer's Single function worked well for accessing properties of ctx.file (as seen above), but the path was not exposed as one of the properties (hence my moving on to use multer's Any function), so I tried to used ctx.file.buffer with the S3 function to no avail.
S3 Upload:
import AWS from 'aws-sdk';
import { db, User } from 'server/db';
const fs = require('fs');
const spacesEndpoint = new AWS.Endpoint('nyc3.digitaloceanspaces.com');
const s3 = new AWS.S3({
endpoint: spacesEndpoint,
accessKeyId: process.env.FILE_UPLOAD_SPACES_KEY,
secretAccessKey: process.env.FILE_UPLOAD_SPACES_SECRET,
});
const spacesFileUploadBucket = 'myta-uploads';
export async function addUploadedFile(
fileName: string,
fileContents: string,
fileType: string,
user: User,
): Promise<string | null | undefined> {
const fileNameRegex = /^[a-zA-Z0-9-._]{1,30}$/;
if (!fileName.match(fileNameRegex)) {
throw new Error('Invalid file name');
}
const body = fs.createReadStream(fileContents);
const params = {
Bucket: spacesFileUploadBucket,
Key: fileName,
Body: body,
Type: fileType,
ACL: 'private',
};
if (!user) throw new Error('No user given');
let fileId;
await s3.putObject(params, async function (err) {
if (err) throw new Error('Could not add file to storage');
fileId = await addFileUploadedToDb(fileName, user);
});
return fileId;
}
async function addFileUploadedToDb(fileName: string, user: User) {
const file = await db.fileUpload.create({
data: {
fileName: fileName,
uploader: { connect: { id: user.id } },
},
});
return file.id;
}
I am creating excel file from user data but unfortunately its not generating file and even don't know what is the error so I can at-least try to solve that error.
Specially this issue occur in iOS platform only.
Please find below code to generate excel file:
public createXSLX(): Promise<any> {
return new Promise((resolve) => {
let sheet = XLSX.utils.json_to_sheet(this.data);
let wb = {
SheetNames: ["export"],
Sheets: {
"export": sheet
}
};
let wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
});
function s2ab(s) {
let buf = new ArrayBuffer(s.length);
let view = new Uint8Array(buf);
for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
let blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' });
resolve(blob);
});
}
This above function works very well in android but in iOS its not generation file from provided data.
How I call above function code :
onExportNew = function (toEmail) {
this.createXSLX().then((xclBlob) => {
let time = new Date().getTime();
let fileName: string = "roster_" + time + ".xlsx";
var fs = ''
if (this.platform.is('ios')){
fs = this.file.documentsDirectory;
}else{
fs = this.file.externalDataDirectory;
}
console.log("File Path:- ",fs)
this.file.writeFile(fs, fileName, xclBlob, true).then(() => {
let fp = fs + fileName;
let email = {
// to: 'lmahajan#cisco.com',
// cc: 'erika#mustermann.de',
// bcc: ['john#doe.com', 'jane#doe.com'],
to: toEmail,
attachments: [fp],
subject: 'Roster Excel File',
body: '<h1>PFA</h1>',
isHtml: true
};
this.emailComposer.open(email).then(() => {
this.showDone = true;
}).catch(() => {
let toast = this.toastCtrl.create({
message: 'Could not open email composer',
duration: 3000
});
toast.present();
});
}).catch(() => {
this.displayAlert('Error', 'error creating file at: ' + fs);
});
}).catch(() => {
console.log("Excel file creation error");
});
}
Guide me if missing anything in above code.
Thanks in advance!
I want to make a basic example Firefox add-on using js-ctype. First, I made a .dll file with a simple C code:
#include "stdio.h"
extern "C"
{
__declspec(dllexport) int add(int a, int b) { return a + b; }
}
The library file is fine. I tested it in another project.
I load it by js-ctypes code:
var {Cu , Cc, Ci} = require("chrome");
Cu.import("resource://gre/modules/ctypes.jsm", null);
Cu.import("resource://gre/modules/Services.jsm");
var self = require("sdk/self");
var prompts = Cc["#mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
var dataUrl = self.data.url("Js-CtypeLib.dll");
dataUrl = Services.io.newURI(dataUrl,null,null).QueryInterface(Ci.nsIFileURL).file.path;
var lib, add;
try{
console.log("Load library");
lib = ctypes.open("Js-CtypeLib.dll");
try{
declareFunc();
}
catch(e){
console.log("Error declare function");
}
}
catch(e){
console.log("Error load Library!");
}
function declareFunc(){
add = lib.declare("add", ctypes.default_abi, ctypes.int, ctypes.int, ctypes.int);
}
function test(){
var rs = add(4,2);
prompts.alert(null, "Result: ", rs);
lib.close();
}
exports.test = test;
and then, I call support.js file by index.js
var buttons = require('sdk/ui/button/action');
var tabs = require("sdk/tabs");
var support = require("./support.js");
var button = buttons.ActionButton({
id: "mozilla-link",
label: "Visit Mozilla",
icon: {
"16": "./images/icon-16.png",
"32": "./images/icon-32.png",
"64": "./images/icon-64.png"
},
onClick: handleClick}
);
function handleClick(state) {
support.test();
}
Finally, in cmd, I run it and get:
Component returned failure code: 0x80004002 (NS_NOINTERFACE) [nsIFileURL.file]
Full error text:
You need to mark your addon as "unpacked" in the package.json. Then you must use a file:// uri in the call to ctypes.open(file_uri_here).
To get a file uri of a file in your addon you would do this:
Components.utils.import("resource://gre/modules/Services.jsm");
var cr = Components.classes['#mozilla.org/chrome/chrome-registry;1'].getService(Components.interfaces.nsIChromeRegistry);
var chromeURI_myLib = Services.io.newURI('chrome://youraddon/content/mySubFolder/myCFunctionsForUnix.so', 'UTF-8', null);
var localFile_myLib = cr.convertChromeURL(chromeURI_myLib);
var jarPath_myLib = localFile_myLib.spec; // "jar:file:///C:/Users/Vayeate/AppData/Roaming/Mozilla/Firefox/Profiles/aecgxse.Unnamed%20Profile%201/extensions/youraddon#jetpack.xpi!/mySubFolder/myCFunctionsForUnix.so"
var filePath_myLib = localFilemyLib.path;
ctypes.open(filePath_myLib);
var {Cc, Ci, Cu} = require("chrome");
function downloadFile(links) {
try {
//new obj_URI object
var obj_URI = Cc["#mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newURI(links, null, null);
//new file object
var obj_TargetFile = Cc["#mozilla.org/file/local;1"].createInstance(Ci.;
//set file with path
obj_TargetFile.initWithPath("c:\\temp\\1.jpg");
//if file doesn't exist, create
if(!obj_TargetFile.exists()) {
obj_TargetFile.create(0x00,0644);
}
obj_TargetFile.reveal();
//new persitence object
var obj_Persist = Cc["#mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Ci.nsIWebBrowserPersist);
// with persist flags if desired
const nsIWBP = Ci.nsIWebBrowserPersist;
const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
nsIWBP.PERSIST_FLAGS_DONT_CHANGE_FILENAMES |
nsIWBP.PERSIST_FLAGS_CLEANUP_ON_FAILURE;
obj_Persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
//save file to target
obj_Persist.saveURI(obj_URI,null,null,null,null,obj_TargetFile,null);
} catch (e) {
console.error(e);
}
};
This work code for silent downloading file at firefox sdk 1.14
How i can change name of "1.jpg" to real downloading file name?
nsIWBP.PERSIST_FLAGS_DONT_CHANGE_FILENAMES
Doesn't have any results.