I have a Teams application where I need to use local storage (IndexedDB).
All works fine with the most common browsers: Chrome, Firefox but when I try to use the application with the Android app for Teams, something goes wrong: "It is necessary for the correct functioning of the app to allow access to IndexedDB".
Android Version: 10 - WebView Version: 81.0.4044.138
From my point of view is something relative to the permissions for local storage with WebView
This is my code:
if (window.indexedDB) {
var request = indexedDB.open('__mydb', 2);
request.onerror = function (event) {
alert('It is necessary for the correct functioning of the app to allow access to IndexedDB.');
};
request.onsuccess = function (event) {
mydb = event.target.result;
try {
console.log('Database opened, checking existence of table');
var objectStore = mydb.transaction([tableName], 'readwrite')
.objectStore(tableName);
console.log('Table exists. Proceeding to save data');
saveTokenDataIndexedDB(objectStore);
console.log('All done, going to app');
goToApp();
} catch (e) {
console.log(e);
}
};
}
This is the manifest file of my Teams application:
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.2/MicrosoftTeams.schema.json",
"manifestVersion": "1.0",
"packageName": "XXXXXX",
"id": "29bcd6f4-XXXXXX-4111-820b-XXXXXXXXXXX",
"version": "0.1",
"developer": {
"name": "XXXXXXXXXXXXX",
"websiteUrl": "https://products.office.com/en-us/sharepoint/collaboration",
"privacyUrl": "https://privacy.microsoft.com/en-us/privacystatement",
"termsOfUseUrl": "https://www.microsoft.com/en-us/servicesagreement"
},
"name": {
"short": "XXXXXXXXXXXX"
},
"description": {
"short": "XXXXXXXXXXXXX",
"full": "XXXXXXXXXXXXXXXX"
},
"icons": {
"outline": "XXXXXXXXXXXX_outline.png",
"color": "XXXXXXXXXXXXX_color.png"
},
"accentColor": "#004578",
"configurableTabs": [
{
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=XXXXX-eadc-4020-XXXX-edea2c24753b%26forceLocale={locale}",
"canUpdateConfiguration": true,
"scopes": [
"team"
]
}
],
"staticTabs": [
{
"entityId": "XXXXX",
"name": "XXXXXXXXX",
"contentUrl": "https://{teamSiteDomain}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest=/_layouts/15/teamshostedapp.aspx%3Fteams%26personal%26componentId=XXXXXXXXX-eadc-4020-b4e2-XXXXXXXb%26forceLocale={locale}",
"scopes": [
"personal"
]
}
],
"validDomains": [
"*.login.microsoftonline.com",
"*.sharepoint.com",
"*.sharepoint-df.com",
"spoppe-a.akamaihd.net",
"spoprod-a.akamaihd.net",
"resourceseng.blob.core.windows.net",
"msft.spoppe.com"
],
"webApplicationInfo": {
"resource": "https://{teamSiteDomain}",
"id": "XXXXXXXX-eadc-4020-b4e2-XXXXXXXXXX"
}
}
I'm stuck with this, if somebody can give me a clue I will be grateful.
Regards
Related
I have made an app for Teams that I want to use to display an adaptive card to the user when they pick an item from the list of search results. In order for this to happen, I need to trigger some code after the user selects a result. This works as expected from the Teams client, as well as in the browser, but from native mobile Teams app, the code is not triggered when selecting an item from the list of results.
const preview = CardFactory.heroCard( obj.package.name );
preview.content.tap = { type: 'invoke', value: { description: obj.package.description } };
The following pictures show the app working in a browser on the computer:
The list of results from the browser on PC
The expected adaptive card showing correctly on browser
And this is how it looks from the mobile perspective:
The list of results from mobile app
The result of selecting the same item from the list
The code used to display this has not been modified, except providing a bot to host it, and was found from Microsoft's bot samples on GitHub:
https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/typescript_nodejs/50.teams-messaging-extensions-search
The code in question looks as follows:
export class TeamsMessagingExtensionsSearchBot extends TeamsActivityHandler {
public async handleTeamsMessagingExtensionQuery( context: TurnContext, query: any ): Promise<any> {
const searchQuery = query.parameters[ 0 ].value;
const response = await axios.get( `http://registry.npmjs.com/-/v1/search?${ querystring.stringify( { text: searchQuery, size: 8 } ) }` );
const attachments = [];
response.data.objects.forEach( ( obj: any ) => {
const heroCard = CardFactory.heroCard( obj.package.name );
const preview = CardFactory.heroCard( obj.package.name );
preview.content.tap = { type: 'invoke', value: { description: obj.package.description } };
const attachment = { ...heroCard, preview };
attachments.push( attachment );
} );
return {
composeExtension: {
attachmentLayout: 'list',
attachments,
type: 'result'
}
};
}
public async handleTeamsMessagingExtensionSelectItem( context: TurnContext, obj: any ): Promise<any> {
return {
composeExtension: {
attachmentLayout: 'list',
attachments: [ CardFactory.thumbnailCard( obj.description ) ],
type: 'result'
}
};
}
}
Is this expected?
Thanks
Edit: Adding the manifest JSON used here:
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
"manifestVersion": "1.5",
"version": "1.0.0",
"id": "9211fa66-f930-414d-861a-40f18f7f1490",
"packageName": "com.teams.sample.teamsmessagingextensionssearch",
"developer": {
"name": "teamsStartNewThreadInChannel",
"websiteUrl": "https://www.microsoft.com",
"privacyUrl": "https://www.teams.com/privacy",
"termsOfUseUrl": "https://www.teams.com/termsofuser"
},
"icons": {
"outline": "icon-outline.png",
"color": "icon-color.png"
},
"name": {
"short": "Search Messaging Extension",
"full": "Microsoft Teams Search Based Messaging Extension"
},
"description": {
"short": "Sample demonstrating a Search Based Messaging Extension",
"full": "Sample Search Messaging Extension built with the Bot Builder SDK"
},
"accentColor": "#FFFFFF",
"bots": [
{
"botId": "9211fa66-f930-414d-861a-40f18f7f1490",
"scopes": [
"personal",
"groupchat",
"team"
],
"supportsFiles": false,
"isNotificationOnly": false
}
],
"composeExtensions": [
{
"botId": "9211fa66-f930-414d-861a-40f18f7f1490",
"canUpdateConfiguration": true,
"commands": [
{
"id": "searchQuery",
"context": [
"compose",
"commandBox"
],
"description": "Test command to run query",
"title": "Search",
"type": "query",
"parameters": [
{
"name": "searchQuery",
"title": "Search Query",
"description": "Your search query",
"inputType": "text"
}
]
}
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": []
}
We are able to re-pro the issue at our end. Raised a bug.We are tracking the bug internally, we don't have ETA to share when it will be fixed. Will update once it is fixed.
Problem
Hi, I recently migrated an ionic cordova project to ionic capacitor following this guide.
I got everything working except for the Watermarkjs library, which I use to watermark pictures taken with the camera of an Android device.
The library used to work fine before the migration, but now everytime I use the functions defined in that library I get this log in Logcat:
Capacitor: Handling local request: http://localhost/9j/4AAQSkZJRgABAQAAAQABAAD
It seems the function is not being called properly.
Code
async takePicture(fieldId){
const image = await Camera.getPhoto({
quality:20,
allowEditing:false,
resultType: CameraResultType.Base64,
source: CameraSource.Prompt
});
let finalImage = await this.addTextWatermark(image.base64String); //problem begins here
console.log("Image with watermark, " finalImage); // this is never printed out in logcat
}
// Function that adds a watermark
// reference: http://brianium.github.io/watermarkjs/text.html
addTextWatermark(base64String){
let result = await watermark([base64String])
.dataUrl(watermark.text.lowerLeft( 'Watermark text', '48px Josefin Slab', '#ffffff', 0.9) )
.then( image => {
return image;
}).catch(error => {
return "error";
})
return result;
}
What I've tried...
Adding watermarkjs script in angular.json scripts section and running npx cap copy, as suggested in this StackOverflow question
angular.json (excerpt)
{
"$schema": "./node_modules/#angular/cli/lib/config/schema.json",
"version": 1,
"defaultProject": "app",
"newProjectRoot": "projects",
"projects": {
"app": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "#angular-devkit/build-angular:browser",
"options": {
"outputPath": "www",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "src/assets",
"output": "assets"
},
{
"glob": "**/*.svg",
"input": "node_modules/ionicons/dist/ionicons/svg",
"output": "./svg"
}
],
"styles": [
{
"input": "src/theme/variables.scss"
},
{
"input": "src/global.scss"
}
],
"scripts": [
"./node_modules/watermarkjs/dist/watermark.js" <----- Added script here
]
} ...
Using different input types for the watermark() function. (file, blob, etc.)
I think the problem is related to not importing the functions properly.
If the library is incompatible with ionic capacitor...
Does anyone know a workaround for this problem?
I am integrating Paypal checkout in an Android App using REST APIs provided by Paypal and my country is India so I am following this guide from PayPal.
How I did as per docs:
Get access-token (/v1/oauth2/token) for further api calls.
Use the Create Order API to create a payment (v2/checkout/orders)and in the response we will get approval url at where you need to redirect user to make the payment.
Now my question is how do I know if payment transaction was successful or not in mobile app because I am using WebView in my app to load approval url.
Order is created like this and I load href inside webview:
{
"id": "1KK44573EX7352015",
"status": "CREATED",
"links": [
{
"href": "https://www.sandbox.paypal.com/checkoutnowtoken=1KK44573EX7352015",
"rel": "approve",
"method": "GET"
}
]
}
I did this way:
As soon as Payment is successfully completed by customer the return_url gets called with query parameters : PayerID & token(orderID). At that time we can update user's payment status in our database (Amount is not deducted yet still because order is yet not approved or captured).
After that we can capture our order (Make sure invoice-id is not duplicate) otherwise status will be not completed.
If order is not approved on the time of capture we get this kind of error:
{
"name": "UNPROCESSABLE_ENTITY",
"details": [
{
"issue": "ORDER_NOT_APPROVED",
"description": "Payer has not yet approved the Order for payment. Please redirect the payer to the 'rel':'approve' url returned as part of the HATEOAS links within the Create Order call or provide a valid payment_source in the request."
}
],
"message": "The requested action could not be performed, semantically incorrect, or failed business validation.",
"debug_id": "47af43e..",
"links": [
{
"href": "https://developer.paypal.com/docs/api/orders/v2/#error-ORDER_NOT_APPROVED",
"rel": "information_link",
"method": "GET"
}
]
}
If there is duplicate invoice-id you will see error at the time of capture:
{
"name": "UNPROCESSABLE_ENTITY",
"details": [
{
"issue": "DUPLICATE_INVOICE_ID",
"description": "Duplicate Invoice ID detected. To avoid a potential duplicate transaction your account setting requires that Invoice Id be unique for each transaction."
}
],
"message": "The requested action could not be performed, semantically incorrect, or failed business validation.",
"debug_id": "86e0cc7f....",
"links": [
{
"href": "https://developer.paypal.com/docs/api/orders/v2/#error-DUPLICATE_INVOICE_ID",
"rel": "information_link",
"method": "GET"
}
]
}
If there is currency based issue:
{
"name": "UNPROCESSABLE_ENTITY",
"details": [
{
"location": "body",
"issue": "CURRENCY_NOT_SUPPORTED",
"description": "Currency code is not currently supported. Please refer https://developer.paypal.com/docs/integration/direct/rest/currency-codes/ for list of supported currency codes."
}
],
"message": "The requested action could not be performed, semantically incorrect, or failed business validation.",
"debug_id": "d666b5e5eb0c0",
"links": [
{
"href": "https://developer.paypal.com/docs/api/orders/v2/#error-CURRENCY_NOT_SUPPORTED",
"rel": "information_link",
"method": "GET"
}
]
}
If your order is successfully captured with status as COMPLETED:
{
"id": "8G0042477K865063U",
"status": "COMPLETED",
"purchase_units": [
{
"reference_id": "default",
"shipping": {
"name": {
"full_name": "John Doe"
},
"address": {
"address_line_1": "10, east street",
"address_line_2": "first building",
"admin_area_2": "Mumbai",
"admin_area_1": "Maharashtra",
"postal_code": "400029",
"country_code": "NZ"
}
},
"payments": {
"captures": [
{
"id": "4K670967VH2547504",
"status": "PENDING",
"status_details": {
"reason": "RECEIVING_PREFERENCE_MANDATES_MANUAL_ACTION"
},
"amount": {
"currency_code": "NZD",
"value": "170.00"
},
"final_capture": true,
"seller_protection": {
"status": "ELIGIBLE",
"dispute_categories": [
"ITEM_NOT_RECEIVED",
"UNAUTHORIZED_TRANSACTION"
]
},
"invoice_id": "INV-1234567888",
"links": [
{
"href": "https://api.sandbox.paypal.com/v2/payments/captures/4K670967VH2547504",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v2/payments/captures/4K670967VH2547504/refund",
"rel": "refund",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/8G0042477K865063U",
"rel": "up",
"method": "GET"
}
],
"create_time": "2020-10-31T13:35:58Z",
"update_time": "2020-10-31T13:35:58Z"
}
]
}
}
],
"payer": {
"name": {
"given_name": "Sumit",
"surname": "Shukla"
},
"email_address": "testg32#gmail.com",
"payer_id": "VW87TYSM2GMZ4",
"address": {
"address_line_1": "10, east street",
"admin_area_2": "Mumbai",
"admin_area_1": "Maharashtra",
"postal_code": "400029",
"country_code": "NZ"
}
},
"links": [
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/8G0042477K865063U",
"rel": "self",
"method": "GET"
}
]
}
After that you can redirect user to thank you page and update mobile app screen based on database values.
I have a class which is part of a school and this class has teachers and students, all of them has name and maybe has phone number , I want to get the full data for the classes
but firstly, could you advice me, what is the best for performance and maintaining from the following Dbs :
1st one
"schools":{
"school1":{
"class1":{
"name":"SC1",
"teachers":[{
"name":"T1"
}, {
"name":"T2"
}],
"students":[
{"name":"S1"},
{"name":"S2"}
]
}
}
.
.
.
.
.
.
.
}
and the 2nd
"school":{
"school1":{
"name":"SC1"
},
"school2":{
"name":"SC2"
}
},
"classes": {
"class1": {
"name": "C1"
},
"class2": {
"name": "C2"
}
},
"students": {
"student1": {
"name": "S1",
"phone":"123456789"
},
"student2": {
"name": "S2",
"phone":"123456789"
},
"student3": {
"name": "S3",
"phone":"123456789"
},
"student4": {
"name": "S4",
"phone":"123456789"
}
},
"teachers": {
"student1": {
"name": "T1",
"phone":"123456789"
},
"student2": {
"name": "T2",
"phone":"123456789"
},
"student3": {
"name": "T3",
"phone":"123456789"
},
"student4": {
"name": "T4",
"phone":"123456789"
}
},
"classes_enrollments": {
"class1": {
"teacher1": true,
"teacher3": true,
"student1": true,
"student2": true
},
"class2": {
"teacher2": true,
"teacher4": true,
"student3": true,
"student4": true
},
"class3": {
"teacher1": true,
"teacher2": true,
"student3": true,
"student4": true,
"student1": true,
"student2": true
}
},
"student_friends": {
"student1": {
"student2": true
},
"students2": {
"student1": true,
"student3": true
},
"students3": {
"student2": true
}
},
"teacher_friends": {
"teacher1": {
"teacher2": true
},
"teacher2": {
"teacher1": true,
"teacher3": true
},
"teacher3": {
"teacher2": true
}
}
and for the 2nd way how to get the full data for the class1: in which school and it's name and count of teachers and students and their names and phones
Thank you
I would mix those two.
For code simplicity and reading performance of individual class details, the 2nd scheme would indeed be messy. The 1st scheme would be better, but with some improvements.
Keep the teachers and students paths at root, just like in the 2nd scheme.
Add teacher_enrollments and student_enrollments path at root, to save the ids of the classes that each teacher/student is associated with.
Don't save class teachers and students as arrays inside classes, but use maps instead, similar to what you're saving in the root teachers and students path.
That way, when you edit a teacher from the root path, you can also get a list of all their associated classes (the ids) from the enrollments path, and do a multi-path update for these classes, to update the teacher/student details in each associated class.
If you have lots of data, you might want to maintain a separate path for class summaries, so that you can easily show a list of classes, without having to download the data for all included teachers and students (which would be present multiple times in all these classes).
When you delete a class, you would also want to do a multi-path update to delete all associated enrollments. If the total number of students and teachers is not too big, you can just delete the enrollments for ALL teacheres/students. If you have lots of teachers/students, you could keep your classes_enrollments path (but with intermediate teachers and students before the ids), so that you can make an update with only the required teacher/student ids. (it's actually a lot simpler. You already have the teacher/student IDs in the class info)
// How to delete a class in JavaScript.
// For Java, use updateChildren() instead of update(),
// and supply it with a HashMap instead of a plain object.
const classToDelete = { id: 'class1', teachers: ..., students: ..., school: ... };
const updateObject = {
['classes/'+classToDelete.id]: null },
['schools/'+classToDelete.school.id+'/classes/'+classToDelete.id]: null },
};
Object.keys(classToDelete.teachers).forEach(teacherId => {
updateObject['teachers/'+teacherId +'/classes/'+classToDelete.id] = null;
});
Object.keys(classToDelete.students).forEach(studentId=> {
updateObject['students/'+studentId+'/classes/'+classToDelete.id] = null;
});
dbRef.update(updateObject);
Example database structure (slightly different than instructed, but using the same concepts):
"schools": {
"school1": {
"id": "school1",
"name": "The best school",
"classes": {
"class1": {
"id": "class1",
"name": "The best class"
}
}
}
},
"classes": {
"class1": {
"id": "class1",
"name": "The best class",
"teachers": {
"teacher1": {
"id": "teacher1",
"name": "The best teacher",
"phone": "123"
}
},
"students": {
"student1": {
"id": "student1",
"name": "The best student",
"phone": "456"
}
},
"school": {
"id": "school1",
"name": "The best school"
}
}
},
"teachers": {
"teacher1": {
"id": "teacher1",
"name": "The best teacher",
"phone": "123",
"classes": {
"class1": {
"name": "The best class",
"school": {
"id": "school1",
"name": "The best school"
}
}
}
}
},
"students": {
"student1": {
"id": "student1",
"name": "The best student",
"phone": "456",
"classes": {
"class1": {
"name": "The best class",
"school": {
"id": "school1",
"name": "The best school"
}
}
}
}
}
Good luck!
My previous server is lost, I am developing an Android app with backend on a ubuntu server on AWS, what I am trying to do now is I need to recover the backend loopback based on the json files of the models. For example I am having this,
{
"name": "BikeStop",
"plural": "bikeStops",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"geo": {
"type": "geopoint",
"required": true
},
"description": {
"type": "string",
"required": false
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
is it possible that I can just import this to a new lb folder, or do i have to call lb model and manually type in and set up? If there is a way to do it, how??
Copy your model in a JSON file as
bikestop.json
{
"name": "BikeStop",
"plural": "bikeStops",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"geo": {
"type": "geopoint",
"required": true
},
"description": {
"type": "string",
"required": false
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
Now create a javascript file for this model as
bikestop.js
'use strict';
module.exports = function(Bikestop) {
};
PS : Model name should has only the first letter uppercase.
Finally define this model in model-config.json
"BikeStop": {
"dataSource": "YourDataSourceName",
"public": true
}