diff --git a/apps/pwabuilder-vscode/src/library/package-utils.ts b/apps/pwabuilder-vscode/src/library/package-utils.ts index bfdc54205..985f8c74e 100644 --- a/apps/pwabuilder-vscode/src/library/package-utils.ts +++ b/apps/pwabuilder-vscode/src/library/package-utils.ts @@ -1,13 +1,13 @@ import { writeFile } from "fs/promises"; const fetch = require('node-fetch'); -import { Headers, Response } from "node-fetch"; +import { Response } from "node-fetch"; import { Manifest, MsixInfo } from "../interfaces"; import * as vscode from "vscode"; import { AndroidPackageOptions } from "../android-interfaces"; import { URL } from "url"; -import { trackEvent, trackException } from "../services/usage-analytics"; +import { getSessionID, standard_headers, trackEvent, trackException } from "../services/usage-analytics"; import { getURL } from "../services/web-publish"; export const WindowsDocsURL = @@ -72,12 +72,13 @@ export async function packageForWindows(options: any) { let response: Response | undefined; try { + response = await fetch( "https://pwabuilder-winserver.centralus.cloudapp.azure.com/msix/generatezip", { method: "POST", body: JSON.stringify(options), - headers: new Headers({ "content-type": "application/json" }), + headers: standard_headers, } ); } catch (err: any) { @@ -134,7 +135,7 @@ export async function buildAndroidPackage(options: AndroidPackageOptions) { response = await fetch(generateAppUrl, { method: "POST", body: JSON.stringify(options), - headers: new Headers({ "content-type": "application/json" }), + headers: standard_headers, }); } catch (err: any) { vscode.window.showErrorMessage( @@ -159,7 +160,7 @@ export async function buildIOSPackage(options: IOSAppPackageOptions) { response = await fetch(generateAppUrl, { method: "POST", body: JSON.stringify(options), - headers: new Headers({ "content-type": "application/json" }), + headers: standard_headers, }); } catch (err: any) { vscode.window.showErrorMessage( diff --git a/apps/pwabuilder-vscode/src/services/manifest/assets-service.ts b/apps/pwabuilder-vscode/src/services/manifest/assets-service.ts index 6d8640385..dbab7fc5b 100644 --- a/apps/pwabuilder-vscode/src/services/manifest/assets-service.ts +++ b/apps/pwabuilder-vscode/src/services/manifest/assets-service.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; const fetch = require('node-fetch'); import { writeFile } from 'fs/promises'; import { Manifest } from '../../interfaces'; -import { trackEvent, trackException } from "../usage-analytics"; +import { standard_headers, trackEvent, trackException } from "../usage-analytics"; import { findManifest } from './manifest-service'; const pwaAssetGenerator = require('pwa-asset-generator'); @@ -40,9 +40,7 @@ export async function generateScreenshots(skipPrompts?: boolean) { try { const response = await fetch(url, { method: 'POST', - headers: { - 'content-type': 'application/json', - }, + headers: standard_headers, body: JSON.stringify({ url: [urlToScreenshot] }), diff --git a/apps/pwabuilder-vscode/src/services/usage-analytics.ts b/apps/pwabuilder-vscode/src/services/usage-analytics.ts index d7775c34d..c01a1419a 100644 --- a/apps/pwabuilder-vscode/src/services/usage-analytics.ts +++ b/apps/pwabuilder-vscode/src/services/usage-analytics.ts @@ -1,6 +1,18 @@ import { setup, defaultClient } from 'applicationinsights'; import { getFlag } from '../flags'; +import * as vscode from 'vscode'; +import { Headers } from 'node-fetch'; + +const sessionID = getSessionID(); +export const standard_headers = new Headers( + { + "content-type": "application/json", + "Platform-Identifier": "PWAStudio", + "Correlation-Id": sessionID, + } +) + export function initAnalytics() { try { // check flag first @@ -22,13 +34,20 @@ export function initAnalytics() { } } +export function getSessionID() { + return vscode.env.sessionId; +} + // function to trackEvent export function trackEvent(name: string, properties: any) { try { if (getFlag("analytics") === true) { + // add session id to properties + properties.sessionId = getSessionID(); + defaultClient.trackEvent({ - name, + name, properties }); } @@ -44,7 +63,10 @@ export function trackException(err: Error) { try { if (getFlag("analytics") === true) { defaultClient.trackException({ - exception: err + exception: err, + properties: { + sessionId: getSessionID() + } }); } } diff --git a/libraries/manifest-validation/src/validations.ts b/libraries/manifest-validation/src/validations.ts index bf54bbde7..bae5670cc 100644 --- a/libraries/manifest-validation/src/validations.ts +++ b/libraries/manifest-validation/src/validations.ts @@ -16,6 +16,29 @@ export const maniTests: Array = [ return value && typeof value === "string" && value.length > 0; } }, + { + infoString: "The handle_links field specifies how links to your app are opened, either in your app itself or in the users browser", + displayString: "Manifest has handle_links field", + category: "recommended", + member: "handle_links", + defaultValue: "auto", + docsLink: "", + errorString: "handle_links is recommended and should be either auto, preferred or not-proferred", + quickFix: true, + test: (value: string) => { + if (value && typeof value === "string") { + if (value === "auto" || "preferred" || "not-preferred") { + return true; + } + else { + return false; + } + } + else { + return false; + } + } + }, { infoString: "share_target enables your app to get shared content from other apps", displayString: "Manifest has share_target field", @@ -28,7 +51,7 @@ export const maniTests: Array = [ "title": "title", "text": "text", "url": "url" - } + } }), docsLink: "https://web.dev/web-share-target/", errorString: "share_target must be an object", @@ -89,7 +112,7 @@ export const maniTests: Array = [ quickFix: true, test: (value: any[]) => { const isArray = value && Array.isArray(value) && value.length > 0 ? true : false; - + if (isArray) { const anyIcon = value.find(icon => icon.purpose === "any"); @@ -124,7 +147,7 @@ export const maniTests: Array = [ quickFix: false, test: (value: any[]) => { const isArray = value && Array.isArray(value) && value.length > 0 ? true : false; - + if (isArray) { const anyIcon = value.find(icon => isAtLeast(icon.sizes, 512, 512) && (icon.type === 'image/png' || icon.src.endsWith(".png"))); @@ -159,7 +182,7 @@ export const maniTests: Array = [ quickFix: true, test: (value: any[]) => { const isArray = value && Array.isArray(value) && value.length > 0 ? true : false; - + if (isArray) { const wrongIcon = value.find(icon => icon.purpose === "any maskable"); @@ -210,8 +233,8 @@ export const maniTests: Array = [ errorString: "short_name is required and must be a string with a length >= 3", quickFix: true, test: (value: string) => { - const existsAndLength = value && value.length >= 3; - return existsAndLength; + const existsAndLength = value && value.length >= 3; + return existsAndLength; }, }, { @@ -479,7 +502,7 @@ export const maniTests: Array = [ "https://docs.pwabuilder.com/#/builder/manifest?id=prefer_related_applications-boolean", quickFix: false, // @ Justin Willis, I added this but left it false because idk how to do quick fixes lol. test: (value: any) => { - return typeof(value) === "boolean" + return typeof (value) === "boolean" }, errorString: "prefer_related_applications should be set to a boolean value", }, @@ -495,12 +518,12 @@ export const maniTests: Array = [ quickFix: true, test: (value: any[]) => { let isGood; - if(value){ - containsStandardCategory(value) && Array.isArray(value) - ? - isGood = true - : - isGood = false; + if (value) { + containsStandardCategory(value) && Array.isArray(value) + ? + isGood = true + : + isGood = false; } return isGood @@ -518,7 +541,7 @@ export const maniTests: Array = [ errorString: "lang should be set to a valid language code", quickFix: true, test: (value: string) => - value && typeof value === "string" && value.length > 0 && isValidLanguageCode(value) + value && typeof value === "string" && value.length > 0 && isValidLanguageCode(value) }, { member: "dir", @@ -531,7 +554,7 @@ export const maniTests: Array = [ "https://docs.pwabuilder.com/#/builder/manifest?id=dir-string", quickFix: true, test: (value: string) => - value && typeof value === "string" && value.length > 0 && (value === "ltr" || value === "rtl" || value === "auto") + value && typeof value === "string" && value.length > 0 && (value === "ltr" || value === "rtl" || value === "auto") }, { member: "description", @@ -654,88 +677,88 @@ export const maniTests: Array = [ export async function loopThroughKeys(manifest: Manifest): Promise> { return new Promise((resolve) => { let data: Array = []; - + const keys = Object.keys(manifest); - + keys.forEach((key) => { maniTests.forEach(async (test) => { if (test.member === key && test.test) { const testResult = await test.test(manifest[key]); - - - if(testResult){ - test.valid = true; - data.push(test); + + + if (testResult) { + test.valid = true; + data.push(test); } else { - test.valid = false; - data.push(test); + test.valid = false; + data.push(test); } } }) }) - + resolve(data); }) - } - - export async function loopThroughRequiredKeys(manifest: Manifest): Promise> { +} + +export async function loopThroughRequiredKeys(manifest: Manifest): Promise> { return new Promise((resolve) => { - let data: Array = []; - - const keys = Object.keys(manifest); - - keys.forEach((key) => { - maniTests.forEach(async (test) => { - if (test.category === "required") { - if (test.member === key && test.test) { - const testResult = await test.test(manifest[key]); - - if (testResult === false) { - test.valid = false; - data.push(test); - } - else { - test.valid = true; - data.push(test); - } - } - } + let data: Array = []; + + const keys = Object.keys(manifest); + + keys.forEach((key) => { + maniTests.forEach(async (test) => { + if (test.category === "required") { + if (test.member === key && test.test) { + const testResult = await test.test(manifest[key]); + + if (testResult === false) { + test.valid = false; + data.push(test); + } + else { + test.valid = true; + data.push(test); + } + } + } + }) }) - }) - - resolve(data); + + resolve(data); }) - } - - export async function findSingleField(field: string, value: any): Promise { +} + +export async function findSingleField(field: string, value: any): Promise { return new Promise(async (resolve) => { - - // For && operations, true is the base. - let singleField = true; - let failedTests: string[] | undefined = []; - - maniTests.forEach((test) => { - if (test.member === field && test.test) { - - const testResult = test.test(value); - - if(!testResult){ - failedTests!.push(test.errorString!); - } - - // If the test passes true && true = true. - // If the test fails true && false = false - // If a field has MULTIPLE tests, they will stack - // ie: true (base) && true (test 1) && false (ie test 2 fails). - singleField = singleField && testResult; + + // For && operations, true is the base. + let singleField = true; + let failedTests: string[] | undefined = []; + + maniTests.forEach((test) => { + if (test.member === field && test.test) { + + const testResult = test.test(value); + + if (!testResult) { + failedTests!.push(test.errorString!); + } + + // If the test passes true && true = true. + // If the test fails true && false = false + // If a field has MULTIPLE tests, they will stack + // ie: true (base) && true (test 1) && false (ie test 2 fails). + singleField = singleField && testResult; + } + }); + + if (singleField) { + resolve({ "valid": singleField }) } - }); - - if(singleField){ - resolve({"valid": singleField}) - } - - resolve({"valid": singleField, "errors": failedTests}); + + resolve({ "valid": singleField, "errors": failedTests }); }) } diff --git a/libraries/manifest-validation/test/validation-tests.mjs b/libraries/manifest-validation/test/validation-tests.mjs index 9966d16d4..fdba2687c 100644 --- a/libraries/manifest-validation/test/validation-tests.mjs +++ b/libraries/manifest-validation/test/validation-tests.mjs @@ -18,6 +18,7 @@ const test_manifest = { "background_color": "#FFFFFF", "related_applications": [], "prefer_related_applications": false, + "handle_links": "preferred", "screenshots": [ { "src": "assets/screen.png" @@ -105,7 +106,7 @@ describe('Manifest Validation with hardcoded test manifest', async () => { it('returns correct number of tests', async () => { const data = await maniLib.validateManifest(test_manifest); - assert.equal(data.length, 24); + assert.equal(data.length, 25); }); /* @@ -185,6 +186,18 @@ describe('Manifest Validation with hardcoded test manifest', async () => { assert.equal(validity.valid, true); }); + it("handle_links is valid, should pass", async () => { + const validity = await maniLib.validateSingleField("handle_links", "auto"); + + assert.equal(validity.valid, true); + }) + + if ("handle_links is invalid, should fail", async () => { + const validity = await maniLib.validateSingleField("handle_links", "justin"); + + assert.equal(validity.valid, false); + }) + /* * test validateRequiredFields method */