I acually found some people at my company that had made a similar function and together with them I was able to find a somewhat satisfying solution. If anyone else in the future want to do the same thing as this I though I would share how I've done this.
Question 1: Is Add In Part shared or personal
I could not find any property on an AddInPart for if it's shared or created by the user so I solved this by simply loading all shared AddInParts and stored thier GUIDs in an array. By doing so I could check whether the AddInPart i wanted to remove was in that array or not and in that case take the correct action. This is how I did it:
var sharedWebPartGuids = new Array(); // An array containing guids from all shared webparts function fetchSharedWebparts() { //get the client context var clientContext = new SP.ClientContext(_spPageContextInfo.webServerRelativeUrl); //get the current page as a file var oFile = clientContext.get_web().getFileByServerRelativeUrl(_spPageContextInfo.serverRequestPath); // Fetch all webparts that are shared var limitedWebPartManagerShared = oFile.getLimitedWebPartManager(SP.WebParts.PersonalizationScope.shared); //get the web parts on the current page var collWebPart = limitedWebPartManagerUser.get_webParts(); //request the web part collection and load it from the server clientContext.load(collWebPart); clientContext.executeQueryAsync(Function.createDelegate(this, function() { // Go through all webparts for (var x = 0; x < collWebPart.get_count(); x++) { var webPartDef = collWebPart.get_item(x); sharedWebPartGuids.push(webPartDef.get_id().toString()); } }), Function.createDelegate(this, function() { alert("failed to fetch shared webparts from page") })); }
Question 2: Get ClientWebPart (AppPart) xml
Unfortunetley I was not able to do this without manually providing the ProductID (a GUID) for each App that has an AppPart that should be able to be added. For this I deploy a list called AvailableAppParts which contains two columns; Title and ProductId. I then match the items in the list with the installed Apps by comparing Title. The GUID can be found in AppManifest.xml in the App or adding the AppPart to a page and then export it. I also had to include a xml template in my code and replace the unique values. Since everything is done async I have a counter called initMethods wich I add to for each async method and then at the end of them call initMethodDone() which checks if all async methods are done before I show anything to the user. This is how I did it:
var webpartsFromGallery = new Array(); // An array where I store an object for each AppPart // The xml template: var appPartXml = '<webParts><webPart xmlns="http://schemas.microsoft.com/WebPart/v3"><metaData><type name="Microsoft.SharePoint.WebPartPages.ClientWebPart, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" /><importErrorMessage>Cannot import this Web Part.</importErrorMessage> </metaData><data><properties><property name="TitleIconImageUrl" type="string" /><property name="Direction" type="direction">NotSet</property><property name="ExportMode" type="exportmode">NonSensitiveData</property><property name="HelpUrl" type="string" /><property name="Hidden" type="bool">False</property><property name="Description" type="string">AppPart Description</property><property name="FeatureId" type="System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">{FeatureId}</property><property name="Title" type="string">{Title}</property><property name="AllowHide" type="bool">True</property><property name="ProductWebId" type="System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">{ProductWebId}</property><property name="AllowZoneChange" type="bool">True</property><property name="TitleUrl" type="string" /><property name="ChromeType" type="chrometype">Default</property><property name="AllowConnect" type="bool">True</property><property name="Width" type="unit" /><property name="Height" type="unit" /><property name="WebPartName" type="string">AppPart</property><property name="HelpMode" type="helpmode">Navigate</property><property name="AllowEdit" type="bool">True</property><property name="AllowMinimize" type="bool">True</property><property name="ProductId" type="System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">{ProductId}</property><property name="AllowClose" type="bool">True</property><property name="ChromeState" type="chromestate">Normal</property></properties></data></webPart></webParts>'; function fetchAppPartsFromGallery() { initMethods++; var ctx = SP.ClientContext.get_current(); var web = ctx.get_web(); // Fetch all installed Apps on this site (SPWeb) var appInstances = SP.AppCatalog.getAppInstances(ctx, web); ctx.load(appInstances); ctx.executeQueryAsync( function() { if (appInstances.get_count() == 0) { // No apps found, stop here return; } // Iterate through the app instances and store them in a map // Also create a filter string var map = new Object(); var filterString = ""; for (var i = 0; i < appInstances.get_count(); i++) { var instance = appInstances.getItemAtIndex(i); var title = instance.get_title(); map[title] = instance; // Append filter if (filterString.length > 0) { filterString += "or"; } filterString += "(Title+eq+'" + title + "')"; } if (filterString.length > 0) { filterString = "?$filter=" + filterString; } // Fetch available apps from my list $.ajax({ url: siteApiUrl + "/web/lists/getbytitle('AvailableAppParts')/items" + filterString, type: "GET", headers: { "accept": "application/json;odata=verbose", }, success: function(data) { // Go through all returned apps and build the xml for each one var results = data.d.results; for (var i = 0; i < results.length; i++) { var listItem = results[i]; var title = listItem.Title; var productId = listItem.ProductId; var appInstance = map[title]; if (!appInstance) { console.log("Could not find app with title '" + title + "'"); continue; } var instanceId = appInstance.get_id(); var webId = appInstance.get_webId(); var featureId = calculateFeatureIdFromProductId(productId); // This is the values we have to replace with our unique ones var xml = appPartXml.replace(/{Title}/g, title); xml = xml.replace(/{ProductId}/g, productId); xml = xml.replace(/{ProductWebId}/g, webId); xml = xml.replace(/{FeatureId}/g, featureId); webpartsFromGallery.push({ id: instanceId, title: title, xml: xml }); } function calculateFeatureIdFromProductId(productID) { // Calculate featureId since it is always one hexadecimal value higher than productId var lastIndex = productID.lastIndexOf('-'); var lastNumber = productID.substring(lastIndex + 12); // We are using hexadecimal numbers therefor using parseInt(..., 16) and toString(16) var increment = parseInt(lastNumber, 16) + 1; var incrementHex = increment.toString(16); var featureID = productID.substring(0, lastIndex + 12) + incrementHex; return featureID; } initMethodDone(); }, error: function(error) { alert(JSON.stringify(error)); initMethodDone(); } }); }, function(sender, args) { console.log(JSON.stringify(args)); initMethodDone(); }); }