├── .gitignore ├── .vscode └── launch.json ├── README.md ├── SECURITY.md ├── build.yml ├── dist ├── 9p1j8s7ccwwt_us_en-us.html ├── badge │ └── ms-store-badge.bundled.js ├── iframe.html ├── images │ ├── af dark.svg │ ├── af light.svg │ ├── am dark.svg │ ├── am light.svg │ ├── ar dark.svg │ ├── ar light.svg │ ├── as dark.svg │ ├── as light.svg │ ├── az dark.svg │ ├── az light.svg │ ├── bg dark.svg │ ├── bg light.svg │ ├── bn dark.svg │ ├── bn light.svg │ ├── bs dark.svg │ ├── bs light.svg │ ├── ca dark.svg │ ├── ca light.svg │ ├── cs dark.svg │ ├── cs light.svg │ ├── cy dark.svg │ ├── cy light.svg │ ├── da dark.svg │ ├── da light.svg │ ├── de dark.svg │ ├── de light.svg │ ├── el dark.svg │ ├── el light.svg │ ├── en-us dark.svg │ ├── en-us light.svg │ ├── es dark.svg │ ├── es light.svg │ ├── et dark.svg │ ├── et light.svg │ ├── fa dark.svg │ ├── fa light.svg │ ├── fi dark.svg │ ├── fi light.svg │ ├── fil dark.svg │ ├── fil light.svg │ ├── fr dark.svg │ ├── fr light.svg │ ├── ga dark.svg │ ├── ga light.svg │ ├── gd dark.svg │ ├── gd light.svg │ ├── gl dark.svg │ ├── gl light.svg │ ├── gu dark.svg │ ├── gu light.svg │ ├── he dark.svg │ ├── he light.svg │ ├── hi dark.svg │ ├── hi light.svg │ ├── hr dark.svg │ ├── hr light.svg │ ├── hu dark.svg │ ├── hu light.svg │ ├── hy dark.svg │ ├── hy light.svg │ ├── id dark.svg │ ├── id light.svg │ ├── is dark.svg │ ├── is light.svg │ ├── it dark.svg │ ├── it light.svg │ ├── ja dark.svg │ ├── ja light.svg │ ├── ka dark.svg │ ├── ka light.svg │ ├── kk dark.svg │ ├── kk light.svg │ ├── km dark.svg │ ├── km light.svg │ ├── kn dark.svg │ ├── kn light.svg │ ├── ko dark.svg │ ├── ko light.svg │ ├── kok dark.svg │ ├── kok light.svg │ ├── lb dark.svg │ ├── lb light.svg │ ├── lo dark.svg │ ├── lo light.svg │ ├── lt dark.svg │ ├── lt light.svg │ ├── lv dark.svg │ ├── lv light.svg │ ├── mi dark.svg │ ├── mi light.svg │ ├── mk dark.svg │ ├── mk light.svg │ ├── ml dark.svg │ ├── ml light.svg │ ├── mr dark.svg │ ├── mr light.svg │ ├── ms dark.svg │ ├── ms light.svg │ ├── mt dark.svg │ ├── mt light.svg │ ├── ne dark.svg │ ├── ne light.svg │ ├── nl dark.svg │ ├── nl light.svg │ ├── nn dark.svg │ ├── nn light.svg │ ├── or dark.svg │ ├── or light.svg │ ├── pa dark.svg │ ├── pa light.svg │ ├── pl dark.svg │ ├── pl light.svg │ ├── pt-br dark.svg │ ├── pt-br light.svg │ ├── pt-pt dark.svg │ ├── pt-pt light.svg │ ├── quz dark.svg │ ├── quz light.svg │ ├── ro dark.svg │ ├── ro light.svg │ ├── ru dark.svg │ ├── ru light.svg │ ├── sk dark.svg │ ├── sk light.svg │ ├── sl dark.svg │ ├── sl light.svg │ ├── sq dark.svg │ ├── sq light.svg │ ├── sr dark.svg │ ├── sr light.svg │ ├── sv dark.svg │ ├── sv light.svg │ ├── ta dark.svg │ ├── ta light.svg │ ├── te dark.svg │ ├── te light.svg │ ├── th dark.svg │ ├── th light.svg │ ├── tr dark.svg │ ├── tr light.svg │ ├── ug dark.svg │ ├── ug light.svg │ ├── uk dark.svg │ ├── uk light.svg │ ├── ur dark.svg │ ├── ur light.svg │ ├── uz dark.svg │ ├── uz light.svg │ ├── vi dark.svg │ ├── vi light.svg │ ├── zh-cn dark.svg │ ├── zh-cn light.svg │ ├── zh-tw dark.svg │ └── zh-tw light.svg ├── index.html ├── ms-store-badge.bundled.js └── staticwebapp.config.json ├── images └── discord-psi.png ├── package-lock.json ├── package.json ├── release.yml ├── rollup.config.js ├── src ├── 9p1j8s7ccwwt_us_en-us.html ├── iframe.html ├── images │ ├── af dark.svg │ ├── af light.svg │ ├── am dark.svg │ ├── am light.svg │ ├── ar dark.svg │ ├── ar light.svg │ ├── as dark.svg │ ├── as light.svg │ ├── az dark.svg │ ├── az light.svg │ ├── bg dark.svg │ ├── bg light.svg │ ├── bn dark.svg │ ├── bn light.svg │ ├── bs dark.svg │ ├── bs light.svg │ ├── ca dark.svg │ ├── ca light.svg │ ├── cs dark.svg │ ├── cs light.svg │ ├── cy dark.svg │ ├── cy light.svg │ ├── da dark.svg │ ├── da light.svg │ ├── de dark.svg │ ├── de light.svg │ ├── el dark.svg │ ├── el light.svg │ ├── en-us dark.svg │ ├── en-us light.svg │ ├── es dark.svg │ ├── es light.svg │ ├── et dark.svg │ ├── et light.svg │ ├── fa dark.svg │ ├── fa light.svg │ ├── fi dark.svg │ ├── fi light.svg │ ├── fil dark.svg │ ├── fil light.svg │ ├── fr dark.svg │ ├── fr light.svg │ ├── ga dark.svg │ ├── ga light.svg │ ├── gd dark.svg │ ├── gd light.svg │ ├── gl dark.svg │ ├── gl light.svg │ ├── gu dark.svg │ ├── gu light.svg │ ├── he dark.svg │ ├── he light.svg │ ├── hi dark.svg │ ├── hi light.svg │ ├── hr dark.svg │ ├── hr light.svg │ ├── hu dark.svg │ ├── hu light.svg │ ├── hy dark.svg │ ├── hy light.svg │ ├── id dark.svg │ ├── id light.svg │ ├── is dark.svg │ ├── is light.svg │ ├── it dark.svg │ ├── it light.svg │ ├── ja dark.svg │ ├── ja light.svg │ ├── ka dark.svg │ ├── ka light.svg │ ├── kk dark.svg │ ├── kk light.svg │ ├── km dark.svg │ ├── km light.svg │ ├── kn dark.svg │ ├── kn light.svg │ ├── ko dark.svg │ ├── ko light.svg │ ├── kok dark.svg │ ├── kok light.svg │ ├── lb dark.svg │ ├── lb light.svg │ ├── lo dark.svg │ ├── lo light.svg │ ├── lt dark.svg │ ├── lt light.svg │ ├── lv dark.svg │ ├── lv light.svg │ ├── mi dark.svg │ ├── mi light.svg │ ├── mk dark.svg │ ├── mk light.svg │ ├── ml dark.svg │ ├── ml light.svg │ ├── mr dark.svg │ ├── mr light.svg │ ├── ms dark.svg │ ├── ms light.svg │ ├── mt dark.svg │ ├── mt light.svg │ ├── ne dark.svg │ ├── ne light.svg │ ├── nl dark.svg │ ├── nl light.svg │ ├── nn dark.svg │ ├── nn light.svg │ ├── or dark.svg │ ├── or light.svg │ ├── pa dark.svg │ ├── pa light.svg │ ├── pl dark.svg │ ├── pl light.svg │ ├── pt-br dark.svg │ ├── pt-br light.svg │ ├── pt-pt dark.svg │ ├── pt-pt light.svg │ ├── quz dark.svg │ ├── quz light.svg │ ├── ro dark.svg │ ├── ro light.svg │ ├── ru dark.svg │ ├── ru light.svg │ ├── sk dark.svg │ ├── sk light.svg │ ├── sl dark.svg │ ├── sl light.svg │ ├── sq dark.svg │ ├── sq light.svg │ ├── sr dark.svg │ ├── sr light.svg │ ├── sv dark.svg │ ├── sv light.svg │ ├── ta dark.svg │ ├── ta light.svg │ ├── te dark.svg │ ├── te light.svg │ ├── th dark.svg │ ├── th light.svg │ ├── tr dark.svg │ ├── tr light.svg │ ├── ug dark.svg │ ├── ug light.svg │ ├── uk dark.svg │ ├── uk light.svg │ ├── ur dark.svg │ ├── ur light.svg │ ├── uz dark.svg │ ├── uz light.svg │ ├── vi dark.svg │ ├── vi light.svg │ ├── zh-cn dark.svg │ ├── zh-cn light.svg │ ├── zh-tw dark.svg │ └── zh-tw light.svg ├── index.html ├── ms-store-badge.d.ts ├── ms-store-badge.ts ├── psi-service.d.ts ├── psi-service.d.ts.map ├── psi-service.js ├── psi-service.js.map ├── psi-service.ts ├── staticwebapp.config.json ├── throttle-async.d.ts ├── throttle-async.d.ts.map ├── throttle-async.js ├── throttle-async.js.map └── throttle-async.ts ├── tsconfig.json └── web-dev-server.config.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /lib/ 3 | /test/ 4 | custom-elements.json 5 | # top level source 6 | my-element.js 7 | my-element.js.map 8 | my-element.d.ts 9 | my-element.d.ts.map 10 | ms-store-badge.d.ts.map 11 | ms-store-badge.js 12 | ms-store-badge.js.map 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:8000", 12 | "webRoot": "${workspaceFolder}/src" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # App Store Badge 2 | 3 | This repository contains the source code for web component that displays the "Get this app on the Microsoft Store" badge. 4 | 5 | ## Usage 6 | 7 | Generate your own app badge using https://apps.microsoft.com/store/app-badge/ 8 | 9 | Alternately, add the following code in your HTML where you want the button to appear: 10 | 11 | ```html 12 | 13 | 14 | ``` 15 | 16 | The component has some additional configuration options: 17 | 18 | | Option | Type | Default value | Description | 19 | |--------------|-----------|------------|------------| 20 | | productid | string | undefined | Your app ID in the Microsoft Store. You can find this value by navigating to your app in the [Microsoft Store for Web](https://apps.microsoft.com) and grabbing the last part of the URL. The Discord app, for example, is at [https://apps.microsoft.com/store/detail/discord/XPDC2RH70K22MN](https://apps.microsoft.com/store/detail/discord/XPDC2RH70K22MN), so its product ID is `XPDC2RH70K22MN` | 21 | | productname | string | undefined | Your app product name in the Microsoft Store. This is only used for the default direct method of installation, where your product name will be part of the installer template. 22 | | cid | string | undefined | Your app campaign code for analytics purposes. If the user clicks the app badge, this value will be passed to analytics and will be available to you in your [Microsoft Partner Center](https://partner.microsoft.com/en-us/dashboard/home) reports. | 23 | | window-mode | "direct" or "full" | "direct" | Configure the badge to automatically download your app with direct mode, or open your app in full store mode.

In `direct` mode, users who click your app badge will see the default portable store installer, an executable that allows users to download your product directly from the browser. ![Discord in PSI](images/discord-psi.png)
In `full` mode, users who click your app badge will see full store app:
) | 24 | | theme | "dark" or "light" or "auto" | "dark" | Configure the badge theme dark mode, light mode, or auto mode. Auto mode detects the user's dark mode preference and sets the badge theme accordingly.

`dark` should be used on sites with light backgrounds:


`light` should be use on sites with dark backgrounds:
| 25 | | animation | "on" or "off" | "off" | When `on`, the badge will use an animation and shadow on hover. Alternately, you may build and apply your own animations by adding CSS to the `img` part. See [styling the badge](#styling-the-badge) for more information. | 26 | | language | string | '' | The language to display the app badge. If left empty, the language will be detected from the user's browser `navigator.userAgent.language`.
Sample of specifying a different language:


See supported languages
| 27 | 28 | Example using all the available options: 29 | 30 | ```html 31 | 32 | 33 | ``` 34 | 35 | ## Styling the badge 36 | 37 | To style the app badge web component, use [CSS parts](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) to style the badge image. Specifically, the app badge web component exposes the badge image as `img` part: 38 | 39 | ```css 40 | /* Customize the badge's appearance */ 41 | ms-store-badge::part(img) { 42 | max-height: 52px; 43 | } 44 | ``` 45 | 46 | ## Why a web component? 47 | 48 | Most app store badges are a simple image with a link to the web store. Why is this a web component? 49 | 50 | In short, for better localization and better user experience. 51 | 52 | - **Localization**: the web component supports automatic detection of the user's locale, showing a localized button to the user based on the user's browser locale. 53 | - **Fewer security prompts**: if the user is on Edge on Windows, no browser security prompt ("this site is trying to launch Microsoft Store") is shown. 54 | - **Better behavior on other OSes**: If the user is on MacOS or other non-Windows OS and they click your app badge, instead of trying (and failing) to launch the Microsoft Store app, it instead launches the OS's native share dialog, allowing the user to share the link to your app. 55 | - **Automatic theme support**: You can configure your app badge to use automatic theme detection. When `theme="auto"`, the app badge will be themed based on whether the user has [dark mode](https://css-tricks.com/dark-modes-with-css/) enabled. 56 | 57 | ## HTML-only 58 | 59 | While the app badge script is small (about 9k), there are contexts where a script doesn't make sense: Github markdown pages, static sites, or other contexts without Javascript available. For such contexts, you can use a simple image with link: 60 | 61 | ```html 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | Be mindful that the HTML-only version of the badge loses some functionality of the full badge. For example, automatic theme detection, OS-specific behavior (e.g. launch the Store on Windows, or open the [apps's web Product Description Page](https://apps.microsoft.com/store/detail/chavah-messianic-radio/9NHKJB6LPPTV) on non-Windows OS), localization, and fewer user prompts for Edge users are not available in the HTML-only version of the badge. 69 | 70 | ## Running the code 71 | 72 | To run code locally, `npm run dev`, then launch http://localhost:8000/create-your-own.html 73 | 74 | To build for production, `npm run build`. This will create the production artifacts in `/dist`. 75 | 76 | ## Deploying 77 | 78 | ADO release pipeline is set to deploy successful builds of main to the app badge CDN. Code changes are then synced to GH repo for open source visibility. 79 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /build.yml: -------------------------------------------------------------------------------- 1 | # This Yaml Document has been converted by ESAI Yaml Pipeline Conversion Tool. 2 | 3 | trigger: 4 | - main 5 | resources: 6 | repositories: 7 | - repository: 1ESPipelineTemplates 8 | type: git 9 | name: 1ESPipelineTemplates/1ESPipelineTemplates 10 | ref: refs/tags/release 11 | extends: 12 | template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates 13 | parameters: 14 | pool: 15 | name: StoreWebBuildPool-Centeral 16 | customBuildTags: 17 | - ES365AIMigrationTooling 18 | stages: 19 | - stage: stage 20 | jobs: 21 | - job: job 22 | templateContext: 23 | outputs: 24 | - output: pipelineArtifact 25 | displayName: 'Publish build artifact' 26 | targetPath: '$(System.DefaultWorkingDirectory)/dist' 27 | artifactName: 'ms-store-badge-artifacts' 28 | publishLocation: 'Container' 29 | steps: 30 | - checkout: self 31 | submodules: true 32 | 33 | - script: | 34 | icacls "$(Build.SourcesDirectory)" /grant Everyone:F /T 35 | if exist "$(Build.SourcesDirectory)\.npmrc" ( 36 | dir "$(Build.SourcesDirectory)\.npmrc" 37 | ) else ( 38 | echo .npmrc file not found 39 | echo. > "$(Build.SourcesDirectory)\.npmrc" 40 | ) 41 | displayName: 'Add vsts directory permissions' 42 | 43 | - script: | 44 | npm install -g typescript 45 | displayName: 'Install TypeScript' 46 | 47 | - task: NpmAuthenticate@0 48 | inputs: 49 | workingFile: '.npmrc' 50 | displayName: 'Authenticate to NPM' 51 | 52 | - script: | 53 | echo "Cleaning dist directory" 54 | rm -rf "$(System.DefaultWorkingDirectory)/dist" 55 | displayName: 'Clean dist directory' 56 | 57 | - script: | 58 | npm install 59 | displayName: 'Install dependencies' 60 | 61 | - script: | 62 | echo "Listing current directory contents:" 63 | dir 64 | displayName: 'List directory contents after install' 65 | 66 | - script: | 67 | cd src 68 | tsc -t es2015 --moduleResolution node ms-store-badge.ts 69 | displayName: 'Compile TypeScript' 70 | 71 | - script: | 72 | npm run build:prod 73 | displayName: 'Build badge' 74 | 75 | - script: | 76 | echo "Listing dist directory structure:" 77 | dir dist /s 78 | displayName: 'List dist directory structure' 79 | 80 | - script: | 81 | echo "Listing current directory contents:" 82 | dir 83 | displayName: 'List current directory contents after build' 84 | 85 | - script: | 86 | echo "Setting up GitHub repository" 87 | git config --global user.name "Justin Lau" 88 | git config --global user.email "justinlau@microsoft.com" 89 | git remote add github https://$(GITHUB_TOKEN)@github.com/microsoft/app-store-badge.git 90 | git fetch github 91 | git checkout github/main 92 | git merge origin/main -X ours --allow-unrelated-histories --no-edit 93 | git rm .npmrc -f 94 | git add -A 95 | git commit -m "Sync from ADO repo" || echo "No changes to commit" 96 | git push github HEAD:main --force 97 | displayName: 'Sync ADO Repo to GitHub' 98 | env: 99 | GITHUB_TOKEN: $(GITHUB_TOKEN) -------------------------------------------------------------------------------- /dist/badge/ms-store-badge.bundled.js: -------------------------------------------------------------------------------- 1 | var t=function(t,i,e,n){return new(e||(e=Promise))((function(o,r){function s(t){try{c(n.next(t))}catch(t){r(t)}}function a(t){try{c(n.throw(t))}catch(t){r(t)}}function c(t){var i;t.done?o(t.value):(i=t.value,i instanceof e?i:new e((function(t){t(i)}))).then(s,a)}c((n=n.apply(t,i||[])).next())}))};function i(i,e){return t(this,arguments,void 0,(function*(i,e,n={},o){const r="psi_f_svc",s={method:"GET",headers:{Origin:"https://apps.microsoft.com",Referer:document.URL,"Access-Control-Request-Method":"GET","X-Correlation-Id":crypto.randomUUID(),"Content-Type":"application/octet-stream"},cache:"no-cache",params:new URLSearchParams(o)},a=Object.assign(Object.assign({},s),n);let c=null;try{c=yield fetch(`${o}`,a)}catch(t){window.location.href=`ms-windows-store://pdp/?productid=${i}&ocid=${r}na`}(null==c?void 0:c.ok)?function(i){t(this,void 0,void 0,(function*(){var t;const n=null!==(t=i.headers.get("X-PSI-FileName"))&&void 0!==t?t:`${e} Installer.exe`,o=yield i.blob(),r=URL.createObjectURL(o),s=document.createElement("a");s.href=r,s.download=decodeURIComponent(n),s.style.display="none",document.body.appendChild(s);try{s.click(),yield new Promise((t=>setTimeout(t,1e3)))}catch(t){}finally{URL.revokeObjectURL(r),document.body.removeChild(s)}}))}(c):function(e){t(this,void 0,void 0,(function*(){const t=null==e?void 0:e.status;window.location.href=`ms-windows-store://pdp/?productid=${i}&ocid=${r}${t}`}))}(c)}))}var e=function(t,i,e,n){return new(e||(e=Promise))((function(o,r){function s(t){try{c(n.next(t))}catch(t){r(t)}}function a(t){try{c(n.throw(t))}catch(t){r(t)}}function c(t){var i;t.done?o(t.value):(i=t.value,i instanceof e?i:new e((function(t){t(i)}))).then(s,a)}c((n=n.apply(t,i||[])).next())}))};let n=!1;var o,r,s,a,c,h,d,l,u,f=function(t,i,e,n){return new(e||(e=Promise))((function(o,r){function s(t){try{c(n.next(t))}catch(t){r(t)}}function a(t){try{c(n.throw(t))}catch(t){r(t)}}function c(t){var i;t.done?o(t.value):(i=t.value,i instanceof e?i:new e((function(t){t(i)}))).then(s,a)}c((n=n.apply(t,i||[])).next())}))},m=function(t,i,e,n,o){if("m"===n)throw new TypeError("Private method is not writable");if("a"===n&&!o)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof i?t!==i||!o:!i.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===n?o.call(t,e):o?o.value=e:i.set(t,e),e},p=function(t,i,e,n){if("a"===e&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof i?t!==i||!n:!i.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?n:"a"===e?n.call(t):n?n.value:i.get(t)};class g extends HTMLElement{constructor(){super(),o.add(this),this.productId="",this.productName="",this.cid="",this.size="large",this.windowMode="direct",this.theme="dark",this.language="",this.animation="off",s.set(this,r.englishLanguage),a.set(this,"prod"),c.set(this,"dev"===p(this,a,"f")?"iframe.html":"https://get.microsoft.com/iframe.html"),h.set(this,"dev"===p(this,a,"f")?"/images":"https://get.microsoft.com/images"),d.set(this,{isWindows:!1,windowsVersion:null,isEdgeBrowser:!1}),l.set(this,!1),this.PSIDownloadUrl="https://get.microsoft.com/installer/download/",this.throttleDownload=function(t,i){return(...o)=>e(this,void 0,void 0,(function*(){if(!n){n=!0;try{yield t(...o)}finally{setTimeout((()=>n=!1),i)}}}))}(i,1500),this.imgPSIHandler=()=>this.throttleDownload(this.productId,this.productName||"Microsoft Store Direct",void 0,this.getPSIUrl()),this.imgPDPHandler=t=>this.launchApp(t),this.getPlatformDetails().then((t=>m(this,d,t,"f"))),m(this,s,r.getSupportedLanguageFromCode(this.language),"f"),this.language=p(this,s,"f").code;const t=this.attachShadow({mode:"open"}),u=this.createStyle(),f=this.createHtml();t.appendChild(u),t.appendChild(f)}updateImageSrc(){var t;const i=null===(t=this.shadowRoot)||void 0===t?void 0:t.querySelector("img");i&&(i.setAttribute("src",this.getImageSource()),i.className=this.getImageClass())}updateListeners(){var t;const i=null===(t=this.shadowRoot)||void 0===t?void 0:t.querySelector("img");null==i||i.removeEventListener("click",this.imgPDPHandler),null==i||i.removeEventListener("click",this.imgPSIHandler),"direct"===this.windowMode?null==i||i.addEventListener("click",this.imgPSIHandler):null==i||i.addEventListener("click",this.imgPDPHandler)}connectedCallback(){}static get observedAttributes(){return["productid","productname","cid","window-mode","theme","size","language","animation"]}attributeChangedCallback(t,i,e){var n;"size"!==t||"large"!==e&&"small"!==e||i===e?"language"!==t||e===i||"string"!=typeof e&&e?"productid"===t&&e!==i&&"string"==typeof e?this.productId=e:"productname"===t&&e!==i&&"string"==typeof e?this.productName=e:"cid"===t&&e!==i&&"string"==typeof e?this.cid=e:"window-mode"!==t||"popup"!==e&&"full"!==e&&"direct"!==e||i===e?"theme"!==t||"dark"!=e&&"light"!==e&&"auto"!==e||i===e?"animation"!==t||"on"!==e&&"off"!==e||i===e||(this.animation=e,null===(n=this.shadowRoot)||void 0===n||n.appendChild(this.createStyle())):(this.theme=e,this.updateImageSrc()):("popup"===this.windowMode&&(this.windowMode="full"),this.windowMode=e,this.updateImageSrc(),this.updateListeners()):(m(this,s,r.getSupportedLanguageFromCode(e),"f"),this.language=p(this,s,"f").code,this.updateImageSrc()):(this.size=e,this.updateImageSrc())}createStyle(){var t="";t="on"===this.animation?"\n :host {\n display: inline-block;\n cursor: pointer;\n height: fit-content;\n }\n\n iframe {\n border: none;\n width: 0px;\n height: 0px;\n }\n\n img {\n border-radius: 8px;\n transition: 0.35s ease;\n }\n \n img:hover {\n transform: translate(0, -4px);\n cursor: pointer;\n box-shadow: 0 12px 40px 20px rgba(0, 0, 0, 0.05);\n }\n \n img.large {\n height: 104px;\n }\n \n div {\n height: 104px;\n }":"\n :host {\n display: inline-block;\n cursor: pointer;\n height: fit-content;\n }\n\n iframe {\n border: none;\n width: 0px;\n height: 0px;\n }\n\n img {\n width: auto;\n border-radius: 8px;\n }\n\n img.large {\n height: 104px;\n }\n \n div {\n height: 104px;\n }";const i=document.createElement("style");return i.textContent=t,i}createHtml(){const t=document.createElement("div");return t.appendChild(this.createImage()),t.appendChild(this.createIFrame()),t}getPlatformDetails(){return f(this,void 0,void 0,(function*(){const t=navigator;if(t.userAgentData&&t.userAgentData.getHighEntropyValues)try{const i=yield t.userAgentData.getHighEntropyValues(["platform","platformVersion"]),e="Windows"===i.platform;return{isWindows:e,windowsVersion:e?parseFloat((null==i?void 0:i.platformVersion)||""):null,isEdgeBrowser:(t.userAgentData.brands||[]).some((t=>"Microsoft Edge"===t.brand))}}catch(t){}const i=new RegExp(/.?Windows NT (\d+\.?\d?\.?\d?\.?\d?)/gi).exec(navigator.userAgent);return i&&i.length>=2?{isWindows:!0,windowsVersion:parseFloat(i[1]),isEdgeBrowser:!!navigator.userAgent.match("Edg/")}:{isWindows:!1,windowsVersion:null,isEdgeBrowser:!!navigator.userAgent.match("Edg/")}}))}static getSupportedLanguageFromCode(t){if(!t)return r.getSupportedLanguageFromUserAgent();const i=r.supportedLanguages.find((i=>i.code===t.toLowerCase()))||r.supportedLanguages.find((i=>i.code.substring(0,3)===t.toLowerCase()))||r.supportedLanguages.find((i=>i.code.substring(0,2)===t.toLowerCase()));return i||r.englishLanguage}static getSupportedLanguageFromUserAgent(){const t=r.supportedLanguages.find((t=>t.code.toLowerCase()===(navigator.language||"").toLowerCase()));if(t)return t;if(navigator.languages){var i=navigator.languages.map((t=>r.supportedLanguages.find((i=>i.code===t)))).find((t=>!!t));if(i)return i}const e=navigator.language.indexOf("-");if(e>0){const t=navigator.language.substring(0,e),i=r.supportedLanguages.find((i=>i.code.toLowerCase()===t.toLowerCase()));if(i)return i}return r.englishLanguage}createIFrame(){const t=document.createElement("iframe");return t.setAttribute("loading","eager"),t.title="Microsoft Store App Badge",t.src=p(this,c,"f"),t}createImage(){const t=document.createElement("img");return t.setAttribute("part","img"),t.src=this.getImageSource(),t.className=this.getImageClass(),t.alt="Microsoft Store app badge","direct"===this.windowMode?t.addEventListener("click",this.imgPSIHandler):t.addEventListener("click",this.imgPDPHandler),t}getImageSource(){var t=null;if("dark"===this.theme)t=p(this,s,"f").imageLarge.fileName;else if("light"===this.theme)t=p(this,s,"f").imageLargeLight.fileName;else if("auto"===this.theme){t=window.matchMedia("(prefers-color-scheme:dark)").matches?p(this,s,"f").imageLargeLight.fileName:p(this,s,"f").imageLarge.fileName}return`${p(this,h,"f")}/${t}`}getImageClass(){return"large"===this.size?"large":"small"}launchApp(t){this.productId&&(p(this,d,"f").isWindows&&p(this,d,"f").isEdgeBrowser?this.launchStoreAppPdpViaWhitelistedDomain():p(this,d,"f").isWindows?this.launchFullStoreApp():this.launchStoreWebPdp(t))}getPSIUrl(){return`${this.PSIDownloadUrl}${this.productId.toUpperCase()}?referrer=appbadge&source=${encodeURIComponent(window.location.hostname.toLowerCase())}`}launchFullStoreApp(){const t=new URLSearchParams;t.append("productid",this.productId),t.append("referrer","appbadge"),t.append("source",window.location.hostname.toLowerCase()),this.cid&&t.append("cid",this.cid),location.href="ms-windows-store://pdp/?"+t.toString()}launchStoreAppPdpViaWhitelistedDomain(){var t;const i=null===(t=this.shadowRoot)||void 0===t?void 0:t.querySelector("iframe");if(!p(this,l,"f")&&i&&i.contentWindow){p(this,o,"m",u).call(this);const t={message:"launch",productId:this.productId,cid:this.cid,windowMode:this.windowMode,source:window.location.hostname};i.contentWindow.postMessage(t,"*")}else this.launchFullStoreApp()}launchStoreWebPdp(t){var i="";i=this.cid?`https://apps.microsoft.com/store/detail/${this.productId}?cid=${encodeURIComponent(this.cid)}&referrer=appbadge&source=${encodeURIComponent(window.location.hostname.toLowerCase())}`:`https://apps.microsoft.com/store/detail/${this.productId}?referrer=appbadge&source=${encodeURIComponent(window.location.hostname.toLowerCase())}`,t.ctrlKey?window.open(i,"_blank"):window.location.href=i}static createSupportedLanguages(){let t=new Map;t.set("Afrikaans","af"),t.set("Albanian","sq"),t.set("Amharic","am"),t.set("Arabic","ar"),t.set("Armenian","hy"),t.set("Assamese","as"),t.set("Azerbaijani","az"),t.set("Bengali","bn"),t.set("Bosnian","bs"),t.set("Bulgarian","bg"),t.set("Catalan","ca"),t.set("Chinese_Simplified","zh-cn"),t.set("Chinese_Traditional","zh-tw"),t.set("Croatian","hr"),t.set("Czech","cs"),t.set("Danish","da"),t.set("Dutch","nl"),t.set("English","en-us"),t.set("Estonian","et"),t.set("Filipino","fil"),t.set("Finnish","fi"),t.set("French","fr"),t.set("Galician","gl"),t.set("Georgian","ka"),t.set("German","de"),t.set("Greek","el"),t.set("Gujarati","gu"),t.set("Hebrew","he"),t.set("Hindi","hi"),t.set("Hungarian","hu"),t.set("Icelandic","is"),t.set("Indonesian","id"),t.set("Irish","ga"),t.set("Italian","it"),t.set("Japanese","ja"),t.set("Kannada","kn"),t.set("Kazakh","kk"),t.set("Khmer","km"),t.set("Konkani","kok"),t.set("Korean","ko"),t.set("Lao","lo"),t.set("Latvian","lv"),t.set("Lithuanian","lt"),t.set("Luxembourgish","lb"),t.set("Macedonian","mk"),t.set("Malay","ms"),t.set("Malayalam","ml"),t.set("Maltese","mt"),t.set("Māori","mi"),t.set("Marathi","mr"),t.set("Nepali","ne"),t.set("Norwegian","nn"),t.set("Oriya","or"),t.set("Persian","fa"),t.set("Polish","pl"),t.set("Portuguese_Brazil","pt-br"),t.set("Portuguese_Portugal","pt-pt"),t.set("Punjabi","pa"),t.set("Quechua","quz"),t.set("Romanian","ro"),t.set("Russian","ru"),t.set("Scottish_Gaelic","gd"),t.set("Serbian","sr"),t.set("Slovak","sk"),t.set("Slovenian","sl"),t.set("Spanish","es"),t.set("Swedish","sv"),t.set("Tamil","ta"),t.set("Telugu","te"),t.set("Thai","th"),t.set("Turkish","tr"),t.set("Uighur","ug"),t.set("Ukrainian","uk"),t.set("Urdu","ur"),t.set("Uzbek","uz"),t.set("Vietnamese","vi");let i=[];for(let e of t.keys()){let n={name:e,imageLarge:{fileName:t.get(e).concat(" ").concat("dark.svg")},imageLargeLight:{fileName:t.get(e).concat(" ").concat("light.svg")},code:t.get(e)||""};i.push(n)}return i}}r=g,s=new WeakMap,a=new WeakMap,c=new WeakMap,h=new WeakMap,d=new WeakMap,l=new WeakMap,o=new WeakSet,u=function(){const t="securitypolicyviolation",i=i=>{"frame-src"===i.violatedDirective&&i.type===t&&(m(this,l,!0,"f"),this.launchFullStoreApp())};document.addEventListener(t,i,{once:!0}),setTimeout((()=>document.removeEventListener(t,i)),2e3)},g.englishLanguage={name:"English",code:"en-us",imageLarge:{fileName:"en-us dark.svg"},imageLargeLight:{fileName:"en-us light.svg"}},g.supportedLanguages=r.createSupportedLanguages(),customElements.define("ms-store-badge",g); 2 | -------------------------------------------------------------------------------- /dist/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Microsoft Store Popup Badge 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /dist/images/he dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /dist/images/ko dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /dist/images/mt dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | App badge tester 24 | 25 | 26 | 27 |
28 |

App Badge test page

29 | 30 |
31 |
32 |
33 | 34 |
35 | 36 | 41 | 46 | 47 | 48 |
49 | 50 |
51 | 52 | 57 | 58 | 59 |
60 | 61 |
62 | 63 | 143 |
144 | 145 |
146 | 147 | 152 |
153 | 154 |
155 | 156 | 161 |
162 | 163 |
164 | 165 | 169 |
170 | 171 |
172 | 175 | 176 | 181 |
182 |
183 |
184 | 185 |
186 |
188 | 189 | 190 |
191 |
192 |
193 |
194 | 195 | 196 | 199 | 200 | 201 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /dist/ms-store-badge.bundled.js: -------------------------------------------------------------------------------- 1 | var t=function(t,i,e,n){return new(e||(e=Promise))((function(o,r){function s(t){try{c(n.next(t))}catch(t){r(t)}}function a(t){try{c(n.throw(t))}catch(t){r(t)}}function c(t){var i;t.done?o(t.value):(i=t.value,i instanceof e?i:new e((function(t){t(i)}))).then(s,a)}c((n=n.apply(t,i||[])).next())}))};function i(i,e){return t(this,arguments,void 0,(function*(i,e,n={},o){const r="psi_f_svc",s={method:"GET",headers:{Origin:"https://apps.microsoft.com",Referer:document.URL,"Access-Control-Request-Method":"GET","X-Correlation-Id":crypto.randomUUID(),"Content-Type":"application/octet-stream"},cache:"no-cache",params:new URLSearchParams(o)},a=Object.assign(Object.assign({},s),n);let c=null;try{c=yield fetch(`${o}`,a)}catch(t){window.location.href=`ms-windows-store://pdp/?productid=${i}&ocid=${r}na`}(null==c?void 0:c.ok)?function(i){t(this,void 0,void 0,(function*(){var t;const n=null!==(t=i.headers.get("X-PSI-FileName"))&&void 0!==t?t:`${e} Installer.exe`,o=yield i.blob(),r=URL.createObjectURL(o),s=document.createElement("a");s.href=r,s.download=decodeURIComponent(n),s.style.display="none",document.body.appendChild(s);try{s.click(),yield new Promise((t=>setTimeout(t,1e3)))}catch(t){}finally{URL.revokeObjectURL(r),document.body.removeChild(s)}}))}(c):function(e){t(this,void 0,void 0,(function*(){const t=null==e?void 0:e.status;window.location.href=`ms-windows-store://pdp/?productid=${i}&ocid=${r}${t}`}))}(c)}))}var e=function(t,i,e,n){return new(e||(e=Promise))((function(o,r){function s(t){try{c(n.next(t))}catch(t){r(t)}}function a(t){try{c(n.throw(t))}catch(t){r(t)}}function c(t){var i;t.done?o(t.value):(i=t.value,i instanceof e?i:new e((function(t){t(i)}))).then(s,a)}c((n=n.apply(t,i||[])).next())}))};let n=!1;var o,r,s,a,c,h,d,l,u,f=function(t,i,e,n){return new(e||(e=Promise))((function(o,r){function s(t){try{c(n.next(t))}catch(t){r(t)}}function a(t){try{c(n.throw(t))}catch(t){r(t)}}function c(t){var i;t.done?o(t.value):(i=t.value,i instanceof e?i:new e((function(t){t(i)}))).then(s,a)}c((n=n.apply(t,i||[])).next())}))},m=function(t,i,e,n,o){if("m"===n)throw new TypeError("Private method is not writable");if("a"===n&&!o)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof i?t!==i||!o:!i.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===n?o.call(t,e):o?o.value=e:i.set(t,e),e},p=function(t,i,e,n){if("a"===e&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof i?t!==i||!n:!i.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?n:"a"===e?n.call(t):n?n.value:i.get(t)};class g extends HTMLElement{constructor(){super(),o.add(this),this.productId="",this.productName="",this.cid="",this.size="large",this.windowMode="direct",this.theme="dark",this.language="",this.animation="off",s.set(this,r.englishLanguage),a.set(this,"prod"),c.set(this,"dev"===p(this,a,"f")?"iframe.html":"https://get.microsoft.com/iframe.html"),h.set(this,"dev"===p(this,a,"f")?"/images":"https://get.microsoft.com/images"),d.set(this,{isWindows:!1,windowsVersion:null,isEdgeBrowser:!1}),l.set(this,!1),this.PSIDownloadUrl="https://get.microsoft.com/installer/download/",this.throttleDownload=function(t,i){return(...o)=>e(this,void 0,void 0,(function*(){if(!n){n=!0;try{yield t(...o)}finally{setTimeout((()=>n=!1),i)}}}))}(i,1500),this.imgPSIHandler=()=>this.throttleDownload(this.productId,this.productName||"Microsoft Store Direct",void 0,this.getPSIUrl()),this.imgPDPHandler=t=>this.launchApp(t),this.getPlatformDetails().then((t=>m(this,d,t,"f"))),m(this,s,r.getSupportedLanguageFromCode(this.language),"f"),this.language=p(this,s,"f").code;const t=this.attachShadow({mode:"open"}),u=this.createStyle(),f=this.createHtml();t.appendChild(u),t.appendChild(f)}updateImageSrc(){var t;const i=null===(t=this.shadowRoot)||void 0===t?void 0:t.querySelector("img");i&&(i.setAttribute("src",this.getImageSource()),i.className=this.getImageClass())}updateListeners(){var t;const i=null===(t=this.shadowRoot)||void 0===t?void 0:t.querySelector("img");null==i||i.removeEventListener("click",this.imgPDPHandler),null==i||i.removeEventListener("click",this.imgPSIHandler),"direct"===this.windowMode?null==i||i.addEventListener("click",this.imgPSIHandler):null==i||i.addEventListener("click",this.imgPDPHandler)}connectedCallback(){}static get observedAttributes(){return["productid","productname","cid","window-mode","theme","size","language","animation"]}attributeChangedCallback(t,i,e){var n;"size"!==t||"large"!==e&&"small"!==e||i===e?"language"!==t||e===i||"string"!=typeof e&&e?"productid"===t&&e!==i&&"string"==typeof e?this.productId=e:"productname"===t&&e!==i&&"string"==typeof e?this.productName=e:"cid"===t&&e!==i&&"string"==typeof e?this.cid=e:"window-mode"!==t||"popup"!==e&&"full"!==e&&"direct"!==e||i===e?"theme"!==t||"dark"!=e&&"light"!==e&&"auto"!==e||i===e?"animation"!==t||"on"!==e&&"off"!==e||i===e||(this.animation=e,null===(n=this.shadowRoot)||void 0===n||n.appendChild(this.createStyle())):(this.theme=e,this.updateImageSrc()):("popup"===this.windowMode&&(this.windowMode="full"),this.windowMode=e,this.updateImageSrc(),this.updateListeners()):(m(this,s,r.getSupportedLanguageFromCode(e),"f"),this.language=p(this,s,"f").code,this.updateImageSrc()):(this.size=e,this.updateImageSrc())}createStyle(){var t="";t="on"===this.animation?"\n :host {\n display: inline-block;\n cursor: pointer;\n height: fit-content;\n }\n\n iframe {\n border: none;\n width: 0px;\n height: 0px;\n }\n\n img {\n border-radius: 8px;\n transition: 0.35s ease;\n }\n \n img:hover {\n transform: translate(0, -4px);\n cursor: pointer;\n box-shadow: 0 12px 40px 20px rgba(0, 0, 0, 0.05);\n }\n \n img.large {\n height: 104px;\n }\n \n div {\n height: 104px;\n }":"\n :host {\n display: inline-block;\n cursor: pointer;\n height: fit-content;\n }\n\n iframe {\n border: none;\n width: 0px;\n height: 0px;\n }\n\n img {\n width: auto;\n border-radius: 8px;\n }\n\n img.large {\n height: 104px;\n }\n \n div {\n height: 104px;\n }";const i=document.createElement("style");return i.textContent=t,i}createHtml(){const t=document.createElement("div");return t.appendChild(this.createImage()),t.appendChild(this.createIFrame()),t}getPlatformDetails(){return f(this,void 0,void 0,(function*(){const t=navigator;if(t.userAgentData&&t.userAgentData.getHighEntropyValues)try{const i=yield t.userAgentData.getHighEntropyValues(["platform","platformVersion"]),e="Windows"===i.platform;return{isWindows:e,windowsVersion:e?parseFloat((null==i?void 0:i.platformVersion)||""):null,isEdgeBrowser:(t.userAgentData.brands||[]).some((t=>"Microsoft Edge"===t.brand))}}catch(t){}const i=new RegExp(/.?Windows NT (\d+\.?\d?\.?\d?\.?\d?)/gi).exec(navigator.userAgent);return i&&i.length>=2?{isWindows:!0,windowsVersion:parseFloat(i[1]),isEdgeBrowser:!!navigator.userAgent.match("Edg/")}:{isWindows:!1,windowsVersion:null,isEdgeBrowser:!!navigator.userAgent.match("Edg/")}}))}static getSupportedLanguageFromCode(t){if(!t)return r.getSupportedLanguageFromUserAgent();const i=r.supportedLanguages.find((i=>i.code===t.toLowerCase()))||r.supportedLanguages.find((i=>i.code.substring(0,3)===t.toLowerCase()))||r.supportedLanguages.find((i=>i.code.substring(0,2)===t.toLowerCase()));return i||r.englishLanguage}static getSupportedLanguageFromUserAgent(){const t=r.supportedLanguages.find((t=>t.code.toLowerCase()===(navigator.language||"").toLowerCase()));if(t)return t;if(navigator.languages){var i=navigator.languages.map((t=>r.supportedLanguages.find((i=>i.code===t)))).find((t=>!!t));if(i)return i}const e=navigator.language.indexOf("-");if(e>0){const t=navigator.language.substring(0,e),i=r.supportedLanguages.find((i=>i.code.toLowerCase()===t.toLowerCase()));if(i)return i}return r.englishLanguage}createIFrame(){const t=document.createElement("iframe");return t.setAttribute("loading","eager"),t.title="Microsoft Store App Badge",t.src=p(this,c,"f"),t}createImage(){const t=document.createElement("img");return t.setAttribute("part","img"),t.src=this.getImageSource(),t.className=this.getImageClass(),t.alt="Microsoft Store app badge","direct"===this.windowMode?t.addEventListener("click",this.imgPSIHandler):t.addEventListener("click",this.imgPDPHandler),t}getImageSource(){var t=null;if("dark"===this.theme)t=p(this,s,"f").imageLarge.fileName;else if("light"===this.theme)t=p(this,s,"f").imageLargeLight.fileName;else if("auto"===this.theme){t=window.matchMedia("(prefers-color-scheme:dark)").matches?p(this,s,"f").imageLargeLight.fileName:p(this,s,"f").imageLarge.fileName}return`${p(this,h,"f")}/${t}`}getImageClass(){return"large"===this.size?"large":"small"}launchApp(t){this.productId&&(p(this,d,"f").isWindows&&p(this,d,"f").isEdgeBrowser?this.launchStoreAppPdpViaWhitelistedDomain():p(this,d,"f").isWindows?this.launchFullStoreApp():this.launchStoreWebPdp(t))}getPSIUrl(){return`${this.PSIDownloadUrl}${this.productId.toUpperCase()}?referrer=appbadge&source=${encodeURIComponent(window.location.hostname.toLowerCase())}`}launchFullStoreApp(){const t=new URLSearchParams;t.append("productid",this.productId),t.append("referrer","appbadge"),t.append("source",window.location.hostname.toLowerCase()),this.cid&&t.append("cid",this.cid),location.href="ms-windows-store://pdp/?"+t.toString()}launchStoreAppPdpViaWhitelistedDomain(){var t;const i=null===(t=this.shadowRoot)||void 0===t?void 0:t.querySelector("iframe");if(!p(this,l,"f")&&i&&i.contentWindow){p(this,o,"m",u).call(this);const t={message:"launch",productId:this.productId,cid:this.cid,windowMode:this.windowMode,source:window.location.hostname};i.contentWindow.postMessage(t,"*")}else this.launchFullStoreApp()}launchStoreWebPdp(t){var i="";i=this.cid?`https://apps.microsoft.com/store/detail/${this.productId}?cid=${encodeURIComponent(this.cid)}&referrer=appbadge&source=${encodeURIComponent(window.location.hostname.toLowerCase())}`:`https://apps.microsoft.com/store/detail/${this.productId}?referrer=appbadge&source=${encodeURIComponent(window.location.hostname.toLowerCase())}`,t.ctrlKey?window.open(i,"_blank"):window.location.href=i}static createSupportedLanguages(){let t=new Map;t.set("Afrikaans","af"),t.set("Albanian","sq"),t.set("Amharic","am"),t.set("Arabic","ar"),t.set("Armenian","hy"),t.set("Assamese","as"),t.set("Azerbaijani","az"),t.set("Bengali","bn"),t.set("Bosnian","bs"),t.set("Bulgarian","bg"),t.set("Catalan","ca"),t.set("Chinese_Simplified","zh-cn"),t.set("Chinese_Traditional","zh-tw"),t.set("Croatian","hr"),t.set("Czech","cs"),t.set("Danish","da"),t.set("Dutch","nl"),t.set("English","en-us"),t.set("Estonian","et"),t.set("Filipino","fil"),t.set("Finnish","fi"),t.set("French","fr"),t.set("Galician","gl"),t.set("Georgian","ka"),t.set("German","de"),t.set("Greek","el"),t.set("Gujarati","gu"),t.set("Hebrew","he"),t.set("Hindi","hi"),t.set("Hungarian","hu"),t.set("Icelandic","is"),t.set("Indonesian","id"),t.set("Irish","ga"),t.set("Italian","it"),t.set("Japanese","ja"),t.set("Kannada","kn"),t.set("Kazakh","kk"),t.set("Khmer","km"),t.set("Konkani","kok"),t.set("Korean","ko"),t.set("Lao","lo"),t.set("Latvian","lv"),t.set("Lithuanian","lt"),t.set("Luxembourgish","lb"),t.set("Macedonian","mk"),t.set("Malay","ms"),t.set("Malayalam","ml"),t.set("Maltese","mt"),t.set("Māori","mi"),t.set("Marathi","mr"),t.set("Nepali","ne"),t.set("Norwegian","nn"),t.set("Oriya","or"),t.set("Persian","fa"),t.set("Polish","pl"),t.set("Portuguese_Brazil","pt-br"),t.set("Portuguese_Portugal","pt-pt"),t.set("Punjabi","pa"),t.set("Quechua","quz"),t.set("Romanian","ro"),t.set("Russian","ru"),t.set("Scottish_Gaelic","gd"),t.set("Serbian","sr"),t.set("Slovak","sk"),t.set("Slovenian","sl"),t.set("Spanish","es"),t.set("Swedish","sv"),t.set("Tamil","ta"),t.set("Telugu","te"),t.set("Thai","th"),t.set("Turkish","tr"),t.set("Uighur","ug"),t.set("Ukrainian","uk"),t.set("Urdu","ur"),t.set("Uzbek","uz"),t.set("Vietnamese","vi");let i=[];for(let e of t.keys()){let n={name:e,imageLarge:{fileName:t.get(e).concat(" ").concat("dark.svg")},imageLargeLight:{fileName:t.get(e).concat(" ").concat("light.svg")},code:t.get(e)||""};i.push(n)}return i}}r=g,s=new WeakMap,a=new WeakMap,c=new WeakMap,h=new WeakMap,d=new WeakMap,l=new WeakMap,o=new WeakSet,u=function(){const t="securitypolicyviolation",i=i=>{"frame-src"===i.violatedDirective&&i.type===t&&(m(this,l,!0,"f"),this.launchFullStoreApp())};document.addEventListener(t,i,{once:!0}),setTimeout((()=>document.removeEventListener(t,i)),2e3)},g.englishLanguage={name:"English",code:"en-us",imageLarge:{fileName:"en-us dark.svg"},imageLargeLight:{fileName:"en-us light.svg"}},g.supportedLanguages=r.createSupportedLanguages(),customElements.define("ms-store-badge",g); 2 | -------------------------------------------------------------------------------- /dist/staticwebapp.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "networking": { 3 | "allowedIpRanges": [ 4 | "AzureFrontDoor.Backend" 5 | ] 6 | }, 7 | "forwardingGateway": { 8 | "allowedForwardedHosts": [ 9 | "getbadgefd.azurefd.net", 10 | "getbadgefd-fve8angnggd6gwc7.b02.azurefd.net", 11 | "get.microsoft.com" 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /images/discord-psi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/app-store-badge/557cb234e412f802f6d8809769de626537214bf3/images/discord-psi.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@microsoft/store-app-badge", 3 | "private": true, 4 | "version": "0.6.0", 5 | "description": "Web component that renders a 'Get this app from the Microsoft Store' button. Detects the user's language and displays a localized app badge. If running in Edge, clicking the badge will skip the app launch prompt.", 6 | "main": "dist/ms-store-badge.bundled.js", 7 | "module": "dist/ms-store-badge.bundled.js", 8 | "type": "module", 9 | "scripts": { 10 | "build:prod": "concurrently \"tsc\" \"rollup --config rollup.config.js\"", 11 | "build:dev": "concurrently \"tsc --watch\" \"npm run serve\"", 12 | "build": "npm run build:prod", 13 | "serve": "wds --watch", 14 | "lint:eslint": "eslint 'src/**/*.ts'" 15 | }, 16 | "keywords": [ 17 | "web-components", 18 | "typescript" 19 | ], 20 | "author": "Microsoft", 21 | "license": "BSD-3-Clause", 22 | "dependencies": { 23 | "uuid": "^9.0.1", 24 | "wds": "^0.11.0", 25 | "webkit": "^0.0.0" 26 | }, 27 | "devDependencies": { 28 | "@rollup/plugin-replace": "^2.3.3", 29 | "@types/uuid": "^9.0.8", 30 | "@web/dev-server": "0.1.31", 31 | "@web/dev-server-rollup": "^0.3.17", 32 | "concurrently": "^6.2.2", 33 | "rollup": "^2.72.1", 34 | "rollup-plugin-copy": "^3.4.0", 35 | "rollup-plugin-summary": "^1.2.3", 36 | "rollup-plugin-terser": "^7.0.2", 37 | "typescript": "^4.9.5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /release.yml: -------------------------------------------------------------------------------- 1 | # This Yaml Document has been converted by ESAI Yaml Pipeline Conversion Tool. 2 | trigger: none 3 | name: $(Date:yyyyMMdd).$(Rev:r) 4 | resources: 5 | pipelines: 6 | - pipeline: '_StoreWeb-BadgeBuild' 7 | project: 'Universal Store' 8 | source: 'Store Web - Badge Build' 9 | repositories: 10 | - repository: 1ESPipelineTemplates 11 | type: git 12 | name: 1ESPipelineTemplates/1ESPipelineTemplates 13 | ref: refs/tags/release 14 | extends: 15 | template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates 16 | parameters: 17 | pool: 18 | name: StoreWebBuildPool-Centeral 19 | serviceTreeId: 74a6c3e5-c954-4157-b6b2-2a2eb1fbc922 20 | customBuildTags: 21 | - ES365AIMigrationTooling-Release-MOBR 22 | stages: 23 | - stage: Deploy_Static_Web_App 24 | displayName: Deploy Static Web App 25 | jobs: 26 | - job: DeployJob 27 | displayName: Deploy Static Web App Job 28 | condition: succeeded() 29 | timeoutInMinutes: 0 30 | templateContext: 31 | inputs: 32 | - input: pipelineArtifact 33 | buildType: specific 34 | project: Universal Store 35 | definition: 151610 36 | branchName: user/kendzisah/fix-deployment-to-swa 37 | artifactName: 'ms-store-badge-artifacts' 38 | targetPath: '$(System.ArtifactsDirectory)' 39 | steps: 40 | - task: NodeTool@0 41 | displayName: 'Install Node.js' 42 | inputs: 43 | versionSpec: '18.x' 44 | 45 | - script: | 46 | echo "Listing contents of artifact directory:" 47 | ls -R "$(System.ArtifactsDirectory)" 48 | displayName: 'List Artifact Contents' 49 | 50 | - script: | 51 | npm install -g @azure/static-web-apps-cli 52 | displayName: 'Install SWA CLI' 53 | 54 | - script: | 55 | swa deploy "$(System.ArtifactsDirectory)" --deployment-token $(DEPLOYMENT_TOKEN) 56 | displayName: Deploy to SWA using Deployment Token 57 | 58 | 59 | - stage: Deploy_To_PROD_SA 60 | displayName: Drop badge build to Prod SA 61 | trigger: manual 62 | templateContext: 63 | cloud: Public 64 | isProduction: true 65 | approval: 66 | workflow: lockbox 67 | scope: 68 | serviceGroupName: APS Store Web 69 | subscriptionIds: 70 | - 74a6c3e5-c954-4157-b6b2-2a2eb1fbc922 71 | jobs: 72 | - job: Job_2 73 | displayName: Agent job 74 | condition: succeeded() 75 | timeoutInMinutes: 0 76 | templateContext: 77 | type: releaseJob 78 | inputs: 79 | - input: pipelineArtifact 80 | buildType: specific 81 | definition: 151610 82 | project: Universal Store 83 | branchName: user/kendzisah/fix-deployment-to-swa 84 | artifactName: 'ms-store-badge-artifacts' 85 | targetPath: '$(System.ArtifactsDirectory)' 86 | workflow: m365-custom 87 | steps: 88 | - task: prepare-deployment@1 89 | inputs: 90 | taskType: credentialFetchTaskAzureRM 91 | armserviceconnection: Arm-Public-Placeholder-Endpoint 92 | subscriptionid: 74a6c3e5-c954-4157-b6b2-2a2eb1fbc922 93 | 94 | - task: AzureFileCopy@6 95 | displayName: ' File Copy Bundle' 96 | inputs: 97 | SourcePath: $(System.ArtifactsDirectory)/ms-store-badge.bundled.js 98 | ConnectedServiceNameARM: Arm-Public-Placeholder-Endpoint 99 | Destination: AzureBlob 100 | StorageAccountRM: sabadgedelivery 101 | ContainerName: $web 102 | 103 | - task: AzureFileCopy@6 104 | displayName: ' File Copy Images' 105 | inputs: 106 | SourcePath: $(System.ArtifactsDirectory)/images 107 | ConnectedServiceNameARM: Arm-Public-Placeholder-Endpoint 108 | Destination: AzureBlob 109 | StorageAccountRM: sabadgedelivery 110 | ContainerName: $web -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import summary from 'rollup-plugin-summary'; 2 | import { terser } from 'rollup-plugin-terser'; 3 | import resolve from '@rollup/plugin-node-resolve'; 4 | import replace from '@rollup/plugin-replace'; 5 | import copy from 'rollup-plugin-copy'; 6 | import fs from 'fs'; 7 | 8 | export default { 9 | input: 'src/ms-store-badge.js', 10 | output: { 11 | file: 'dist/ms-store-badge.bundled.js', 12 | format: 'esm', 13 | }, 14 | onwarn(warning) { 15 | if (warning.code !== 'THIS_IS_UNDEFINED') { 16 | console.error(`(!) ${warning.message}`); 17 | } 18 | }, 19 | plugins: [ 20 | replace({ 21 | 'Reflect.decorate': 'undefined', 22 | 23 | // Mark us as production 24 | 'window.__rollup_injected_env': '"prod"', 25 | 26 | delimiters: ['', ''], 27 | preventAssignment: true 28 | }), 29 | copy({ 30 | targets: [ 31 | { src: 'src/images', dest: 'dist' }, 32 | { src: 'src/staticwebapp.config.json', dest: 'dist' }, 33 | { src: 'src/iframe.html', dest: 'dist' }, 34 | { src: 'src/9p1j8s7ccwwt_us_en-us.html', dest: 'dist' }, 35 | { src: 'src/index.html', dest: 'dist' }, 36 | //{ src: 'dist/ms-store-badge.bundled.js', dest: 'dist/badge' }, // Copy to a badge subdirectory so that we can serve the badge script from get.microsoft.com/badge/ms-store-badge.bundled.js 37 | { 38 | src: 'src/index.html', 39 | dest: 'dist', 40 | transform: (contents) => contents.toString().replace(new RegExp('/ms-store-badge.js', 'g'), 'https://getbadgecdn.azureedge.net/ms-store-badge.bundled.js') 41 | } 42 | ] 43 | }), 44 | resolve(), 45 | terser({ 46 | ecma: 2020, 47 | module: true, 48 | warnings: true, 49 | mangle: { 50 | properties: { 51 | regex: /^__/, 52 | }, 53 | }, 54 | }), 55 | summary(), 56 | { 57 | name: 'copy-to-badge-dir', 58 | writeBundle() { 59 | const dir = 'dist/badge'; 60 | if (!fs.existsSync(dir)) { 61 | fs.mkdirSync(dir, { recursive: true }); 62 | } 63 | fs.copyFileSync('dist/ms-store-badge.bundled.js', 'dist/badge/ms-store-badge.bundled.js'); 64 | console.log('Successfully copied bundled file to badge directory'); 65 | } 66 | } 67 | ], 68 | }; 69 | -------------------------------------------------------------------------------- /src/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Microsoft Store Popup Badge 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/images/he dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/images/ko dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/images/mt dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | App badge tester 24 | 25 | 26 | 27 |
28 |

App Badge test page

29 | 30 |
31 |
32 |
33 | 34 |
35 | 36 | 41 | 46 | 47 | 48 |
49 | 50 |
51 | 52 | 57 | 58 | 59 |
60 | 61 |
62 | 63 | 143 |
144 | 145 |
146 | 147 | 152 |
153 | 154 |
155 | 156 | 161 |
162 | 163 |
164 | 165 | 169 |
170 | 171 |
172 | 175 | 176 | 181 |
182 |
183 |
184 | 185 |
186 |
188 | 189 | 190 |
191 |
192 |
193 |
194 | 195 | 196 | 199 | 200 | 201 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /src/ms-store-badge.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | //# sourceMappingURL=ms-store-badge.d.ts.map -------------------------------------------------------------------------------- /src/psi-service.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * PSI service class which helps perform the default product acquisition method by calling the service endpoint 3 | * and fetching an installer template for the user to receive back on the client-side and install the product. 4 | */ 5 | export declare function performPSIAcquisition(productId: string, productName: string, options: {} | undefined, url: string): Promise; 6 | //# sourceMappingURL=psi-service.d.ts.map -------------------------------------------------------------------------------- /src/psi-service.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"psi-service.d.ts","sourceRoot":"","sources":["psi-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,gBAAK,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkE5H"} -------------------------------------------------------------------------------- /src/psi-service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PSI service class which helps perform the default product acquisition method by calling the service endpoint 3 | * and fetching an installer template for the user to receive back on the client-side and install the product. 4 | */ 5 | // Perform PSI acquisition flow using download URL, send error if invalid response 6 | export async function performPSIAcquisition(productId, productName, options = {}, url) { 7 | const psiServiceFailedOcidPrefix = "psi_f_svc"; 8 | // Set up request 9 | const defaultOptions = { 10 | method: "GET", 11 | headers: { 12 | // Origin and referer overwritten by browser 13 | "Origin": "https://apps.microsoft.com", 14 | "Referer": document.URL, 15 | "Access-Control-Request-Method": "GET", 16 | "X-Correlation-Id": crypto.randomUUID(), 17 | "Content-Type": "application/octet-stream" 18 | }, 19 | cache: "no-cache", 20 | params: new URLSearchParams(url) 21 | }; 22 | const mergedOptions = { ...defaultOptions, ...options }; 23 | // Attempt to fetch response from url 24 | let response = null; 25 | try { 26 | response = await fetch(`${url}`, mergedOptions); 27 | } 28 | catch (error) { 29 | window.location.href = `ms-windows-store://pdp/?productid=${productId}&ocid=${psiServiceFailedOcidPrefix}na`; // Redirect to fallback mode 30 | } 31 | // Parse response 32 | if (response === null || response === void 0 ? void 0 : response.ok) { 33 | getDownload(response); // If response ok, get blob installer template 34 | } 35 | else { 36 | onResponseError(response); // If response not ok, log error and redirect 37 | } 38 | // Helper method to retrieve installer template from response 39 | async function getDownload(response) { 40 | var _a; 41 | // Grab file name from custom header. Otherwise, use fallback title. 42 | const psiFileName = (_a = response.headers.get("X-PSI-FileName")) !== null && _a !== void 0 ? _a : `${productName} Installer.exe`; 43 | const data = await response.blob(); 44 | const fileURL = URL.createObjectURL(data); 45 | // Create a hidden link element and initiate the download 46 | const link = document.createElement("a"); 47 | link.href = fileURL; 48 | link.download = decodeURIComponent(psiFileName); 49 | link.style.display = "none"; 50 | document.body.appendChild(link); 51 | try { 52 | // Initiate the download 53 | link.click(); 54 | // Wait for the download to complete 55 | await new Promise(resolve => setTimeout(resolve, 1000)); 56 | } 57 | catch (error) { 58 | } 59 | finally { 60 | // Safely clean up the object URL and the link element 61 | URL.revokeObjectURL(fileURL); 62 | document.body.removeChild(link); 63 | } 64 | } 65 | // Helper method to log error from response 66 | async function onResponseError(response) { 67 | const status = response === null || response === void 0 ? void 0 : response.status; 68 | window.location.href = `ms-windows-store://pdp/?productid=${productId}&ocid=${psiServiceFailedOcidPrefix}${status}`; // Fallback to full PDP 69 | } 70 | } 71 | //# sourceMappingURL=psi-service.js.map -------------------------------------------------------------------------------- /src/psi-service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"psi-service.js","sourceRoot":"","sources":["psi-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,SAAiB,EAAE,WAAmB,EAAE,OAAO,GAAG,EAAE,EAAE,GAAW;IACzG,MAAM,0BAA0B,GAAG,WAAW,CAAC;IAC/C,iBAAiB;IACjB,MAAM,cAAc,GAAG;QACnB,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACL,4CAA4C;YAC5C,QAAQ,EAAE,4BAA4B;YACtC,SAAS,EAAE,QAAQ,CAAC,GAAG;YACvB,+BAA+B,EAAE,KAAK;YACtC,kBAAkB,EAAE,MAAM,CAAC,UAAU,EAAE;YACvC,cAAc,EAAE,0BAA0B;SAC7C;QACD,KAAK,EAAE,UAA0B;QACjC,MAAM,EAAE,IAAI,eAAe,CAAC,GAAG,CAAC;KACnC,CAAC;IACF,MAAM,aAAa,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,CAAC;IAExD,qCAAqC;IACrC,IAAI,QAAQ,GAAoB,IAAI,CAAC;IACrC,IAAI;QACA,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,GAAG,EAAE,EAAE,aAAa,CAAC,CAAC;KACnD;IAAC,OAAO,KAAK,EAAE;QACZ,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,qCAAqC,SAAS,SAAS,0BAA0B,IAAI,CAAC,CAAC,4BAA4B;KAC7I;IAED,iBAAiB;IACjB,IAAI,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,EAAE,EAAE;QACd,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,8CAA8C;KACxE;SAAM;QACH,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,6CAA6C;KAC3E;IAED,6DAA6D;IAC7D,KAAK,UAAU,WAAW,CAAC,QAAkB;;QACzC,oEAAoE;QACpE,MAAM,WAAW,GAAG,MAAA,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,mCAAI,GAAG,WAAW,gBAAgB,CAAC;QAE7F,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAE1C,yDAAyD;QACzD,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAEhC,IAAI;YACA,wBAAwB;YACxB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,oCAAoC;YACpC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;SAC3D;QAAC,OAAO,KAAK,EAAE;SACf;gBAAS;YACN,sDAAsD;YACtD,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;SACnC;IACL,CAAC;IAED,2CAA2C;IAC3C,KAAK,UAAU,eAAe,CAAC,QAAyB;QACpD,MAAM,MAAM,GAAG,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,MAAM,CAAC;QAChC,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,qCAAqC,SAAS,SAAS,0BAA0B,GAAG,MAAM,EAAE,CAAC,CAAC,uBAAuB;IAChJ,CAAC;AACL,CAAC","sourcesContent":["/**\r\n * PSI service class which helps perform the default product acquisition method by calling the service endpoint \r\n * and fetching an installer template for the user to receive back on the client-side and install the product.\r\n */\r\n// Perform PSI acquisition flow using download URL, send error if invalid response\r\nexport async function performPSIAcquisition(productId: string, productName: string, options = {}, url: string): Promise {\r\n const psiServiceFailedOcidPrefix = \"psi_f_svc\";\r\n // Set up request\r\n const defaultOptions = {\r\n method: \"GET\",\r\n headers: {\r\n // Origin and referer overwritten by browser\r\n \"Origin\": \"https://apps.microsoft.com\",\r\n \"Referer\": document.URL,\r\n \"Access-Control-Request-Method\": \"GET\",\r\n \"X-Correlation-Id\": crypto.randomUUID(),\r\n \"Content-Type\": \"application/octet-stream\"\r\n },\r\n cache: \"no-cache\" as RequestCache, // Don't cache locally, need to request new template from server\r\n params: new URLSearchParams(url)\r\n };\r\n const mergedOptions = { ...defaultOptions, ...options };\r\n\r\n // Attempt to fetch response from url\r\n let response: Response | null = null;\r\n try {\r\n response = await fetch(`${url}`, mergedOptions);\r\n } catch (error) {\r\n window.location.href = `ms-windows-store://pdp/?productid=${productId}&ocid=${psiServiceFailedOcidPrefix}na`; // Redirect to fallback mode\r\n }\r\n\r\n // Parse response\r\n if (response?.ok) {\r\n getDownload(response); // If response ok, get blob installer template\r\n } else {\r\n onResponseError(response); // If response not ok, log error and redirect\r\n }\r\n\r\n // Helper method to retrieve installer template from response\r\n async function getDownload(response: Response) {\r\n // Grab file name from custom header. Otherwise, use fallback title.\r\n const psiFileName = response.headers.get(\"X-PSI-FileName\") ?? `${productName} Installer.exe`;\r\n\r\n const data = await response.blob();\r\n const fileURL = URL.createObjectURL(data);\r\n\r\n // Create a hidden link element and initiate the download\r\n const link = document.createElement(\"a\");\r\n link.href = fileURL;\r\n link.download = decodeURIComponent(psiFileName);\r\n link.style.display = \"none\";\r\n document.body.appendChild(link);\r\n\r\n try {\r\n // Initiate the download\r\n link.click();\r\n // Wait for the download to complete\r\n await new Promise(resolve => setTimeout(resolve, 1000));\r\n } catch (error) {\r\n } finally {\r\n // Safely clean up the object URL and the link element\r\n URL.revokeObjectURL(fileURL);\r\n document.body.removeChild(link);\r\n }\r\n }\r\n\r\n // Helper method to log error from response\r\n async function onResponseError(response: Response | null) {\r\n const status = response?.status;\r\n window.location.href = `ms-windows-store://pdp/?productid=${productId}&ocid=${psiServiceFailedOcidPrefix}${status}`; // Fallback to full PDP\r\n }\r\n}"]} -------------------------------------------------------------------------------- /src/psi-service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * PSI service class which helps perform the default product acquisition method by calling the service endpoint 3 | * and fetching an installer template for the user to receive back on the client-side and install the product. 4 | */ 5 | // Perform PSI acquisition flow using download URL, send error if invalid response 6 | export async function performPSIAcquisition(productId: string, productName: string, options = {}, url: string): Promise { 7 | const psiServiceFailedOcidPrefix = "psi_f_svc"; 8 | // Set up request 9 | const defaultOptions = { 10 | method: "GET", 11 | headers: { 12 | // Origin and referer overwritten by browser 13 | "Origin": "https://apps.microsoft.com", 14 | "Referer": document.URL, 15 | "Access-Control-Request-Method": "GET", 16 | "X-Correlation-Id": crypto.randomUUID(), 17 | "Content-Type": "application/octet-stream" 18 | }, 19 | cache: "no-cache" as RequestCache, // Don't cache locally, need to request new template from server 20 | params: new URLSearchParams(url) 21 | }; 22 | const mergedOptions = { ...defaultOptions, ...options }; 23 | 24 | // Attempt to fetch response from url 25 | let response: Response | null = null; 26 | try { 27 | response = await fetch(`${url}`, mergedOptions); 28 | } catch (error) { 29 | window.location.href = `ms-windows-store://pdp/?productid=${productId}&ocid=${psiServiceFailedOcidPrefix}na`; // Redirect to fallback mode 30 | } 31 | 32 | // Parse response 33 | if (response?.ok) { 34 | getDownload(response); // If response ok, get blob installer template 35 | } else { 36 | onResponseError(response); // If response not ok, log error and redirect 37 | } 38 | 39 | // Helper method to retrieve installer template from response 40 | async function getDownload(response: Response) { 41 | // Grab file name from custom header. Otherwise, use fallback title. 42 | const psiFileName = response.headers.get("X-PSI-FileName") ?? `${productName} Installer.exe`; 43 | 44 | const data = await response.blob(); 45 | const fileURL = URL.createObjectURL(data); 46 | 47 | // Create a hidden link element and initiate the download 48 | const link = document.createElement("a"); 49 | link.href = fileURL; 50 | link.download = decodeURIComponent(psiFileName); 51 | link.style.display = "none"; 52 | document.body.appendChild(link); 53 | 54 | try { 55 | // Initiate the download 56 | link.click(); 57 | // Wait for the download to complete 58 | await new Promise(resolve => setTimeout(resolve, 1000)); 59 | } catch (error) { 60 | } finally { 61 | // Safely clean up the object URL and the link element 62 | URL.revokeObjectURL(fileURL); 63 | document.body.removeChild(link); 64 | } 65 | } 66 | 67 | // Helper method to log error from response 68 | async function onResponseError(response: Response | null) { 69 | const status = response?.status; 70 | window.location.href = `ms-windows-store://pdp/?productid=${productId}&ocid=${psiServiceFailedOcidPrefix}${status}`; // Fallback to full PDP 71 | } 72 | } -------------------------------------------------------------------------------- /src/staticwebapp.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "networking": { 3 | "allowedIpRanges": [ 4 | "AzureFrontDoor.Backend" 5 | ] 6 | }, 7 | "forwardingGateway": { 8 | "allowedForwardedHosts": [ 9 | "getbadgefd.azurefd.net", 10 | "getbadgefd-fve8angnggd6gwc7.b02.azurefd.net", 11 | "get.microsoft.com" 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /src/throttle-async.d.ts: -------------------------------------------------------------------------------- 1 | export declare function throttle(func: (...args: T) => Promise, limit: number): (...args: T) => void; 2 | //# sourceMappingURL=throttle-async.d.ts.map -------------------------------------------------------------------------------- /src/throttle-async.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"throttle-async.d.ts","sourceRoot":"","sources":["throttle-async.ts"],"names":[],"mappings":"AAEA,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,CAWtH"} -------------------------------------------------------------------------------- /src/throttle-async.js: -------------------------------------------------------------------------------- 1 | // Helper function to throttle async tasks and avoid repeated requests 2 | let inThrottle = false; 3 | export function throttle(func, limit) { 4 | return async (...args) => { 5 | if (!inThrottle) { 6 | inThrottle = true; 7 | try { 8 | await func(...args); 9 | } 10 | finally { 11 | setTimeout(() => inThrottle = false, limit); 12 | } 13 | } 14 | }; 15 | } 16 | //# sourceMappingURL=throttle-async.js.map -------------------------------------------------------------------------------- /src/throttle-async.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"throttle-async.js","sourceRoot":"","sources":["throttle-async.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,IAAI,UAAU,GAAG,KAAK,CAAC;AACvB,MAAM,UAAU,QAAQ,CAAsB,IAAmC,EAAE,KAAa;IAC5F,OAAO,KAAK,EAAE,GAAG,IAAO,EAAE,EAAE;QACxB,IAAI,CAAC,UAAU,EAAE;YACb,UAAU,GAAG,IAAI,CAAC;YAClB,IAAI;gBACA,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;aACvB;oBAAS;gBACN,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,GAAG,KAAK,EAAE,KAAK,CAAC,CAAC;aAC/C;SACJ;IACL,CAAC,CAAC;AACN,CAAC","sourcesContent":["// Helper function to throttle async tasks and avoid repeated requests\r\nlet inThrottle = false;\r\nexport function throttle(func: (...args: T) => Promise, limit: number): (...args: T) => void {\r\n return async (...args: T) => {\r\n if (!inThrottle) {\r\n inThrottle = true;\r\n try {\r\n await func(...args);\r\n } finally {\r\n setTimeout(() => inThrottle = false, limit);\r\n }\r\n }\r\n };\r\n}"]} -------------------------------------------------------------------------------- /src/throttle-async.ts: -------------------------------------------------------------------------------- 1 | // Helper function to throttle async tasks and avoid repeated requests 2 | let inThrottle = false; 3 | export function throttle(func: (...args: T) => Promise, limit: number): (...args: T) => void { 4 | return async (...args: T) => { 5 | if (!inThrottle) { 6 | inThrottle = true; 7 | try { 8 | await func(...args); 9 | } finally { 10 | setTimeout(() => inThrottle = false, limit); 11 | } 12 | } 13 | }; 14 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "module": "es2020", 5 | "lib": ["es2020", "DOM", "DOM.Iterable"], 6 | "declaration": true, 7 | "declarationMap": true, 8 | "sourceMap": true, 9 | "inlineSources": true, 10 | "outDir": "./src", 11 | "rootDir": "./src", 12 | "strict": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitAny": true, 18 | "noImplicitThis": true, 19 | "moduleResolution": "node", 20 | "allowSyntheticDefaultImports": true, 21 | "experimentalDecorators": true, 22 | "forceConsistentCasingInFileNames": true 23 | }, 24 | "include": ["src/**/*.ts"], 25 | "exclude": [] 26 | } 27 | -------------------------------------------------------------------------------- /web-dev-server.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2021 Google LLC 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | export default { 8 | nodeResolve: true, 9 | preserveSymlinks: true, 10 | rootDir: "src", 11 | appIndex: "src/index.html" 12 | }; --------------------------------------------------------------------------------