I am trying to save the base64 string to the gallery. When I invoke this plugin my code get's crashed. Here is the link I used to check.
code I use
let options:Base64ToGalleryOptions = { prefix: '_img',mediaScanner: true }
//after the below line my gets close automatically any idea
this.base64ToGallery.base64ToGallery(base64Image[1],options)
.then(
res => {
debugger
console.log('Saved image to gallery ', res)
},
err => {
debugger
console.log('Error saving image to gallery ', err)
});
I am not able to debug
I am not able to understand why my app closes automatically after hitting this code
Update:
After installing this particular version of the plugin
ionic cordova plugin add cordova-base64-to-gallery#2.0.2
and moving my code to platform
this.platform.ready().then(() => {
this.base64ToGallery.base64ToGallery(base64Image,options)
.then(
res => {
console.log('Saved image to gallery ', res);
this.navCtrl.pop();
},
err => { //For ios i am getting as `plugin_not_installed`
console.log('Error saving image to gallery ', err);
this.navCtrl.pop()
});
})
But this same code is not working for ios according to the doc i have installed the same version which supports ios also (2.0.2) but it looks something is missing if any please let me know
Since you are unable to debug here are three problems I ran across until I got it to work, most likely the second problem if on Android or the third problem if on iOS.
1) Error saving image to gallery cordova_not_available
Fix for this was to create a project that had cordova baked in with the command ionic start blank --cordova
2) Error saving image to gallary Error while saving image I got this error message on an Android device. I looked at their code implementation here https://github.com/Nexxa/cordova-base64-to-gallery/blob/2f531aaa0bf17b900cf6bd9704082e72f183d325/src/android/Base64ToGallery.java
Saw that they have not done anything regarding WRITE_EXTERNAL_STORAGE permissions.
My solution was to add AndroidPermissions and check for WRITE_EXTERNAL_STORAGE permissions at runtime.
hasWriteAccess: boolean = false;
constructor(private base64ToGallery: Base64ToGallery,
private androidPermissions: AndroidPermissions) {
}
ionViewWillEnter() {
this.checkPermissions();
}
checkPermissions() {
this.androidPermissions
.checkPermission(this.androidPermissions
.PERMISSION.WRITE_EXTERNAL_STORAGE)
.then((result) => {
console.log('Has permission?',result.hasPermission);
this.hasWriteAccess = result.hasPermission;
},(err) => {
this.androidPermissions
.requestPermission(this.androidPermissions
.PERMISSION.WRITE_EXTERNAL_STORAGE);
});
if (!this.hasWriteAccess) {
this.androidPermissions
.requestPermissions([this.androidPermissions
.PERMISSION.WRITE_EXTERNAL_STORAGE]);
}
}
saveImage() {
if (!this.hasWriteAccess) {
this.checkPermissions();
}
let options: Base64ToGalleryOptions = {
prefix: '_img',
mediaScanner: true
};
this.base64ToGallery
.base64ToGallery(this.base64Data, options).then(
res => console.log('Saved image to gallery:', res),
err => console.log('Error saving image to gallery:', err)
);
}
3) This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSPhotoLibraryAddUsageDescription key with a string value explaining to the user how the app uses this data.
Solution is to add NSPhotoLibraryAddUsageDescription to project_name/config.xml nested between <platform name="ios"> and </platform>
<config-file parent="NSPhotoLibraryAddUsageDescription" target="*-Info.plist">
<string>Saves images from base64 to your Photo Library</string>
</config-file>
Related
I am using https://github.com/lxieyang/chrome-extension-boilerplate-react as the basis to build a chrome extension. It all works fine, and everything does hot-reloading (popup, background, options, newtab) except for the content-script. Reloading the matching pages, does not reload the underlying .js. It takes to reload/turn-off-on the whole extension in order for the changes to go into effect.
So, in webpack.config.js i commented out 'contentScript' hoping for it to fix that, but it makes no difference.
...
chromeExtensionBoilerplate: {
notHotReload: [
//'contentScript'
],
},
...
In src/pages/Content/index.js it actually states
console.log('Must reload extension for modifications to take effect.');
When developing another extension in plain vanilla js, i dropped a hot-reload.js from https://github.com/xpl/crx-hotreload which worked perfectly. From what i understand it is the 'chrome.runtime.reload()' call that makes chrome completely reload the extension.
So my question(s) actually is:
When changing src/pages/Content/index.js, webpack does re-build the build/contentScript.bundle.js. But why doesn't manually reloading the tab/page recognize these changes, when for popup, background, etc. it does?
And if there is no way to let the above boilerplate reload the extension (i don't mind the hard reload) how would i be able to integrate the hot-reload.js (or its effect actually) into this boilerplate? That is, how do i reload the extension when build/contentScript.bundle.js is changed?
Thanks in advance!
For who is interested. I ended up placing mentioned hot-reload.js in my extension, and loading it from within the background script. That breaks webpack's hot-reloading, by reloading the entire extension on any file-change. But as long as i only work on the content script, thats fine. I can remove it once im done, or if i work on other scripts.
Use server-sent-events:
start.js
const SSEStream = require('ssestream').default;
let sseStream;
...
setupMiddlewares: (middlewares, _devServer) => {
if (!_devServer) {
throw new Error('webpack-dev-server is not defined');
}
/** 改动:/reload path SSE */
middlewares.unshift({
name: 'handle_content_change',
// `path` is optional
path: '/reload',
middleware: (req, res) => {
console.log('sse reload');
sseStream = new SSEStream(req);
sseStream.pipe(res);
res.on('close', () => {
sseStream.unpipe(res);
});
},
});
return middlewares;
}
webpack.compiler.hook
let contentOrBackgroundIsChange = false;
compiler.hooks.watchRun.tap('WatchRun', (comp) => {
if (comp.modifiedFiles) {
const changedFiles = Array.from(comp.modifiedFiles, (file) => `\n ${file}`).join('');
console.log('FILES CHANGED:', changedFiles);
if(watchRunDir.some(p => changedFiles.includes(p))) {
contentOrBackgroundIsChange = true;
}
}
});
compiler.hooks.done.tap('contentOrBackgroundChangedDone', () => {
if(contentOrBackgroundIsChange) {
contentOrBackgroundIsChange = false;
console.log('--------- 发起 chrome reload 更新 ---------');
sseStream?.writeMessage(
{
event: 'content_changed_reload',
data: {
action: 'reload extension and refresh current page'
}
},
'utf-8',
(err) => {
sseStream?.unpipe();
if (err) {
console.error(err);
}
},
);
}
});
crx background
if(process.env.NODE_ENV === 'development') {
const eventSource = new EventSource(`http://${process.env.REACT_APP__HOST__}:${process.env.REACT_APP__PORT__}/reload/`);
console.log('--- start listen ---');
eventSource.addEventListener('content_changed_reload', async ({ data }) => {
const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
const tabId = tab.id || 0;
console.log(`tabId is ${tabId}`);
await chrome.tabs.sendMessage(tabId, { type: 'window.location.reload' });
console.log('chrome extension will reload', data);
chrome.runtime.reload();
});
}
I'm using Google Drive Picker UI to select a folder and create or update spreadsheet into that folder on a schedule
Sometimes it works as expected but recently it is showing a message called "In order to select an item, please sign in". On clicking "sign in" button it shows "The feature you requested is currently unavailable. Please try again later."
Previously, this used to occur when reauthorizing immediately after revoking access but now I'm requesting with extra params like whom the folder is shared with, created date, folder name to display in front-end. It worked fine for some days but now, the issue above mentioned is occurring frequently.
createPicker(oauthToken, authCode, authUser) {
const googleViewId = window.google.picker.ViewId.FOLDERS;
const docsView = new window.google.picker.DocsView(googleViewId)
.setIncludeFolders(true)
.setMimeTypes('application/vnd.google-apps.folder')
.setSelectFolderEnabled(true);
const picker = new window.google.picker.PickerBuilder()
.addView(docsView)
.setOAuthToken(oauthToken)
.setDeveloperKey(this.props.developerKey)
.setCallback(data => {
if (data.action === window.google.picker.Action.PICKED) {
this.fetchFolderDetails(data, authCode, authUser);
}
});
if (this.props.multiSelect) {
picker.enableFeature(window.google.picker.Feature.MULTISELECT_ENABLED);
}
picker.build().setVisible(true);
}
fetchFolderDetails(data, authCode, authUser) {
window.gapi.client
.init({
apiKey: this.props.developerKey
})
.then(() =>
window.gapi.client.request({
path: 'https://www.googleapis.com/drive/v2/files/' + data.docs[0].id,
params: {
fields: 'permissions, title, createdDate, shared'
}
})
)
.then(response => {
let googleDriveData = {
folderId: data.docs[0].id,
mimeType: data.docs[0].mimeType,
authCode,
authUser,
folderName: response.result.title,
permissions: response.result.permissions,
shared: response.result.shared,
createdTime: response.result.createdDate
};
this.props.onChange(googleDriveData);
});
}
I expect to see the list of folders after authorizing.
Update
Adding a google drive scope somewhat fixed the issue but still the immediate reauthorizing issue persists.
We are getting Sentry reports from our Android customers who get errors during makeDirectoryAsync(). The error is:
Error: Directory 'file:///data/user/0/companyName/cache/ExperienceData/%2540companyName%252FappName/content/' could not be created..
The issue seem only to be related to users with Huawei phones (SKD 24-27).
The error accrue during our initial setup of the cache and this is the code.
const TEMP_DIR = FileSystem.cacheDirectory
const DIR_PATH = `${TEMP_DIR}${folderName}/`
return FileSystem.getInfoAsync(DIR_PATH).then(info => {
if (!info.exists) {
return FileSystem.makeDirectoryAsync(DIR_PATH, {
intermediates: true,
}).then(() => {
[...]
})
}
[...]
})
Expo sdk => 30.0.0
related issues:
https://forums.expo.io/t/makedirectoryasync-error-could-not-be-created/11916
https://github.com/expo/expo/issues/1980
We have not detached our app from expo.
thankful for any help.
Holger
I am trying to use the cordova social sharing plugin for sharing video on social sites. So far what I have achieved is, I have successfully captured video using following code -
var options = {
limit: 1,
duration: 15
};
$cordovaCapture.captureVideo(options).then(function (videoData) {
$scope.videoUrl = videoData[0].fullPath;
}, function (err) {
// An error occurred. Show a message to the user
//alert("video error : "+err);
});
I can successfully find the captured video files url but unfortunately I can not share them to the social media sites. I have tried both of the following methods -
$cordovaSocialSharing
.share(message, subject, file, link)
and
$cordovaSocialSharing
.shareViaTwitter(message, image, link)
Now my question is -
Is there any way to share video through this approach?
If not, please let me know if there is any possible way for this.
N.B. : I have already bothered the Google a lot.
Thanks in advance.
my problem was passing a bad filePath, so i found a solution like below :
import {CaptureError, MediaFile, MediaCapture, CaptureImageOptions, Transfer} from "ionic-native";`
declare let cordova: any;
private static options = {
message: '', // not supported on some apps (Facebook, Instagram)
subject: '', // for email
files: [''], // an array of filenames either locally or remotely
url: ''
};
videoOptions: CaptureImageOptions = {limit: 1};
videoData: any;
captureVideo() {
MediaCapture.captureVideo(this.videoOptions)
.then(
(data: MediaFile[]) => {
this.videoData = data[0];
const fileTransfer = new Transfer();
fileTransfer.download(this.videoData.fullPath, cordova.file.applicationStorageDirectory + 'fileDir/filename.mp4').then((entry) => {
this.options.message = " Your message";
this.options.subject = "Your Subject";
this.options.files = [entry.toURL()];
this.options.url = "https://www.google.com.tr/";
SocialSharing.shareWithOptions(this.options);
}, (error) => {
});
},
(err: CaptureError) => {
}
);
}
As you see above, i just copy my video file to applicationStorageDirectory
Code reference: https://github.com/aredfox/screencapturer
Problem description:
Here is an electron app with a "MainWindow" that holds a button "Start capture". Once clicked it fires an event to the main process, the main process then launches a new, seperate, "BrowserWindow" object called 'captureWindow' with it's own capture.html and capture.js associated. In capture.js, every three seconds a screenshot is made and saved to c:\temp\screencap (this is a demo app to illustrate a problem, thus I did not make this configurable and hard coded the path in for now). Every time a capture is made in the 'craptureWindow' it freezes, which I expected it to. But, the 'mainWindow' object freezes as well, which I did not expect it to do. How should I handle this, so thaty the mainWindow does not freeze when a process is being run in another "BrowserWindow" object? I assumed electron BrowserWindows (or "tabs") had a seperate thread?
EDIT 20/12/2016
Possible culprit is desktopCapturer.getSources().
ADDENDUM: Found that the issue must be inside the codeblock of getMainSource, because when I cache that "source" result it doesn't freeze the whole of electron. Thus it must be that the filter method or getting the screen itself is causing the issue of the freeze.
function getMainSource(desktopCapturer, screen, done) {
const options = {
types: ['screen'], thumbnailSize: screen.getPrimaryDisplay().workAreaSize
}
desktopCapturer.getSources(options, (err, sources) => {
if (err) return console.log('Cannot capture screen: ', err)
const isMainSource = source => source.name === 'Entire screen' || source.name === 'Screen 1'
done(sources.filter(isMainSource)[0])
})
}
The solution though is not caching the result of getMainSource (aka the "source"), as it will result in the same image data each time of course. I verified that by writing to file as png, and indeed each screenshot then was the exact same, even though enough had changed on the desktop. TODO: Possible option is to setup a video stream and save an image from the stream?
If you're wanting to capture screenshots cross platform, i'd advice using the approach below in stead of relying on the built in electron-api's. Not that they're not good, but they're not suited for taking screenshots every three seconds for example.
The solution for me was the npm-module desktop-screenshot - and a npm package called hazardous, as this was needed on Windows & asar execution.
The code I ended up implementing was this - it might be a source of inspiration/example for your problem.
/* ******************************************************************** */
/* MODULE IMPORTS */
import { remote, nativeImage } from 'electron';
import path from 'path';
import os from 'os';
import { exec } from 'child_process';
import moment from 'moment';
import screenshot from 'desktop-screenshot';
/* */
/*/********************************************************************///
/* ******************************************************************** */
/* CLASS */
export default class ScreenshotTaker {
constructor() {
this.name = "ScreenshotTaker";
}
start(cb) {
const fileName = `cap_${moment().format('YYYYMMDD_HHmmss')}.png`;
const destFolder = global.config.app('capture.screenshots');
const outputPath = path.join(destFolder, fileName);
const platform = os.platform();
if(platform === 'win32') {
this.performWindowsCapture(cb, outputPath);
}
if(platform === 'darwin') {
this.performMacOSCapture(cb, outputPath);
}
if(platform === 'linux') {
this.performLinuxCapture(cb, outputPath);
}
}
performLinuxCapture(cb, outputPath) {
// debian
exec(`import -window root "${outputPath}"`, (error, stdout, stderr) => {
if(error) {
cb(error, null, outputPath);
} else {
cb(null, stdout, outputPath);
}
});
}
performMacOSCapture(cb, outputPath) {
this.performWindowsCapture(cb, outputPath);
}
performWindowsCapture(cb, outputPath) {
require('hazardous');
screenshot(outputPath, (err, complete) => {
if(err) {
cb(err, null, outputPath);
} else {
cb(null, complete, outputPath);
}
});
}
}
/*/********************************************************************///