Thursday, 1 January 2026

Flutter

dart run flutter_launcher_icons
flutter build apk --release 
adb install build/app/outputs/flutter-apk/app-release.apk

Tuesday, 30 December 2025

Creating a Shopify Web pixel

shopify app init

shopify app generate extension --template=web_pixel --name=my-webpixel

shopify app deploy

shopify app release

Once the app is installed on the store, it will be shown in Customer Events but disconnected.
You must run GraphQL to install it, USING THE CREDENTIALS FOR THE APP.



string shopDomain = "mystore.myshopify.com";
string apiVersion = "2025-10";                                      // use a current version
string clientId = "myclientId";										// In the Dev Dashboard
string clientSecret = "myClientSecret";

// gid://shopify/WebPixel/3094610295

string query = @"
mutation CreateWebPixel($settings: JSON!) {
  webPixelCreate(webPixel: { settings: $settings }) {
    userErrors { code field message }
    webPixel { id settings }
  }
}";

public async Task Main()
{
	var variables = new { settings = "{\"accountID\":\"123\"}" };
	var payload = new { query, variables };
	var json = JsonSerializer.Serialize(payload);

	try
	{
		var cache = new MemoryCache(new MemoryCacheOptions());
		var services = new ServiceCollection()
					.AddHttpClient()                    // registers IHttpClientFactory and the default plumbing
					.BuildServiceProvider();

		var factory = services.GetRequiredService<IHttpClientFactory>();

		var client = factory.CreateClient("shopify");
		var tokenService = new ShopifyTokenService(client, cache, shopDomain, clientId, clientSecret);

		client.BaseAddress = new Uri($"https://{shopDomain}/admin/api/2025-04/graphql.json");
		client.DefaultRequestHeaders.Add("X-Shopify-Access-Token", await tokenService.GetTokenAsync());
		client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
		var content = new StringContent(json, Encoding.UTF8, "application/json");

		var response = await client.PostAsync("", content);
		var responseString = await response.Content.ReadAsStringAsync();
		Console.WriteLine(response.StatusCode);
		Console.WriteLine(responseString);
	}
	catch (Exception error)
	{
		while (error != null)
		{
			Console.WriteLine(error.Message);
			error = error.InnerException;
		}
	}
}

public sealed class ShopifyTokenService
{
	private readonly HttpClient httpClient;
	private readonly IMemoryCache cache;
	private readonly string shopDomain;
	private readonly string clientId;
	private readonly string clientSecret;
	private readonly int fallbackTtl = 30;
	private static readonly string cacheKey = "shopify_admin_token";

	public ShopifyTokenService(HttpClient httpClient, IMemoryCache cache, string shopDomain, string clientId, string clientSecret)
	{
		this.httpClient = httpClient;
		this.cache = cache;
		this.shopDomain = shopDomain;
		this.clientId = clientId;
		this.clientSecret = clientSecret;
	}

	public async Task<string> GetTokenAsync()
	{
		if (cache.TryGetValue(cacheKey, out string token))
			return token;

		var endpoint = $"https://{shopDomain}/admin/oauth/access_token";
		var body = new { grant_type = "client_credentials", client_id = clientId, client_secret = clientSecret };

		Debug.WriteLine("Requesting a Shopify access token");
		var resp = await httpClient.PostAsync(endpoint,
			new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"));

		var text = await resp.Content.ReadAsStringAsync();
		resp.EnsureSuccessStatusCode();

		var parsed = JsonSerializer.Deserialize<TokenResponse>(text, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
					?? throw new InvalidOperationException("Token parse error.");

		TimeSpan ttl = TimeSpan.FromMinutes(fallbackTtl);
		if (parsed.ExpiresInSeconds.HasValue)
		{
			ttl = TimeSpan.FromSeconds(parsed.ExpiresInSeconds.Value - 30);
		}

		Debug.WriteLine($"Access token received with a TTL of {ttl.TotalSeconds:N0} seconds ({ttl.TotalMinutes:0} minutes or {ttl.TotalHours:0} hours)");
		cache.Set(cacheKey, parsed.AccessToken, ttl);
		return parsed.AccessToken;
	}

	private sealed class TokenResponse
	{
		[JsonPropertyName("access_token")]
		public string AccessToken { get; set; } = "";
		[JsonPropertyName("expires_in")]
		public int? ExpiresInSeconds { get; set; }
		[JsonPropertyName("scope")]
		public string Scope { get; set; }
	}
}

Friday, 7 November 2025

Chrome profile directory

 C:\Users\<username>\AppData\Local\Google\Chrome\User Data

Sunday, 12 October 2025

Windows 11: slow loading to desktop after login prompt

 On some of my computers Windows 11 was taking a long time to show the desktop after logging in with your user credentials.

What happens is you get a build up of data in %userprofile%\AppData\Local\Temp.

Pre-cleansing, one of mine was 35.1GB. It was full of folders named with GUIDs.

Deleting this significantly speeds up the desktop load.

Monday, 21 April 2025

Using RSync on OpenMediaVault

 Attach the backup disk.

Confirm the device name and serial from Storage > Disks.

Mount an existing file system. Select the correct mount point (e.g. /dev/sdb2).

Apply the changes.

Create a shared folder:
Name: Data2
File system: /dev/sdb2
Relative Path: Data/

Create an Rsyn Task: Services > Rsync > Tasks

Type: Local
Source: Data
Destination: Data2
Delete: <optional>

The mount point can be accessed from a shell terminal at:
/srv/dev-disk-by-uuid-<uuid>

to query the number of files enter:
find . -ls | wc -l



Monday, 3 March 2025

Enterprise Applications vs App Registrations


Enterprise Application

Enterprise application is also known as a Service Principal.

It is shown as a blue grid with a green world icon in the corner. In the search box it is shown as a Service Principal, but if you click through to the resource, it is an Enterprise Application. 

Clicking on the Service Principal (the bottom one above), it shows you the Enterprise Application resource.


App Registration

If you click on the Application entry in the search box

It takes you to the App Registration.


It is the Application Registration client secret that the application uses to prove its identity when requesting a token. Also can be referred to as application password. This needs periodic renewal.

Additional Information



https://stackoverflow.com/questions/65922566/what-are-the-differences-between-service-principal-and-app-registration


Monday, 27 January 2025

npmrc locations

On a Windows machine the .npmrc configuration is loaded from the following locations in sequence.

%USERPROFILE%\AppData\Roaming\npm\node_modules\npm\npmrc

<project directory>\.npmrc

%USERPROFILE%\.npmrc

%USERPROFILE%\AppData\Roaming\npm\etc\npmrc