Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .husky/post-checkout
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-checkout' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs post-checkout "$@"
command -v git-lfs >/dev/null 2>&1 || { printf >&2 "\n%s\n\n" "This repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-checkout' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks')."; exit 2; }
git lfs post-checkout "$@"
4 changes: 2 additions & 2 deletions .husky/post-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-commit' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs post-commit "$@"
command -v git-lfs >/dev/null 2>&1 || { printf >&2 "\n%s\n\n" "This repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-commit' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks')."; exit 2; }
git lfs post-commit "$@"
4 changes: 2 additions & 2 deletions .husky/post-merge
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-merge' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs post-merge "$@"
command -v git-lfs >/dev/null 2>&1 || { printf >&2 "\n%s\n\n" "This repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-merge' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks')."; exit 2; }
git lfs post-merge "$@"
4 changes: 2 additions & 2 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs pre-push "$@"
command -v git-lfs >/dev/null 2>&1 || { printf >&2 "\n%s\n\n" "This repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks')."; exit 2; }
git lfs pre-push "$@"
6 changes: 3 additions & 3 deletions e2e/c2pa-migration-test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ test.describe('c2pa-web SDK migration — trust badge rendering', () => {

await page.goto(`/?source=${encodeURIComponent(`${FIXTURES_BASE}/CAICAI.jpg`)}`);

await expect(page.getByText('Content Credentials', { exact: false })).toBeVisible({
await expect(page.getByText('Content Credentials', { exact: true })).toBeVisible({
timeout: 20000,
});

Expand All @@ -63,7 +63,7 @@ test.describe('c2pa-web SDK migration — trust badge rendering', () => {

await page.goto(`/?source=${encodeURIComponent(`${FIXTURES_BASE}/CAICAI.jpg`)}`);

await expect(page.getByText('Content Credentials', { exact: false })).toBeVisible({
await expect(page.getByText('Content Credentials', { exact: true })).toBeVisible({
timeout: 20000,
});

Expand All @@ -90,7 +90,7 @@ test.describe('c2pa-web SDK migration — trust badge rendering', () => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await page.setInputFiles('input[type="file"]', legacyImagePath!);

await expect(page.getByText('Content Credentials', { exact: false })).toBeVisible({
await expect(page.getByText('Content Credentials', { exact: true })).toBeVisible({
timeout: 20000,
});

Expand Down
14 changes: 14 additions & 0 deletions e2e/fixtures/cors_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import sys
import socketserver
from http.server import SimpleHTTPRequestHandler

class CORS(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
super().end_headers()

if __name__ == '__main__':
port = int(sys.argv[1])
socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(('127.0.0.1', port), CORS) as httpd:
httpd.serve_forever()
9 changes: 6 additions & 3 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import testImageConfig from './e2e/c2pa-test-image-service.config';


export const port = parseInt(
(process.env.HOST_PORT as string | undefined) ?? '4173',
10,
Expand All @@ -16,6 +17,7 @@ const baseURL = `${base ?? `http://localhost`}:${port}/`;

const config: PlaywrightTestConfig = {
testDir: 'e2e',
testIgnore: '**/snapshot/**',
retries: process.env.CI ? 1 : 0,
forbidOnly: !!process.env.CI,
use: {
Expand All @@ -28,17 +30,18 @@ const config: PlaywrightTestConfig = {
},
webServer: [
{
command: `pnpm dev --port=${port}`,
command: `npx vite preview --port=${port}`,
port,
reuseExistingServer: !process.env.CI,
},
{
command: `pnpm http-server e2e/fixtures --port=${fixturesPort} --cors --gzip`,
command: `python3 cors_server.py ${fixturesPort}`,
cwd: 'e2e/fixtures',
port: fixturesPort,
reuseExistingServer: !process.env.CI,
},
{
command: `pnpm run test-image-service`,
command: `./node_modules/.bin/c2pa-test-image-service --config e2e/c2pa-test-image-service.config.ts`,
port: testImageConfig.port,
reuseExistingServer: !process.env.CI,
},
Expand Down
27 changes: 0 additions & 27 deletions src/components/SocialMediaInfo/SocialMediaInfo.svelte

This file was deleted.

89 changes: 48 additions & 41 deletions src/lib/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@ import type {
Manifest,
ManifestStore,
ResourceRef as Thumbnail,
StatusCodes,
} from '@contentauth/c2pa-web';
import { type ValidationStatus } from './selectors/validationResult';

interface ExtendedIngredient extends Ingredient {
activeManifest?: string;
validationStatus?: ValidationStatus[];
validationResults?: { activeManifest?: StatusCodes };
trustSource?: string;
}
import { selectDoNotTrain } from './selectors/doNotTrain';
import { selectEditsAndActivity, type TranslatedDictionaryCategory } from './selectors/editsAndActivity';
import { selectProducer } from './selectors/producer';
import { selectSocialAccounts } from './selectors/socialAccounts';

import debug from 'debug';
import { selectExif } from './exif';
import {
Expand Down Expand Up @@ -75,11 +83,9 @@ export type ManifestData = {
exif: ReturnType<typeof selectExif>;
label: string | null;
generativeInfo: GenerativeInfo | null;
producer: string | null;
reviewRatings: ReturnType<typeof selectReviewRatings>;
signatureInfo: Manifest['signature_info'];
doNotTrain: ReturnType<typeof selectDoNotTrain>;
socialAccounts: ReturnType<typeof selectSocialAccounts>;
web3Accounts: [string, string[]][];
website: string | null;
autoDubInfo: AutoDubInfo | null;
Expand Down Expand Up @@ -134,6 +140,7 @@ export async function resultToAssetMap({
const disposers: (() => void)[] = [];

const activeManifestLabel = manifestStore?.active_manifest ?? '';

const allLabels = Object.keys(manifestStore?.manifests ?? {});
const runtimeValidationStatuses = manifestStore?.validation_status
? validationStatusByManifestLabel(
Expand All @@ -146,7 +153,7 @@ export async function resultToAssetMap({
dbg('Runtime validation statuses by manifest label', runtimeValidationStatuses);

const activeManifestValidationResults =
manifestStore?.validation_results?.activeManifest ?? undefined;
manifestStore?.validation_results?.activeManifest || undefined;

const rootValidationStatuses =
runtimeValidationStatuses[activeManifestLabel] ?? [];
Expand Down Expand Up @@ -260,7 +267,7 @@ export async function resultToAssetMap({
manifestData: await getManifestData(manifest, rootValidationResult),
dataType: null,
validationResult: rootValidationResult,
trustSource: ((manifest as Manifest & { trust_source?: 'legacy' | 'none' | 'official' }).trust_source || 'none') as 'legacy' | 'none' | 'official',
trustSource: ((manifest as Manifest & { trust_source?: string }).trust_source || 'none') as 'legacy' | 'none' | 'official',
};

if (thumbnail?.dispose) {
Expand All @@ -278,20 +285,23 @@ export async function resultToAssetMap({
runtimeValidationStatuses: ManifestLabelValidationStatusMap,
id: string,
): Promise<AssetData> {
const ingredientManifestLabel = ingredient.active_manifest;
const ingredientManifestLabel = ingredient.active_manifest || (ingredient as ExtendedIngredient).activeManifest;
const ingredientManifest = ingredientManifestLabel ? manifestStore.manifests?.[ingredientManifestLabel] : null;



// 0.17.x SDK dropped internal thumbnail generation. Skip WASM fetch for ingredients.
const thumbnail = await loadThumbnail(
ingredient.thumbnail?.format,
undefined,
);

const activeManifestValidationResults =
ingredient.validation_results?.activeManifest ?? undefined;
(ingredient.validation_results?.activeManifest || (ingredient as ExtendedIngredient).validationResults?.activeManifest) ?? undefined;

const validationStatus = ingredient.validation_status || (ingredient as ExtendedIngredient).validationStatus || [];
let validationResult = selectValidationResult(
ingredient.validation_status || [],
validationStatus,
activeManifestValidationResults,
);

Expand All @@ -318,7 +328,7 @@ export async function resultToAssetMap({
manifestData: await getManifestData(ingredientManifest, validationResult),
dataType: getIngredientDataType(ingredient),
validationResult,
trustSource: ((ingredient as Ingredient & { trust_source?: 'legacy' | 'none' | 'official' })?.trust_source || 'none') as 'legacy' | 'none' | 'official',
trustSource: ((ingredient as ExtendedIngredient)?.trust_source || (ingredient as ExtendedIngredient)?.trustSource || 'none') as 'legacy' | 'none' | 'official',
};

if (thumbnail?.dispose) {
Expand All @@ -338,16 +348,22 @@ export async function resultToAssetMap({
return null;
}

/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
function formattedGeneratorInfo(claim_generator: any): any {
const version = claim_generator?.version;
claim_generator.version = version?.replace(/\([^()]*\)/g, '');
interface GeneratorInfoShape {
name?: string;
version?: string | null;
icon?: string | null;
}

function formattedGeneratorInfo(claim_generator: GeneratorInfoShape) {
const cloned = { ...claim_generator };
const version = cloned?.version;
cloned.version = version ? version.replace(/\([^()]*\)/g, '') : null;

return claim_generator;
return cloned;
}

const claimGeneratorInfo = manifest?.claim_generator_info?.[0]
? formattedGeneratorInfo(manifest.claim_generator_info[0])
? formattedGeneratorInfo(manifest.claim_generator_info[0] as GeneratorInfoShape)
: null;

const claimGeneratorLabel =
Expand All @@ -358,9 +374,10 @@ export async function resultToAssetMap({

const claimGenerator: ClaimGeneratorDisplayInfo = {
label: claimGeneratorLabel,
icon: claimGeneratorInfo?.icon ?? null,
icon: (claimGeneratorInfo?.icon ? { identifier: claimGeneratorInfo.icon } : null) as unknown as Thumbnail | null,
};

// Extract Organization (O) from the native X.509 certificate subject tree
const safeSignatureInfo = manifest.signature_info
? { ...manifest.signature_info }
: null;
Expand All @@ -375,33 +392,24 @@ export async function resultToAssetMap({
: null,
claimGenerator,
signatureInfo: safeSignatureInfo,
producer: selectProducer(manifest)?.name ?? null,
editsAndActivityForLocale: async (locale) => {
const editsAndActivity = await selectEditsAndActivity(
manifest,
locale ?? DEFAULT_LOCALE,
);

if (editsAndActivity) {
const assertions = manifest.assertions;

let actionsAssertion: unknown;

if (Array.isArray(assertions)) {
actionsAssertion = assertions.find(
(a): a is { label: string; data: unknown } =>
typeof a === 'object' &&
a !== null &&
'label' in a &&
typeof (a as Record<string, unknown>)['label'] === 'string' &&
(a as Record<string, unknown>)['label'] === 'c2pa.actions'
);
} else if (assertions && typeof assertions === 'object') {
actionsAssertion = (assertions as Record<string, unknown>)['c2pa.actions'];
interface InferenceAssertion {
data?: {
metadata?: {
'com.adobe.inference'?: unknown;
};
};
}

const hasInference =
!!(actionsAssertion as { data?: { metadata?: Record<string, unknown> } })?.data?.metadata?.['com.adobe.inference'];
const assertionsArr = (manifest.assertions || []) as unknown[];
type AssItem = { label?: string; data?: unknown };
const actionsAss = assertionsArr.find((a: unknown) => (a as AssItem).label === 'c2pa.actions' || (a as AssItem).label === 'c2pa.actions.v2') as InferenceAssertion | undefined;
const hasInference = !!actionsAss?.data?.metadata?.['com.adobe.inference'];

const filteredEditsAndActivity = editsAndActivity.filter(
(value) => !!value.label,
Expand All @@ -415,7 +423,6 @@ export async function resultToAssetMap({

return null;
},
socialAccounts: selectSocialAccounts(manifest),
generativeInfo: selectGenerativeInfo(manifest),
exif: selectExif(manifest),
label: manifest.label ?? null,
Expand All @@ -435,14 +442,14 @@ export async function resultToAssetMap({
if (manifest.assertions instanceof Map) {
actionsAssertion = manifest.assertions.get('c2pa.actions.v2')?.[0] || manifest.assertions.get('c2pa.actions')?.[0] || manifest.assertions.get('c2pa.actions.v2') || manifest.assertions.get('c2pa.actions');
} else if (Array.isArray(manifest.assertions)) {
actionsAssertion = manifest.assertions.find((a: { label?: string }) => a.label === 'c2pa.actions.v2' || a.label === 'c2pa.actions');
actionsAssertion = manifest.assertions.find((a: unknown) => (a as { label?: string }).label === 'c2pa.actions.v2' || (a as { label?: string }).label === 'c2pa.actions');
} else {
actionsAssertion = manifest.assertions?.['c2pa.actions.v2'] || manifest.assertions?.['c2pa.actions'];
}

type C2paActionItem = { action: string; digitalSourceType?: string; parameters?: { digitalSourceType?: string } };
type AssertionValue = { data?: { actions?: C2paActionItem[] }; actions?: C2paActionItem[] };
const actions = (actionsAssertion as AssertionValue)?.data?.actions || (actionsAssertion as AssertionValue)?.actions || [];
type ActionEntry = { action: string; digitalSourceType?: string; parameters?: { digitalSourceType?: string } };
type AssertionBlock = { data?: { actions?: ActionEntry[] }; actions?: ActionEntry[] };
const actions = (actionsAssertion as AssertionBlock)?.data?.actions || (actionsAssertion as AssertionBlock)?.actions || [];
if (actions.length !== 1) return false;

// 3. First and only action must be c2pa.created
Expand Down
Loading
Loading